回调函数和 .NET C# COM 组件






4.36/5 (7投票s)
如何让您的 C# 组件回调 Perl 子例程
引言
最近,我的合作者需要将一个 C# 类库转换为一个 COM 兼容组件,该组件可以从 C++/C#/Perl/脚本环境中调用。 已经有很多关于如何实现这方面的信息,但他有一个更复杂的要求。 该要求是他的 Perl 脚本需要调用一个 C# 函数(例如 Analyze()
)。 Analyze()
函数通常需要很长时间来处理,并且客户端需要定期更新进度计数器。 最好使用回调来解决这个问题。
然而,很少有现成的资料可以解释如何实现这一点。
下面的文章解释了如何在 Perl 环境中处理多个 .NET 事件。
背景
本文试图解释适用于 COM 客户端的事件源和事件接收器的概念。
代码包含一个 C# COM 服务器和一个示例 Perl 客户端,它以最简单的方式解释了回调函数的概念。
本文通过展示如何使用 Perl 中存在的 Win32::OLE
模块,并使用它来捕获 .NET 组件中生成的事件,来实现上述目的。
Using the Code
要使用附带的代码,需要执行几个步骤。
- 解压缩 CSharp_and_Scripting_New_src.zip。
- 浏览到 CSharp_and_Scripting_New\CallBack Server\CallBack 文件夹。
- 运行 register.bat
- 如果 register.bat 脚本对您不起作用,请在 Visual Studio 2005 中打开项目 CallBack.sln。 转到“工具”菜单 -> Visual Studio 2005 命令提示符。
- 在命令窗口中将目录更改为 CSharp_and_Scripting_New\CallBack Server\CallBack\bin\debug 文件夹。
- 键入
regasm CallBack.dll /tlb:Callback.tlb
- 键入
gacutil -i CallBack.dll
- 现在,.NET C# COM 服务器已在您的系统中注册,您可以运行 CSharp_and_Scripting_New\CallBack Client\Sample1.pl 以查看如下所示的输出
描述
COM 服务器代码
COM 服务器已用 C# 编写。 C# 没有函数指针的直接概念。 相反,它定义了称为 delegate
的东西,它是对函数的安全引用形式。 现在为了利用 delegate
的强大功能,让我们在我们的类 DotNetEventSender
中定义一些 delegate
,如下所示
// No need to show this delegate to COM
[ComVisible(false)]
public delegate void MyEventTarget(string msg);
[ComVisible(false)]
public delegate void MyEventTarget2(int nTimerCounter);
public event MyEventTarget TheEvent;
public event MyEventTarget2 TheEvent2;
如您所见,我们在类中声明了两种不同类型的 delegate
,并将两个事件与这些 delegate
关联起来。 现在让我们声明一个接口,我们的类将公开这个接口
//This interface defines purely the events.
//DotNetEventSender should implement this interface with the
//ComSourceInterfaces() attribute to become an Event Source.
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface CallBackEventInterface
{
[DispId(1)] void TheEvent(string msg);
[DispId(2)] void TheEvent2(int nTimerCounter);
}
然后,我们的类将公开此接口,如下所示
//Identifies these interfaces that are exposed as COM event sources //for the attributed class. [ComSourceInterfaces(typeof(CallBackEventInterface))] //Tells the compiler not to generate an interface automatically and that we are //implementing our own interface (IDotNetEventSender) [ClassInterface(ClassInterfaceType.None)] public class DotNetEventSender:IDotNetEventSender { ...
注意属性标签
[ComSourceInterfaces(typeof(CallBackEventInterface))]
这告诉编译器我们有兴趣为我们的类公开一个事件源。 然后,事件接收器将在 Perl 客户端中实现,该客户端包明确地消耗(处理)我们的 C# 服务器生成的所有事件。
现在,如果没有实现 Perl 客户端可以调用的某些服务器函数,那么公开事件源就没什么用了。 例如,Perl 客户端将调用函数 Run()
,这将启动 C# 服务器中的计时器。 然后,该计时器将间歇性地触发事件源,然后可以在 Perl 客户端中处理这些事件源。
因此,现在让我们在 C# 服务器中实现一些类函数。
private int m_nLoopCounter=0;
public void Run()
{
Console.Out.WriteLine("Inside Run");
//Note that the event handlers will be specified in the client
//(e.g. Perl Script)
//start a timer which call the Events every one second.
System.Timers.Timer progress = new System.Timers.Timer(1000);
progress.Elapsed += new ElapsedEventHandler(TimerFunction);
progress.Start();
}
private void TimerFunction(object source, ElapsedEventArgs e)
{
m_nLoopCounter++;
TheEvent("Hello, World!");
TheEvent2(m_nLoopCounter);
}
Perl 客户端代码
在 Perl 客户端代码中,基本步骤是创建 C# 服务器的对象,如下所示
my $TM = Win32::OLE->new('CallBack.DotNetEventSender');
完成此操作后,我们希望通知 C# 服务器,我们有兴趣接收服务器上生成感兴趣的事件时的通知。 我们通过声明来实现这一点
Win32::OLE->WithEvents($TM, 'MyEvents');
在这里,我们通知 C# 服务器所有事件都应转发到包 MyEvents
。 该包将定义子例程,其名称与 C# 服务器中的事件完全相同。
package MyEvents;
#The name of the subroutine should be the exactly the same as the name of the
#event in the C# component.
sub TheEvent
{
my ($obj,$args) = @_;
print "TheEvent() : ".$args."\n";
}
sub TheEvent2
{
my ($obj,$args) = @_;
print "TheEvent2(): Timer Fired Count: ".$args."\n";
}
现在,如果我们不运行一个循环,该循环将使 Perl 客户端等待事件消息到达,那么定义以上所有代码就没什么用了。 为此,我们将代码添加到主包的末尾,如下所示
#keeps the Perl client active, processing the event messages as they
#occur repeatedly.
Win32::OLE->MessageLoop();
就这样,伙计们! 只需运行 sample1.pl 即可查看上述逻辑的实际应用。 祝您“互操作”愉快! :-)
历史
- 2008 年 2 月 1 日:初始帖子