实际上,希望还没有失去。可以从对象类外部触发COM对象上的事件。这个功能实际上是由COM本身提供的,尽管以间接的方式。
在COM中,事件采用发布/订阅模型工作。具有事件的COM对象(“事件源”)发布事件,一个或多个其他COM对象通过将事件处理程序附加到源对象(处理程序称为“事件接收器”)来订阅事件。通常,源对象通过简单地循环遍历所有事件接收器并调用适当的处理程序方法来引发事件。
那么这怎么帮助您呢?恰好,COM允许您查询事件源,以获取当前订阅源对象事件的所有事件接收器对象列表。一旦您拥有事件接收器对象列表,就可以通过调用每个接收器对象的事件处理程序来模拟引发事件。
请注意:我过于简化细节并且对某些术语进行了自由解释,但这是COM中事件如何工作的简短(并且有点政治不正确)版本。
您可以利用这些知识从外部代码上提高COM对象上的事件。实际上,使用System.Runtime.InteropServices和System.Runtime.InteropServices.ComTypes命名空间中的COM互操作支持,可以在C#中完成所有这些。
编辑:
我编写了一个实用程序类,可让您从.NET上提高COM对象上的事件。它非常容易使用。以下是使用您问题中的事件接口的示例:
MyObj legacyComObject = new MyObj();
string messageData = "Hello, world!";
bool cancel = false;
foreach(__MyObj sink in ComEventUtils.GetEventSinks<__MyObj>(legacyComObject))
{
sink.Message(messageData, ref cancel);
if(cancel == true)
{
break;
}
}
以下是
ComEventUtils
类的代码(以及帮助类
SafeIntPtr
,因为我很谨慎,想要一种好的方式来处理与 COM 相关的代码所需的
IntPtr
):
免责声明:我没有彻底测试下面的代码。在一些地方,代码执行手动内存管理,因此可能会在您的代码中引入内存泄漏的可能性。此外,我没有添加错误处理到代码中,因为这只是一个示例。请小心使用。
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using COM = System.Runtime.InteropServices.ComTypes;
namespace YourNamespaceHere
{
public static class ComEventUtils
{
public static List<T> GetEventSinks<T>(object comObject)
{
List<T> sinks = new List<T>();
List<COM.IConnectionPoint> connectionPoints = GetConnectionPoints(comObject);
foreach(COM.IConnectionPoint connectionPoint in connectionPoints)
{
List<COM.CONNECTDATA> connections = GetConnectionData(connectionPoint);
foreach (COM.CONNECTDATA connection in connections)
{
object candidate = connection.pUnk;
try
{
sinks.Add((T)candidate);
}
catch { }
}
foreach (COM.CONNECTDATA connection in connections)
{
Marshal.ReleaseComObject(connection.pUnk);
}
}
return sinks;
}
private static List<COM.IConnectionPoint> GetConnectionPoints(object comObject)
{
COM.IConnectionPointContainer connectionPointContainer = (COM.IConnectionPointContainer)comObject;
COM.IEnumConnectionPoints enumConnectionPoints;
COM.IConnectionPoint[] oneConnectionPoint = new COM.IConnectionPoint[1];
List<COM.IConnectionPoint> connectionPoints = new List<COM.IConnectionPoint>();
connectionPointContainer.EnumConnectionPoints(out enumConnectionPoints);
enumConnectionPoints.Reset();
int fetchCount = 0;
SafeIntPtr pFetchCount = new SafeIntPtr();
do
{
if (0 != enumConnectionPoints.Next(1, oneConnectionPoint, pFetchCount.ToIntPtr()))
{
break;
}
fetchCount = pFetchCount.Value;
if (fetchCount > 0)
connectionPoints.Add(oneConnectionPoint[0]);
} while (fetchCount > 0);
pFetchCount.Dispose();
return connectionPoints;
}
private static List<COM.CONNECTDATA> GetConnectionData(COM.IConnectionPoint connectionPoint)
{
COM.IEnumConnections enumConnections;
COM.CONNECTDATA[] oneConnectData = new COM.CONNECTDATA[1];
List<COM.CONNECTDATA> connectDataObjects = new List<COM.CONNECTDATA>();
connectionPoint.EnumConnections(out enumConnections);
enumConnections.Reset();
int fetchCount = 0;
SafeIntPtr pFetchCount = new SafeIntPtr();
do
{
if (0 != enumConnections.Next(1, oneConnectData, pFetchCount.ToIntPtr()))
{
break;
}
fetchCount = pFetchCount.Value;
if (fetchCount > 0)
connectDataObjects.Add(oneConnectData[0]);
} while (fetchCount > 0);
pFetchCount.Dispose();
return connectDataObjects;
}
}
public class SafeIntPtr : IDisposable
{
private bool _disposed = false;
private IntPtr _pInt = IntPtr.Zero;
public SafeIntPtr()
: this(0)
{
}
public SafeIntPtr(int value)
{
_pInt = Marshal.AllocHGlobal(sizeof(int));
this.Value = value;
}
public int Value
{
get
{
if (_disposed)
throw new InvalidOperationException("This pointer has been disposed.");
return Marshal.ReadInt32(_pInt);
}
set
{
if (_disposed)
throw new InvalidOperationException("This pointer has been disposed.");
Marshal.WriteInt32(_pInt, Value);
}
}
public IntPtr ToIntPtr()
{
return _pInt;
}
public void Dispose()
{
if (!_disposed)
{
Marshal.FreeHGlobal(_pInt);
_disposed = true;
}
}
~SafeIntPtr()
{
if (!_disposed)
Dispose();
}
}
}