Exposing .NET events to COM?

后端 未结 1 514
别跟我提以往
别跟我提以往 2020-12-01 06:38

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 mod

相关标签:
1条回答
  • 2020-12-01 06:47

    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 =>
                            {
                                // https://stackoverflow.com/q/872323/
                                var ev = OnDownloadCompleted;
                                if (ev != null)
                                {
                                    ev();
                                }
                            }, 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. That's 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

    0 讨论(0)
提交回复
热议问题