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

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

问题描述

我一直在尝试向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;私人集合}
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(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代表点

解决方案

.net代码中的关键概念是将事件定义为一个单独的界面,并通过 [ComSourceInterfacesAttribute] 将其连接到类。在这个例子中,这个代码是通过这个代码来实现的。 [ComSourceInterfaces(typeof(IEvents))] 其中 IEvents (s),应在COM客户端处理。



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



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


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




  using System; 
使用System.EnterpriseServices;
使用System.IO;
使用System.Net;
使用System.Runtime.InteropServices;
使用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();
私人事件OnDownloadCompletedDelegate OnDownloadCompleted;
public string _address {get;私人集合}
public string _filename {get;私人集合}
private readonly string _downloadToDirectory =
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

public async任务DownloadFileAsync(string address,string filename)
{
try
{
using(WebClient webClient = new WebClient())
{
webClient.Credentials = new NetworkCredential(
user,psw,domain);
string file = Path.Combine(_downloadToDirectory,filename);
等待webClient.DownloadFileTaskAsync(new Uri(address),file)
.ContinueWith(t =>
{
if(OnDownloadCompleted!= null)
{
OnDownloadCompleted();
}
},TaskScheduler.FromCurrentSynchronizationContext());
}
}
catch(Exception ex)
{
//日志异常在这里...
}
}

}

文件下载注意事项:
要下载使用方法 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客户端代码(MS Excel 2007)




  Option Explicit 

私有WithEvents m_eventSource作为DemoEvents

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

私有子m_eventSource_OnDownloadCompleted()
MsgBox下载完成...
End Sub

Private Sub UserForm_Initialize()
设置m_eventSource =新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天全站免登陆