web-dev-qa-db-ja.com

.NETイベントを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; 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();
}

これはあなたのすべてのバウンティハンター、200 repポイントにとって良いギグです

28
Amen Jlili

.NETコードの重要な概念は、イベントを別のインターフェイスのメソッドとして定義し、それを_[ComSourceInterfacesAttribute]_を介してクラスに接続することです。例では、これはこのコード[ComSourceInterfaces(typeof(IEvents))]で行われます。ここで、IEventsインターフェイスは、COMクライアントで処理する必要があるイベントを定義します。

イベントの命名に関する注意:
c#クラスで定義されたイベント名とインターフェイスで定義されたインターフェイスメソッド名は同じである必要があります。この例では、_IEvents::OnDownloadCompleted_は_DemoEvents::OnDownloadCompleted_
に対応しています。

次に、クラス自体のパブリックAPIを表す2番目のインターフェースが定義されます。ここでは、IDemoEventsと呼ばれます。このインターフェイスでは、COMクライアントで呼び出されるメソッドが定義されています。

C#コード(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 ...
            }
        }
    }
}
_

ファイルのダウンロードに関する注意:
ファイルをダウンロードするには、メソッド_WebClient.DownloadFileTaskAsync_を使用します。指定したリソースを、タスクオブジェクトを使用した非同期操作としてローカルファイルにダウンロードします。
タスクオブジェクトは通常、メインアプリケーションスレッドで同期的にではなく、スレッドプールスレッドで非同期的に実行されます。したがって、メインスレッドでContinueWithを呼び出す必要があります。そうしないと、OnDownloadCompletedイベントを実行できません。そのため、ContinueWith(continuationAction, TaskScheduler.FromCurrentSynchronizationContext)が使用されます。

レガスム

_C:\Windows\Microsoft.NET\Framework\v4.0.30319>regasm C:\Temp\COMVisibleEvents\bin\Debug\COMVisibleEvents.dll /tlb: C:\Temp\COMVisibleEvents\bin\Debug\COMVisibleEvents.tlb
_

_*.tlb_ファイルへのVBAクライアント参照

regasmによって生成された_*tlb_への参照を追加します。ここで、このtlbファイルの名前はCOMVisibleEventsです。

enter image description here

ここでは、ExcelユーザーフォームがVBAクライアントとして使用されました。ボタンがクリックされた後、メソッドDownloadFileAsyncが実行され、このメソッドが完了すると、イベントがハンドラー_m_eventSource_OnDownloadCompleted_でキャッチされました。この例では、C#プロジェクトCOMVisibleEvents.dllのソースコードをドロップボックスからダウンロードできます。

VBAクライアントコード(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
_

結果

enter image description here

18
dee