【问题标题】:Exposing .NET events to COM?向 COM 公开 .NET 事件?
【发布时间】:2017-01-23 12:06:41
【问题描述】:

我一直在尝试向 VBA 客户端公开和触发事件。到目前为止,在 VBA 客户端,事件已公开,我看到方法事件处理方法已添加到我的模块类中,但是 VBA 事件处理方法不会触发。出于某种原因,调试事件时为空。同步修改我的代码也无济于事。

作为记录,我检查了其他 SO 问题,但没有帮助。

任何好的答案将不胜感激。

[ComVisible(true)]
[Guid("56C41646-10CB-4188-979D-23F70E0FFDF5")]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IWebEvents))]
[ProgId("MyAssembly.MyClass")]
public class MyClass : ServicedComponent, IMyClass
{
    public string _address { get; private set; }
    public string _filename { get; private set; }

    [DispId(4)]
    public void DownloadFileAsync(string address, string filename)
    {
        _address = address;
        _filename = filename;
        System.Net.WebClient wc = new System.Net.WebClient();
        Task.Factory.StartNew(() => wc.DownloadFile(_address, _filename))
            .ContinueWith((t) =>
        {
            if (null != this.OnDownloadCompleted)
                OnDownloadCompleted();
        });
    }
    public event OnDownloadCompletedEventHandler OnDownloadCompleted;
}

[ComVisible(false)]
public delegate void OnDownloadCompletedEventHandler();

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IWebEvents
{
    [DispId(1)]
    void OnDownloadCompleted();
}

这对你们所有的赏金猎人来说都是一个很好的演出,200 声望值

【问题讨论】:

  • 尝试在Task之前声明MyClass parent = this。在您的任务中使用 parent.OnDownloadCompleted。

标签: c# vba events com


【解决方案1】:

.NET 代码中的关键概念是将事件定义为单独接口上的方法,并通过[ComSourceInterfacesAttribute] 将其连接到类。在示例中,这是通过代码[ComSourceInterfaces(typeof(IEvents))] 完成的,其中IEvents 接口定义了应该在COM 客户端上处理的事件。

事件命名注意事项:
c# 类中定义的事件名称和接口上定义的接口方法名称必须相同。在此示例中,IEvents::OnDownloadCompleted 对应于 DemoEvents::OnDownloadCompleted

然后定义第二个接口,它代表类本身的公共 API,这里称为IDemoEvents。在此接口上定义了在 COM 客户端上调用的方法。

C# 代码(构建到 COMVisibleEvents.dll)

using System;
using System.EnterpriseServices;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace COMVisibleEvents
{
    [ComVisible(true)]
    [Guid("8403C952-E751-4DE1-BD91-F35DEE19206E")]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IEvents
    {
        [DispId(1)]
        void OnDownloadCompleted();
    }

    [ComVisible(true)]
    [Guid("2BF7DA6B-DDB3-42A5-BD65-92EE93ABB473")]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IDemoEvents
    {
        [DispId(1)]
        Task DownloadFileAsync(string address, string filename);
    }

    [ComVisible(true)]
    [Guid("56C41646-10CB-4188-979D-23F70E0FFDF5")]
    [ClassInterface(ClassInterfaceType.None)]
    [ComSourceInterfaces(typeof(IEvents))]
    [ProgId("COMVisibleEvents.DemoEvents")]
    public class DemoEvents
        : ServicedComponent, IDemoEvents
    {
        public delegate void OnDownloadCompletedDelegate();
        private event OnDownloadCompletedDelegate OnDownloadCompleted;
        public string _address { get; private set; }
        public string _filename { get; private set; }
        private readonly string _downloadToDirectory = 
            Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

        public async Task DownloadFileAsync(string address, string filename)
        {
            try
            {
                using (WebClient webClient = new WebClient())
                {
                    webClient.Credentials = new NetworkCredential(
                        "user", "psw", "domain");
                    string file = Path.Combine(_downloadToDirectory, filename);
                    await webClient.DownloadFileTaskAsync(new Uri(address), file)
                        .ContinueWith(t =>
                        {
                            // https://stackoverflow.com/q/872323/
                            var ev = OnDownloadCompleted;
                            if (ev != null)
                            {
                                ev();
                            }
                        }, TaskScheduler.FromCurrentSynchronizationContext());
                }
            }
            catch (Exception ex)
            {
                // Log exception here ...
            }
        }
    }
}

再高潮

C:\Windows\Microsoft.NET\Framework\v4.0.30319>regasm C:\Temp\COMVisibleEvents\bin\Debug\COMVisibleEvents.dll /tlb: C:\Temp\COMVisibleEvents\bin\Debug\COMVisibleEvents.tlb

VBA 客户端对*.tlb 文件的引用

添加对由regasm 生成的*tlb 的引用。这里tlb 文件的名称是COMVisibleEvents

这里使用 Excel 用户表单作为 VBA 客户端。单击按钮后,方法DownloadFileAsync 被执行,当此方法完成时,事件被处理程序m_eventSource_OnDownloadCompleted 捕获。在此示例中,您可以从我的 Dropbox 下载 C# 项目 COMVisibleEvents.dll 的源代码。

VBA 客户端代码 (MS Excel 2007)

Option Explicit

Private WithEvents m_eventSource As DemoEvents

Private Sub DownloadFileAsyncButton_Click()
    m_eventSource.DownloadFileAsync "https://www.dropbox.com/s/0q3dskxopelymac/COMVisibleEvents.zip?dl=0", "COMVisibleEvents.zip"
End Sub

Private Sub m_eventSource_OnDownloadCompleted()
    MsgBox "Download completed..."
End Sub

Private Sub UserForm_Initialize()
    Set m_eventSource = New COMVisibleEvents.DemoEvents
End Sub

结果

【讨论】:

  • 为什么DemoEvents OnDownloadCompleted 事件在这里是私有的?
  • m_eventSource_OnDownloadCompleted() 是表单处理该事件的事件处理程序,因此此行为属于表单本身的类并且通常是私有的。
  • 啊,我明白了,代码只是一个演示,事件可以/应该是公开的。
  • 我想知道如果以这种方式声明该事件是否可以从 VBA 可访问。无论如何,COM 事件的声明总是困扰着我。
  • 我想在 vba 中这不会发生,您可以通过界面访问事件。
猜你喜欢
  • 2010-12-31
  • 2012-07-27
  • 2012-09-20
  • 2010-09-09
  • 2014-12-20
  • 1970-01-01
  • 1970-01-01
  • 2011-02-15
  • 2011-01-31
相关资源
最近更新 更多