在.NET与COM的互操作性领域,有许多示例和文章,但它们往往忽略了在运行时附加事件的重要性。本文将介绍如何构建一个.NET组件,将其标记为COM可见,并在VB6中运行时使用它并附加到其事件上。
KRISHNA PRASAD.N.撰写了一篇出色的文章,名为“.NET- COM互操作性”,其中详细解释了从COM客户端调用.NET组件、.NET Marshalling、Interop marshaler、COM marshaler等问题。
如果需要了解如何一步步创建C#COM互操作DLL,可以参考RakeshGunijan的“C# COM”。
早期绑定和晚期绑定各有优缺点。通常更倾向于晚期绑定,尤其是当使用别人的API时,因为晚期绑定的优势在于可以减少一些版本依赖性。如果需要使用或分发每月更新的API或应用程序,相信,晚期绑定是更好的选择。
函数指针是一种约定,它允许将用户定义的函数的地址作为参数传递给另一个函数,以便在应用程序中使用。
将构建一个COM互操作,从VB6运行时使用它(无需向项目添加任何引用)并附加到其事件。以下是选择:
从VB6到C#传递事件地址作为函数指针,并从C#调用它。(感谢Misha Shneerson在MSDN论坛上解决这个问题。)
public void AttachToAnEventMethod1(int address)
{
CallBackFunction cb = (CallBackFunction)Marshal.GetDelegateForFunctionPointer(
new IntPtr(address),
typeof(CallBackFunction));
SetCallbackMethod1(cb);
}
从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);
将构建一个简单的类用于测试目的。目标是附加到一个事件,当在运行时执行MultiplyAndNotify方法时,从VB6获得反馈。
[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)
{
object objApp_Late = this.consumer;
Type objClassType = this.consumer.GetType();
object ret = objApp_Late.GetType().InvokeMember(
this.callbackEventName, BindingFlags.InvokeMethod,
null, objApp_Late, null);
}
if (this.cb != null)
{
cb.DynamicInvoke();
}
}
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;
}
}
首先需要使用晚期绑定连接到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