我正在使用C#.NET7,在那里我可以创建编译时未知类型的COM对象.

var comTypeName = "Word.Application";//Assume this is passed in by the user and is unknown at compile time.
var comType = Type.GetTypeFromProgID(comTypeName);
var comObj = Activator.CreateInstance(comType);

我希望收到有关COM对象上发生的事件的通知.我广泛地研究了大约IConnectionPoint/IConnectionPointContainer个,事件槽,IConnectionPoint.Advise()个,我发现没有一个能解决我的问题.因此,我怀疑这个问题要么在C#中是不可行的,要么是如此明显和不言自明,以至于没有人觉得有必要在任何文档中解释它.我希望是后者.

问题的症结在于,我找到的每个示例都与其类型提前知道的COM对象一起工作.因此,代码知道要侦听哪些事件,并定义实现这些事件的接口,并将其传递给IConnectionPoint.Advise():

icpt = (IConnectionPoint)someobject;
icpt.Advise(someinterfacethatimplementsallevents, out var _cookie);

根据我的研究,Advise()的第一个参数是一个对象,它实现了源对象正在寻找的接口.那么,当我在编译时甚至不知道源对象是什么时,我怎么知道接口应该是什么呢?

一些研究似乎认为接收器对象应该实现IDispatch.但在这上面会调用什么方法?Invoke()

这听起来似乎有些道理,但.NET Core(我使用的是.NET7)之前的任何版本都go 掉了IDispatch和其他许多COM功能.那么他们是说我们应该使用.NET中不再存在的接口吗?

为了开始解决这个问题,我从网上的各种来源找到了IDispatch个实现:

[Guid("00020400-0000-0000-c000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
public interface IDispatch
{
    //
    // Omitting type info functions for brevity.
    //
    
    //Invoke seems to be what we care about.
    void Invoke(int dispIdMember,
        [MarshalAs(UnmanagedType.LPStruct)] Guid iid,
        int lcid,
        System.Runtime.InteropServices.ComTypes.INVOKEKIND wFlags,
        [In, Out][MarshalAs(UnmanagedType.LPArray)]
        System.Runtime.InteropServices.ComTypes.DISPPARAMS[] paramArray,
        out object? pVarResult,
        out System.Runtime.InteropServices.ComTypes.EXCEPINFO pExcepInfo,
        out uint puArgErr);
}

我不确定的是,我是否应该这样做:

public class MyDispatch : IDispatch
{
    void Invoke(int dispIdMember,
        [MarshalAs(UnmanagedType.LPStruct)] Guid iid,
        int lcid,
        System.Runtime.InteropServices.ComTypes.INVOKEKIND wFlags,
        [In, Out][MarshalAs(UnmanagedType.LPArray)]
        System.Runtime.InteropServices.ComTypes.DISPPARAMS[] paramArray,
        out object? pVarResult,
        out System.Runtime.InteropServices.ComTypes.EXCEPINFO pExcepInfo,
        out uint puArgErr)
        {
            //Do something with the event info and dispatch to the appropriate place in my code.
            //What I *really* need here is the string name of the event so that I can figure out where to properly dispatch it to.
            /*
            if (nameofevent == "Changed")
                ChangeHandler();
            else if (nameofevent == "Closed")
                ClosedHandler();
            */
        }
}

在这一点上,我已经达到了网上可用信息的尽头来解决这个问题,不确定如何进一步进行.

推荐答案

当实例是动态的时,获取事件并不容易.但正如您发现的那样,您可以使用原始的COM接口(IConnectionPointIConnectionPointContainerIDispatch)获取它们

下面是一个C#实用程序类,它包装IDispatch并连接到所请求的"调度接口"事件接口.

首先要做的是确定:

  • 您想要的Display接口的IID(接口ID)(包含所需事件的IID).
  • 事件DISPID(标识事件的整数).

为此,您可以使用OleView tool from the Windows SDK,打开描述COM对象支持的公共接口的类型库文件.它通常是一个.TLB文件(或嵌入在.dll中),但对于Office,它是一个.OLB.对于Word,它位于C:\Program Files\Microsoft Office\root\Office16\MSWORD.OLB(或类似的路径).

在此示例中,我希望获得Application.DocumentOpen个事件.这是OleView向我展示的:

enter image description here

因此,以下是如何获得活动的方法:

static void Main()
{
    var comTypeName = "Word.Application";
    var comType = Type.GetTypeFromProgID(comTypeName);
    dynamic comObj = Activator.CreateInstance(comType);
    try
    {
        // to get IID and DISPIDs from DispInterfaces, open C:\Program Files\Microsoft Office\root\Office16\MSWORD.OLB (or similar)
        // with OleView tool from Windows SDK
        var dispatcher = new Dispatcher(new Guid("000209FE-0000-0000-C000-000000000046"), comObj);
        dispatcher.Event += (s, e) =>
        {
            switch (e.DispId)
            {
                case 4: // method DocumentOpen(Document doc)
                    dynamic doc = e.Arguments[0]; // arg 1 is "doc"
                    Console.WriteLine("Document '" + doc.Name + "' opened.");
                    break;
            }
        };

        comObj.Documents.Open(@"c:\somepath\some.docx");
    }
    finally
    {
        comObj.Quit(false);
    }
}

和Dispatcher实用程序类:

using System;
using System.Runtime.InteropServices;
using System.Threading;

public class Dispatcher : IDisposable, Dispatcher.IDispatch, ICustomQueryInterface
{
    private IConnectionPoint _connection;
    private int _cookie;
    private bool _disposedValue;

    public event EventHandler<DispatcherEventArgs> Event;

    public Dispatcher(Guid interfaceId, object container)
    {
        ArgumentNullException.ThrowIfNull(container);
        if (container is not IConnectionPointContainer cpContainer)
            throw new ArgumentException(null, nameof(container));

        InterfaceId = interfaceId;
        Marshal.ThrowExceptionForHR(cpContainer.FindConnectionPoint(InterfaceId, out _connection));
        _connection.Advise(this, out _cookie);
    }

    public Guid InterfaceId { get; }

    protected virtual void OnEvent(object sender, DispatcherEventArgs e) => Event?.Invoke(this, e);
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            var connection = Interlocked.Exchange(ref _connection, null);
            if (connection != null)
            {
                connection.Unadvise(_cookie);
                _cookie = 0;
                Marshal.ReleaseComObject(connection);
            }
            _disposedValue = true;
        }
    }

    ~Dispatcher() { Dispose(disposing: false); }
    public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); }

    CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out IntPtr ppv)
    {
        if (iid == typeof(IDispatch).GUID || iid == InterfaceId)
        {
            ppv = Marshal.GetComInterfaceForObject(this, typeof(IDispatch), CustomQueryInterfaceMode.Ignore);
            return CustomQueryInterfaceResult.Handled;
        }

        ppv = IntPtr.Zero;
        if (iid == IID_IManagedObject)
            return CustomQueryInterfaceResult.Failed;

        return CustomQueryInterfaceResult.NotHandled;
    }

    int IDispatch.Invoke(int dispIdMember, Guid riid, int lcid, System.Runtime.InteropServices.ComTypes.INVOKEKIND wFlags, ref System.Runtime.InteropServices.ComTypes.DISPPARAMS pDispParams, IntPtr pvarResult, IntPtr pExcepInfo, IntPtr puArgErr)
    {
        var args = pDispParams.cArgs > 0 ? Marshal.GetObjectsForNativeVariants(pDispParams.rgvarg, pDispParams.cArgs) : null;
        var evt = new DispatcherEventArgs(dispIdMember, args);
        OnEvent(this, evt);
        var result = evt.Result;
        if (pvarResult != IntPtr.Zero)
        {
            Marshal.GetNativeVariantForObject(result, pvarResult);
        }
        return 0;
    }

    int IDispatch.GetIDsOfNames(Guid riid, string[] names, int cNames, int lcid, int[] rgDispId) => E_NOTIMPL;
    int IDispatch.GetTypeInfo(int iTInfo, int lcid, out /*ITypeInfo*/ IntPtr ppTInfo) { ppTInfo = IntPtr.Zero; return E_NOTIMPL; }
    int IDispatch.GetTypeInfoCount(out int pctinfo) { pctinfo = 0; return 0; }

    private const int E_NOTIMPL = unchecked((int)0x80004001);
    private static readonly Guid IID_IManagedObject = new("{C3FCC19E-A970-11D2-8B5A-00A0C9B7C9C4}");

    [ComImport, Guid("00020400-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IDispatch
    {
        [PreserveSig]
        int GetTypeInfoCount(out int pctinfo);

        [PreserveSig]
        int GetTypeInfo(int iTInfo, int lcid, out /*ITypeInfo*/ IntPtr ppTInfo);

        [PreserveSig]
        int GetIDsOfNames([MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 2)] string[] names, int cNames, int lcid, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] int[] rgDispId);

        [PreserveSig]
        int Invoke(int dispIdMember, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, int lcid, System.Runtime.InteropServices.ComTypes.INVOKEKIND wFlags, ref System.Runtime.InteropServices.ComTypes.DISPPARAMS pDispParams, IntPtr pvarResult, IntPtr pExcepInfo, IntPtr puArgErr);
    }

    [ComImport, Guid("b196b286-bab4-101a-b69c-00aa00341d07"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IConnectionPoint
    {
        [PreserveSig]
        int GetConnectionInterface(out Guid pIID);

        [PreserveSig]
        int GetConnectionPointContainer(out IConnectionPointContainer ppCPC);

        [PreserveSig]
        int Advise([MarshalAs(UnmanagedType.IUnknown)] object pUnkSink, out int pdwCookie);

        [PreserveSig]
        int Unadvise(int dwCookie);

        [PreserveSig]
        int EnumConnections(out /*IEnumConnections**/ IntPtr ppEnum);
    }

    [ComImport, Guid("b196b284-bab4-101a-b69c-00aa00341d07"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IConnectionPointContainer
    {
        [PreserveSig]
        int EnumConnectionPoints(out /*IEnumConnectionPoints*/ IntPtr ppEnum);


        [PreserveSig]
        int FindConnectionPoint([MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IConnectionPoint ppCP);
    }
}

public class DispatcherEventArgs : EventArgs
{
    public DispatcherEventArgs(int dispId, params object[] arguments)
    {
        DispId = dispId;
        Arguments = arguments ?? Array.Empty<object>();
    }

    public int DispId { get; }
    public object[] Arguments { get; }
    public object Result { get; set; }
}

Csharp相关问答推荐

C#中的两个列表中的元素加/减/乘/除操作

将.NET 8程序集加载到Matlab R2023a中时出现问题

使用ElasticsearchClient设置忽略属性.默认MappingFor<>

将委托传递到serviceccollection c#web API

并行令牌更新

如何修改中间件或其注册以正确使用作用域服务?

实体框架核心上是否支持使用NPGSQL的字符串聚合?

在LINQ Where子句中使用新的DateTime

应用程序启动器,可 Select 物理屏幕

MongoDB.NET-将数据绑定到模型类,但无法读取整数值

System.Net.Http.HttpClient.SendAsync(request)在docker容器内的POST方法30秒后停止

ASP.NET核心MVC SqlException:违反主键约束';PK_USER';.无法在对象';数据库中插入重复的密钥.用户';

有空容错运算符的对立面吗?

如何将端点(或с匹配请求并判断其路径)添加到BCL?

是否由DI容器自动处理由ActivatorUilties.CreateInstance()创建的服务?

实体框架-IsRequired()与OnDelete()

将C#类导入到PowerShell

未显示详细信息的弹出对话框

Xamarin.Forms-如何创建可 Select 的显示alert 或弹出窗口?

WPF如何获取有关从一个视图模型更改另一个视图模型的信息