技术开发 频道

在非托管事件接受里处理来自于.NET 组件的事件


    本篇文章介绍非托管世界里的COM 对象通过使用Connection Points是怎样异步引起事件的,以及这些事件是怎样被.NET应用程序使用的方法,即我们将得到一个.NET组件来引起事件,然后让非托管接受来使用这些事件。一个.NET组件应该宣告事件,此事件在它的输出事件接口中代表每一个方法的委托实例。当引起事件时,就会调用事件表里的所有委托。这些委托引用通知目标处理程序,因此能呼叫由订户提供的正确的处理程序功能。非托管接受从事预定事件的工作,好像它在与COM 对象交流,此COM 对象支持输出接口使用连接点. COM可调用的包装处理映射事件处理模式,因此,当托管 .NET 事件发生时,COM 客户端的非托管处理程序仍然能够接受通知。

创建一个触发事件的.NET组件

    创建一个.NET组件,在恶劣的天气情况中通知客户。这个组件允许天气预报站设置被记录的风速度。如果风速超过一定的限制(300 mph),它能感觉到即将来临的龙卷风,通过发射OnTornadoWarning事件来通知客户。
Collapse 
using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Reflection;
using System.Diagnostics;


[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ITornadoWatchEvents
{
void OnTornadoWarning(int nWindSpeed);

}/* 结束接口ITornadoWatchEvents */


public interface IWeatherNotify {

int WindSpeed { get; set; }

}/* 结束接口IWeatherNotify */


public delegate void TornadoWarningDelegate(int nWindSpeed);


[
ComSourceInterfaces("ITornadoWatchEvents"),
ClassInterface(ClassInterfaceType.None)
]
public class WeatherNotify : IWeatherNotify
{
private int m_nWindSpeed = 20;

public event TornadoWarningDelegate OnTornadoWarning;

// 构造器
public WeatherNotify() {
}

public int WindSpeed
{
get {

// 返回当前风速
return m_nWindSpeed;
}

set {

// 为风速设置新值
m_nWindSpeed = value;

if(value >= 300) {

try {

if(null != OnTornadoWarning) {

OnTornadoWarning(m_nWindSpeed);

}
}
catch(Exception ex) {

Trace.WriteLine(ex.Message);
}
}

}

}

}/* 结束WeatherNotify类 */

    让我们仔细解析这个代码,试图理解正在发生的事情。ITornadoWatchEvents 接口是输出接口 ,非托管接受需要执行此输出接口 ,为了接受事件通知。这个接口由一个单方法OnTornadoWarning 组成,此单方法OnTornadoWarning 通知客户端龙卷风可能即将到来,也通知了当前的风速。注意:输出接口 ITornadoWatchEvents 被标记有InterfaceType attribute ,而InterfaceType attribute 上有位置参数ComInterfaceType.InterfaceIsIDispatch.。这是因为接口会默认地转移到IDL 中,然后再作为双重接口转移到类型库中。当它们试图在双重接口中接受时,一般脚本客户端会崩溃。,它们仅仅是友好纯分配界面。因此我们使用ComInterfaceType.InterfaceIsIDispatch 值注入一个InterfaceType 属性,强迫类型库工具为输出接口生成一个纯分配界面 。

    你必须定义一个委托 (TornadoWarningdelegate),此委托 (TornadoWarningdelegate)与输出接口里的OnTornadoWarning方法标记式,完全匹配。如果在输出接口中拥有不止一个方法,必须为每一个方法定义相匹配的委托。现在已经定义了输出接口,并且为输出接口里的每一个方法找到了相匹配的委托。现在就是最重要的部分。你必须定义事件,代表在输出接口中为每一个方法所定义的委托。
public event TornadoWarningdelegate OnTornadoWarning;
    事件代表的委托实例需要拥有和输出接口里相对应的方法。这意味代表TornadoWarning,委托实例中的的event需要被命名为OnTornadoWarning。这就是它所要做的。现在你已经准备发送事件通知了。在例子中,当WindSpeed被设置到一个等于或者大于300的值时,就发送了通知。首先,如果存在事件的委托实例时,必须检查它,然后发送OnTornadoWarning事件。因为被event代表的委托是一个多点传送委托,所有的COM 接受者以及委托调用列表中的客户将会被通知到:地平线上隐隐约约有一个难题。你可以使用下面命令,创建上面的.NET组件。

csc /target:library /r:System.dll /out:WeatherNotify.dll WeatherNotify.cs
    你可以在RegAsm.exe上运行程序集,并注册它,在它外面生成类型库。在VB6客户端中可以引用类型库,VB6客户端会接受事件。
regasm WeatherNotify.dll /tlb:WeatherNotify.tlb
处理VB6客户端应用程序中的事件

    这里有一个VB 6.0客户端,它从WeatherNotify组件中预定OnTornadoWarning事件通知。它是一个简单的基于Form的应用程序,通过使用WithEvents关键词,此基于Form的应用程序预定事件通知。当WindSpeed 被设置成一个等于或者大于300时,objWeatherNotify_OnTornadoWarning子程序从.NET组件接受事件通知。



Collapse 
Dim WithEvents objWeatherNotify As WeatherNotify.WeatherNotify

Private Sub Form_Load()
Set objWeatherNotify = New WeatherNotify.WeatherNotify
End Sub

Private Sub SetWindSpeedButton_Click()

Me.LabelWarning = ""

objWeatherNotify.WindSpeed = Me.WindSpeed

End Sub

Private Sub objWeatherNotify_OnTornadoWarning(ByVal nWindSpeed As Long)

Me.LabelWarning = "Tornado Warning: Current Wind Speeds : " & _
nWindSpeed & " mph"

End Sub
    先前知道运行库可调用的包装和元数据帮助程序类型是怎样翻译连接点事件,以此来处理基于委托的事件处理语法,因此.NET应用程序可以从COM 组件接受事件。同样,在这里,COM可调用的包装做绝大部分艰苦的工作,允许非托管代码从.NET 组件预定事件通知,把这些事件运输到非托管 COM领域里各自的处理程序中。

安装包含.NET 组件的程序集

    在先前看到的例子中,把.NET 组件和同一目录下COM 元数据代理程序集设置为应用程序,而且此应用程序正在使用它,因此,当它探索程序集时,运行库程序集解析器就会为它定位。在引用它的应用程序同一目录下以这样的方式安装的程序集 被称作专用程序集。另外一种定位被引用的程序集的方法就是使用配置文件,告诉程序集解析器到哪里寻找被引用的程序集。当.NET运行库运转并绑定到这样的专用程序集上时,它与译本无关。如果你的程序集总被大量的应用程序使用,在全球储藏室里安装程序集非常有用,因此其它应用程序可以享用并使用此程序集。这个被享用的程序集储藏室被称为全局装配件缓存 (全局程序集缓存 )。被安装在全局装配件缓存的程序集被称为共享名程序集或者是强名称程序集.这是因为这些程序集有一个典型的"共享名" 或者是 "强名称"与它相连接,它们附带一个文本名,版本号码, 公钥标记, 文化信息, 以及数字化签名.因此怎样为程序集生成一个Strong name。你需要做的第一件事情就是使用强名称工具(SN.exe),生成金钥对。你可以从命令行运行SN.exe,创建一个新的任意钥对。

   sn -k MyKeyPair.snk

   如果一切顺利,你应该看到下面的信息。
Key pair written to MyKeyPair.snk
    一旦生成了钥对,你需要将这个钥对与程序集连接起来,要为程序集生成一个共享名。使用   System.Reflection.AssemblyKeyFileAttribute,你可以完成这一过程。AssemblyKeyFile属性把钥对文件(此钥对是从SN.exe生成的)与程序集连接起来,因此公钥 和数字签名(是通过使用私钥和程序集清单里的信息生成的)可以被用于生成程序集的共享名称。在编译过程中,要求钥对文件生成完全有效的共享名标记式.。但是出于安全问题考虑,大部分组织都能将它们的专用键输送到程序集的开发者。正因为这个原因,这里有一个可用的Delay signing选择。

    System.Reflection.AssemblyDelaySignAttribute允许以后用专用键标记程序集。

    让我们创建Temperature.dll .NET程序集,先前把Temperature.dll .NET程序集作为一个强名称程序集使用。为了完成这些必须把AssemblyKeyFile属性增添到你的组件。如果正在使用Visual Studio.NET,这个属性以及项目方案AssemblyInfo.cs文件中其它相类似的全球属性可以利用一个展位符。你能编辑文件,指定MyKeyPair.snk作为包含你的钥对的文件。
[assembly:AssemblyKeyFile("MyKeyPair.snk")]
    你可以使用System.Reflection.AssemblyVersionAttribute为你的程序集指定译本信息。这个属性允许指定程序集的译本。此译本通常遵循major.minor.build.revision模式。在Visual Studio.NET的C# Class Library solution AssemblyInfo.cs中,这个属性通常可以用到一个占位符。你可以在那里编辑这个属性。

[assembly: AssemblyVersion("1.0.0.0")]
    当编辑了AssemblyInfo.cs文件中上面指定的两个属性后,重建Temparature组件。现在,你程序集有一个强名称与它相连接。为了证明这个,使用强名称工具展示下面的命令,列出程序集的公共钥和记号。
sn -Tp Temperature.dll
    一个与此相似的输出被返回:
Public key is 
0024000004800000940000000602000000240000525341310004000001000100ed87f0432cbf37
fc70eec5d0e59d7e47327729cd99e257a2790c690957691f20c01b47d46a72b20b4f37a829f6ad
82e6594221bbd0193b5499ca0a83db7fc9b78bcb07177f02ef9c827688246f6073f34405e9a441
37017cf6ed52c5001272b0b820926f078bbe8705fa9d411a18d692c94be9541bb3fde38b1b1f79
5a06dde8

Public key token is f80b1601a4d8a9dd


    如果你使用专用程序集(此专用程序集没有一个强名称与它相连接)上的同一命令,这就是你所得到的。

sn -Tp WeatherNotify.dll 

WeatherNotify.dll does not represent a strongly named assembly
    现在已经准备了在全局程序集缓存(全局程序集缓存 )中安装Temperature.dll,因此,当应用程序试图下载或者使用这个程序集时,程序集分解器可以在全局程序集缓存中定位这个程序集。你可以使用下面的命令,列出全局程序集缓存中所有程序集。
全局程序集缓存 util -l
    或者,你可以使用shfusion.dll里的Windows shell extension,shfusion.dll允许你在全局程序集缓存中浏览,添加或者删除程序集。如果你浏览到你的Windows Directory下面的Assembly文件,这个扩展名为你提供一个程序集(此程序集被安装在全局程序集缓存 中)以及一些属性,例如:名称, 类型, 版本, 文化和私钥标志。为了安装Temperature.dll程序集,只要输入命令行中的以下命令,而且此命令应该被安装在全局程序集缓存中。
全局程序集缓存 util -i Temperature.dll
    如果一切顺利,你可以得到这样一个信息。

Assembly successfully added to the cache
    你可以把强名称程序集拖到由shell extension 提供的view中,而且它应该为你安装它自己。一旦程序集被安装在了全局程序集缓存中,你就没必要把程序集放到应用程序文件夹中,也不会和配置文件相混淆告诉分解器 到哪里找到程序集。现在,程序集分解器 在全局程序集缓存中定位程序集。



    安装全局程序集缓存中的程序集有许多优点,例如:不同译本数目的程序集的并行执行,程序集的单实例(也被称为代码页共享),当被多个应用程序使用,下载次数被减少时,程序集的单实例(也被称为代码页共享)允许执行时下载更少的程序集备份,推动程序集中基于快速修复工程(QFE)的热部署。
0
相关文章