向 COM 公开 .NET 事件? [英] Exposing .NET events to COM?

查看:16
本文介绍了向 COM 公开 .NET 事件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

[ComVisible(true)][指南(56C41646-10CB-4188-979D-23F70E0FFDF5")][ClassInterface(ClassInterfaceType.None)][ComSourceInterfaces(typeof(IWebEvents))][ProgId("MyAssembly.MyClass")]公共类 MyClass : ServicedComponent, IMyClass{公共字符串 _address { 获取;私人套装;}公共字符串 _filename { 获取;私人套装;}[DispId(4)]public void DownloadFileAsync(字符串地址,字符串文件名){_address = 地址;_filename = 文件名;System.Net.WebClient wc = new System.Net.WebClient();Task.Factory.StartNew(() => wc.DownloadFile(_address, _filename)).ContinueWith((t) =>{if (null != this.OnDownloadCompleted)OnDownloadCompleted();});}公共事件 OnDownloadCompletedEventHandler OnDownloadCompleted;}[ComVisible(假)]公共委托无效 OnDownloadCompletedEventHandler();[ComVisible(真)][接口类型(ComInterfaceType.InterfaceIsIDispatch)]公共接口 IWebEvents{[DispId(1)]无效 OnDownloadCompleted();}

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

解决方案

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

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

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

<块引用>

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

使用系统;使用 System.EnterpriseServices;使用 System.IO;使用 System.Net;使用 System.Runtime.InteropServices;使用 System.Threading.Tasks;命名空间 COMVisibleEvents{[ComVisible(真)][指南(8403C952-E751-4DE1-BD91-F35DEE19206E")][接口类型(ComInterfaceType.InterfaceIsIDispatch)]公共接口 IEvents{[DispId(1)]无效 OnDownloadCompleted();}[ComVisible(真)][指南(2BF7DA6B-DDB3-42A5-BD65-92EE93ABB473")][接口类型(ComInterfaceType.InterfaceIsIDispatch)]公共接口 IDemoEvents{[DispId(1)]任务DownloadFileAsync(字符串地址,字符串文件名);}[ComVisible(真)][指南(56C41646-10CB-4188-979D-23F70E0FFDF5")][ClassInterface(ClassInterfaceType.None)][ComSourceInterfaces(typeof(IEvents))][ProgId("COMVisibleEvents.DemoEvents")]公共类 DemoEvents: 服务组件,IDemoEvents{公共委托无效 OnDownloadCompletedDelegate();私人事件 OnDownloadCompletedDelegate OnDownloadCompleted;公共字符串 _address { 获取;私人套装;}公共字符串 _filename { 获取;私人套装;}私有只读字符串 _downloadToDirectory =Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);公共异步任务 DownloadFileAsync(字符串地址,字符串文件名){尝试{使用 (WebClient webClient = new WebClient()){webClient.Credentials = 新的 NetworkCredential(用户"、psw"、域");字符串文件 = Path.Combine(_downloadToDirectory, 文件名);等待 webClient.DownloadFileTaskAsync(new Uri(address), file).ContinueWith(t =>{//https://stackoverflow.com/q/872323/var ev = OnDownloadCompleted;如果 (ev != null){ev();}}, TaskScheduler.FromCurrentSynchronizationContext());}}捕捉(例外前){//在此处记录异常...}}}}

<块引用>

再高潮

C:WindowsMicrosoft.NETFrameworkv4.0.30319>regasm C:TempCOMVisibleEventsinDebugCOMVisibleEvents.dll/tlb:C:TempCOMVisibleEventsinDebugCOMVisibleEvents.tlb

<块引用>

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

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

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

<块引用>

VBA 客户端代码 (MS Excel 2007)

选项显式Private WithEvents m_eventSource 作为 DemoEventsPrivate Sub DownloadFileAsyncButton_Click()m_eventSource.DownloadFileAsynchttps://www.dropbox.com/s/0q3dskxopelymac/COMVisibleEvents.zip?dl=0"、COMVisibleEvents.zip"结束子私有子 m_eventSource_OnDownloadCompleted()MsgBox "下载完成..."结束子私有子 UserForm_Initialize()设置 m_eventSource = New COMVisibleEvents.DemoEvents结束子

<块引用>

结果

I've been trying to expose and fire an event to a VBA client. So far on the VBA client side, the event is exposed and I see the method event handling method added to my module class however the VBA event handling method does not fire. For some reason, when debugging the event is null. Modifying my code with synchronously did not help either.

For the record, I've checked other SO questions but they didn't help.

Any good answers will be appreciated.

[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();
}

This is a good gig for you all bounty hunters, 200 rep points

解决方案

The key concept in .NET code is to define event(s) as method(s) on a separate interface and connect it to the class via [ComSourceInterfacesAttribute]. In the example this is done with this code [ComSourceInterfaces(typeof(IEvents))] where IEvents interface defines the event(s) which should be handled on COM client.

Note to event naming:
Event names defined in c# class and interface method names defined on interface must be the same. In this example IEvents::OnDownloadCompleted corresponds with DemoEvents::OnDownloadCompleted
.

Then a second interface is defined which represents the public API of the class itself, here it is called IDemoEvents. On this interface methods are defined which are called on COM client.

C# code (builds to 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 ...
            }
        }
    }
}

regasm

C:WindowsMicrosoft.NETFrameworkv4.0.30319>regasm C:TempCOMVisibleEventsinDebugCOMVisibleEvents.dll /tlb: C:TempCOMVisibleEventsinDebugCOMVisibleEvents.tlb

VBA client reference to *.tlb file

Add reference to *tlb which was generated by regasm. Here the name of this tlb file is COMVisibleEvents.

Here Excel User Form was used as VBA client. After the button was clicked, the method DownloadFileAsync was executed and when this method completes the event was caught in handler m_eventSource_OnDownloadCompleted. In this example you can download the source codes of the C# project COMVisibleEvents.dll from my dropbox.

VBA client code (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

Result

这篇关于向 COM 公开 .NET 事件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆