将.NET事件暴露给COM? [英] Exposing .NET events to COM?
问题描述
为了记录,我已经检查过其他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屋!