将.NET事件暴露给COM? [英] Exposing .NET events to COM?

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

问题描述

我一直试图向VBA客户端公开并触发事件。到目前为止在VBA客户端,事件暴露了,我看到方法事件处理方法添加到我的模块类,但VBA事件处理方法不会触发。由于某种原因,调试时事件为null。同步修改我的代码也没有帮助。



对于记录,我已检查其他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;私人集}
public string _filename {get;私人集}

[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 = 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代表

$ p

解决方案

.net代码中的关键概念是将事件定义为方法一个单独的接口,并通过 [ComSourceInterfacesAttribute] 将它连接到类。在示例中,使用此代码 [ComSourceInterfaces(typeof(IEvents))] 其中 IEvents (s)应该在COM客户端上处理。



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

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


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




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

命名空间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)]
任务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;私人集}
public string _filename {get;私人集}
private readonly string _downloadToDirectory =
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

public async任务下载FileSync(字符串地址,字符串文件名)
{
try
{
using(WebClient webClient = new WebClient $ b {
webClient.Credentials = new NetworkCredential(
user,psw,domain);
string file = Path.Combine(_downloadToDirectory,filename);
await webClient.DownloadFileTaskAsync(new Uri(address),file)
.ContinueWith(t =>
{
if(OnDownloadCompleted!= null)
{
OnDownloadCompleted();
}
},TaskScheduler.FromCurrentSynchronizationContext());
}
}
catch(例外)
{
//此处记录异常...
}
}
}
}

注意文件下载:
要下载文件使用方法 WebClient.DownloadFileTaskAsync 。它使用任务对象作为异步操作将指定的资源下载到本地文件。
任务对象通常在线程池线程上异步执行,而不是在主应用程序线程上同步执行。因此,必须在主线程上调用 ContinueWith ,否则将无法执行 OnDownloadCompleted 事件。这是为什么使用 ContinueWith(continuationAction,TaskScheduler.FromCurrentSynchronizationContext)




regasm




  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 file


添加 * tlb 的引用, code> regasm 。此 tlb 文件的名称为 COMVisibleEvents





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


VBA客户端代码




 选项显式

私有WithEvents m_eventSource As DemoEvents

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

Private Sub m_eventSource_OnDownloadCompleted()
MsgBox下载完成...
End Sub

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




结果



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 =>
                        {
                            if (OnDownloadCompleted != null)
                            {
                                OnDownloadCompleted();
                            }
                        }, TaskScheduler.FromCurrentSynchronizationContext());
                }
            }
            catch (Exception ex)
            {
                // Log exception here ...
            }
        }
    }
}

Note to file download: To download the file the method WebClient.DownloadFileTaskAsync is used. It downloads the specified resource to a local file as an asynchronous operation using a task object. Task object typically executes asynchronously on a thread pool thread rather than synchronously on the main application thread. Therefore it is necessary to call ContinueWith on main thread because otherwise it won't be possible to execute the OnDownloadCompleted event. Thats why ContinueWith(continuationAction, TaskScheduler.FromCurrentSynchronizationContext) is used.

regasm

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 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

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

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