Подтвердить что ты не робот

Выявление событий .NET для COM?

Я пытаюсь открыть и запустить событие клиенту VBA. Пока что на стороне клиента VBA это событие открыто, и я вижу, что метод обработки событий метода добавлен в мой класс модулей, однако метод обработки событий VBA не срабатывает. По какой-то причине, когда отладка события равна нулю. Модификация моего кода синхронно тоже не помогла.

Для записи я проверил другие вопросы 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 рецензий.

4b9b3361

Ответ 1

Ключевым понятием в .net-коде является определение событий (ов) как метода (ов) на отдельном интерфейсе и подключение его к классу через [ComSourceInterfacesAttribute]. В примере это делается с помощью этого кода [ComSourceInterfaces(typeof(IEvents))], где IEvents интерфейс определяет событие (ы), которое должно обрабатываться на COM-клиенте.

Примечание к именованию событий: имена событий, определенные в классах класса С#, и имена методов интерфейса, определенные на интерфейсе, должны быть одинаковыми. В этом примере IEvents::OnDownloadCompleted соответствует DemoEvents::OnDownloadCompleted.

Затем определяется второй интерфейс, который представляет публичный API самого класса, здесь он называется IDemoEvents. На этом интерфейсе определены методы, которые вызывают на COM-клиенте.

Код С# (создается на 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 ...
            }
        }
    }
}

Примечание к загрузке файла:  Для загрузки файла используется метод 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

Добавьте ссылку на *tlb, которая была сгенерирована regasm. Здесь имя этого файла tlb COMVisibleEvents.

введите описание изображения здесь

Здесь пользовательская форма Excel использовалась как клиент VBA. После нажатия кнопки был выполнен метод DownloadFileAsync, и когда этот метод завершен, событие было уловлено в обработчике m_eventSource_OnDownloadCompleted. В этом примере вы можете загрузить исходные коды проекта С# COMVisibleEvents.dll из моего dropbox.

Клиентский код 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

Результат

введите описание изображения здесь

Ответ 2

Тот факт, что ваше событие null может указывать на то, что вы объявили объект класса на стороне клиента VBA без атрибута WithEvents. Чтобы получать события, вам нужно указать экземпляр класса следующим образом:

Private WithEvents obj As MyAssembly.MyClass