C# COM 动态绑定事件
本文指导如何构建一个 .NET 组件,并在 VB6 项目中通过迟绑定在运行时使用它,附加其事件并获取回调。
引言
本文指导如何构建一个 .NET 组件,并在 VB6 项目中使用它。关于这个问题有很多示例,那么我为什么要写一个新的呢?在我看来,其他文章中缺失的部分是在运行时附加其事件。因此,在本文中,我们将构建一个 .NET 组件,将其标记为 COM 可见组件,在 VB6 中运行时使用它,并附加到它的事件。
.NET - COM 互操作性

KRISHNA PRASAD.N. 实际上写了一篇关于这个问题的很棒的文章,叫做 .NET - COM 互操作性。从 COM 客户端调用 .NET 组件,.NET 封送处理,互操作封送处理程序,COM 封送处理程序等问题都得到了很好的解释。
如果你需要记住如何一步一步地创建 C# COM 互操作 DLL,你还应该查看 C# COM (作者: RakeshGunijan)。
早绑定与迟绑定
早绑定和迟绑定各有其优点和缺点。我不会深入细节。我主要喜欢迟绑定,尤其是在使用别人的 API 时,因为迟绑定具有消除一些版本依赖性的优势。如果你必须使用或分发每月更新的 API 或应用程序,请相信我,迟绑定是更好的选择。
什么是函数指针
函数指针是一种约定,它使你能够将用户定义函数的地址作为参数传递给你声明在你的应用程序中使用的另一个函数。
有哪些选择
我们将构建一个 COM 互操作,在运行时从 VB6 中使用它(无需向我们的项目添加任何引用),并附加到它的事件。以下是选项。
选项 1 - 使用回调函数
将我们的事件地址作为函数指针从 VB6 传递到 C#,并从 C# 调用它。(感谢 Misha Shneerson 在 MSDN 论坛上解决了这个问题。)
public void AttachToAnEventMethod1(int address)
{
CallBackFunction cb = (CallBackFunction)Marshal.GetDelegateForFunctionPointer
(new IntPtr(address), typeof(CallBackFunction));
SetCallbackMethod1(cb);
}
选项 2 - 使用反射
从 VB6 将使用者对象和回调函数的名称传递给 C#,并通过反射从 C# 调用该函数。
object objApp_Late = this.consumer;
Type objClassType = this.consumer.GetType();
object ret = objApp_Late.GetType().InvokeMember
(this.callbackEventName, BindingFlags.InvokeMethod, null, objApp_Late, null);
构建 C# COM
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("CSharpApi.CsApiCore")]
[ComSourceInterfaces(typeof(NotifyInterface))]
public class CsApiCore : _CsApiCore
{
private string sessionID = "";
public string SessionID
{
get { return sessionID; }
set { sessionID = value; }
}
public event NotifyDelegate NotifyEvent;
public CsApiCore()
{
this.SessionID = System.Guid.NewGuid().ToString().ToUpper();
}
public int Add(int a, int b)
{
return a + b;
}
public int MultiplyAndNotify(int a, int b)
{
for (int i = 0; i < 2; i++)
{
Thread.Sleep(500);
notifyClients();
}
return a * b;
}
private void notifyClients()
{
if (NotifyEvent != null)
{
NotifyEvent();
}
if (this.consumer != null)
{
//method2
object objApp_Late = this.consumer;
Type objClassType = this.consumer.GetType();
object ret = objApp_Late.GetType().InvokeMember(this.callbackEventName,
BindingFlags.InvokeMethod, null, objApp_Late, null);
//method2
}
if (this.cb != null)
{
//method1
cb.DynamicInvoke();
//method1
}
}
private object consumer = null;
private string callbackEventName = "";
public void AttachToAnEventMethod2(object consumerObject, string eventName)
{
this.consumer = consumerObject;
this.callbackEventName = eventName;
}
public void DetachMethod2()
{
this.callbackEventName = "";
this.consumer = null;
}
public void AttachToAnEventMethod1(int address)
{
CallBackFunction cb = (CallBackFunction)
Marshal.GetDelegateForFunctionPointer(new IntPtr(address),
typeof(CallBackFunction));
SetCallbackMethod1(cb);
}
public void DetachMethod1()
{
this.cb = null;
}
private Delegate cb = null;
public void SetCallbackMethod1([MarshalAs(UnmanagedType.FunctionPtr)]
CallBackFunction callback)
{
this.cb = callback;
}
我们构建了一个简单的类用于测试目的。我们的目标是附加到一个事件,并且当我们在运行时执行 MultiplyAndNotify
方法时,从 VB6 获取反馈。
从 VB6 使用 C# COM

我们首先需要使用迟绑定连接到 C# COM。
Dim apiHandler As Object
Dim obj As Object
Set obj = CreateObject("CSharpApi.CsApiCore")
If obj Is Nothing Then
MsgBox ("Could not start Cs Api")
Else
Set apiHandler = obj
End If
我们可以测试一切是否正常工作。我们将使用 CallByName
函数。CallByName()
函数用于通过执行方法或设置属性的值来操作对象。
Dim sRet As String
sRet = CallByName(apiHandler, "SessionID", VbGet)
Msgbox sRet
我们需要做的是使用方法 1,附加到 C# 事件,执行一个方法 (MultiplyAndNotify
),并从 VB6 获取回调。
Dim functinPointerAddress As Long
functinPointerAddress = getAddressOfFunction(AddressOf Vb6NotifyEventOnModule)
Dim o As Object
Set o = Me
ret = CallByName(apiHandler, "AttachToAnEventMethod1", VbMethod, functinPointerAddress)
ret = CallByName(apiHandler, "MultiplyAndNotify", VbMethod, 3, 5)
Dim oRet
oRet = CallByName(apiHandler, "DetachMethod1", VbMethod)
MsgBox ret
Using the Code
下载源文件。你将看到一个 CsApiTestSln.sln 解决方案文件。打开并构建解决方案。CsApiTest
是此解决方案中的主要 API 项目。你可以使用 CsApiConsumer
C# 项目来调试主要 API。在 CsApiTest\bin\Debug 子目录中,你将看到一个名为 CsApiVb6Test.vbp 的 VB6 项目。这是一个用于使用主要 API 的 VB6 客户端。
结论
AddressOf
函数的限制是:你只能检索包含在标准 VB 模块中的函数或 sub
(public
或 private
)的地址。这是无法避免的。因此,如果你使用 method1
,你的回调函数或事件必须包含在标准 VB 模块(bas 文件)中。
如果你决定使用方法 2,你必须传递一个对象和一个事件名称,并从 C# 调用它。在这种情况下,你可以使用类模块或窗体(正如你在 VB6 示例项目中看到的那样)。
根据你的需求决定一种方法。
参考文献
历史
- 2010 年 4 月 21 日:首次发布