Exposing .NET events to COM?

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

Register for COM interop

Use this setting during development process on your local computer.
https://stackoverflow.com/a/3700057/863240

VBA client reference to *.tlb file

Add reference to *tlb which was generated by regasm. Here the name of this tlb file is COMVisibleEvents.

enter image description here

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 Airport Codes from datahub.io.

VBA client code (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

enter image description here

Source code is on GitHub too:

https://github.com/ddmail/COMVisibleEvents.git

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)