.NET 代码中的关键概念是将事件定义为单独接口上的方法,并通过以下方式将其连接到类[ComSourceInterfacesAttribute]
。在示例中,这是使用此代码完成的[ComSourceInterfaces(typeof(IEvents))]
where IEvents
接口定义应在 COM 客户端上处理的事件。
事件命名注意事项:
C#类中定义的事件名称和接口上定义的接口方法名称必须相同。在这个例子中IEvents::OnDownloadCompleted
对应于DemoEvents::OnDownloadCompleted
.
然后定义了第二个接口,它代表类本身的公共API,这里称为IDemoEvents
。在此接口上定义了在 COM 客户端上调用的方法。
C# 代码(构建为 COMVisibleEvents.dll)
using System;
using System.Diagnostics;
using System.EnterpriseServices;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace COMVisibleEvents
{
[ComVisible(true)]
[Guid("8403C952-E751-4DE1-BD91-F35DEE19206E")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IEvents
{
[DispId(1)]
void OnDownloadCompleted();
[DispId(2)]
void OnDownloadFailed(string message);
}
[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();
public delegate void OnDownloadFailedDelegate(string message);
public event OnDownloadCompletedDelegate OnDownloadCompleted;
public event OnDownloadFailedDelegate OnDownloadFailed;
private string FileNamePath(string filename)
=> Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), filename);
public async Task DownloadFileAsync(string address, string filename)
{
try
{
using (var webClient = new WebClient())
{
await webClient
.DownloadFileTaskAsync(new Uri(address), FileNamePath(filename))
.ContinueWith(t =>
{
if (t.Status == TaskStatus.Faulted)
{
var failed = OnDownloadFailed;
failed?.Invoke(GetExceptions(t));
}
else
{
var completed = OnDownloadCompleted;
completed?.Invoke();
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
#region Local
string GetExceptions(Task task)
{
var innerExceptions = task.Exception?.Flatten().InnerExceptions;
if (innerExceptions == null)
return string.Empty;
var builder = new StringBuilder();
foreach (var e in innerExceptions)
builder.AppendLine(e.Message);
return builder.ToString();
}
#endregion Local
}
}
}
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
注册 COM 互操作
在本地计算机上的开发过程中使用此设置。https://stackoverflow.com/a/3700057/863240 https://stackoverflow.com/a/3700057/863240
VBA 客户端参考*.tlb
file
添加参考*tlb
这是由生成的regasm
。这里是这个的名字tlb
文件是COMVisibleEvents
.
这里使用 Excel 用户表单作为 VBA 客户端。单击按钮后,该方法DownloadFileAsync
被执行,当该方法完成时,事件被处理程序捕获m_eventSource_OnDownloadCompleted
。在此示例中,您可以从 datahub.io 下载机场代码。
VBA 客户端代码 (MS Excel 2016)
Option Explicit
Private WithEvents m_eventSource As DemoEvents
Private Sub DownloadFileAsyncButton_Click()
m_eventSource.DownloadFileAsync "https://datahub.io/core/airport-codes/r/airport-codes.json", "airport-codes.json"
End Sub
Private Sub m_eventSource_OnDownloadCompleted()
MsgBox "Download completed..."
End Sub
Private Sub m_eventSource_OnDownloadFailed(ByVal message As String)
MsgBox "Download failed. " & message, vbCritical, "Error"
End Sub
Private Sub UserForm_Initialize()
Set m_eventSource = New COMVisibleEvents.DemoEvents
End Sub
Result
源代码也在 GitHub 上:
https://github.com/ddmail/COMVisibleEvents.git https://github.com/ddmail/COMVisibleEvents.git