将.NET事件暴露给COM? [英] Exposing .NET events to 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屋!