2016-09-15 2 views
19

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

Для справки, я проверил другие вопросы, но они не помогли.

Любые хорошие ответы будут оценены.

[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 респ точек

+0

Попробуйте объявить MyClass родителя = это до задачи. В вашей задаче используйте parent.OnDownloadCompleted. – Jules

+0

@jules Какова логика этого? –

+0

«this» в вашем коде относится к Task not MyClass. – Jules

ответ

12

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

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

Затем определяется второй интерфейс, представляющий публичный API самого класса, здесь он называется 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 => 
         { 
          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.

enter image description here

Здесь Excel Форма пользователя была использована в качестве клиента VBA. После нажатия кнопки был выполнен метод DownloadFileAsync, и когда этот метод завершен, событие было уловлено в обработчике m_eventSource_OnDownloadCompleted. В этом примере вы можете загрузить исходные коды проекта C# 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 

Результат

enter image description here

+0

Это работает ** асинхронно **? * Не следует ли объявлять метод с aysnc и ждать? * –

+0

Куда пропадет бит загрузки? –

+0

@ Хорошо, я сосредоточился на части C# -event-COM. См. Отредактированный код. Я добавил асинхронную загрузку. В этом примере вы можете загрузить полный исходный код проекта C# из моего Dropbox :). HTH – dee

-1

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

Private WithEvents obj As MyAssembly.MyClass 
+0

@downvoter Отлаживайте код и смотрите, что произойдет, когда вы опустите 'WithEvents'. Это согласуется с описанным поведением. – Aurora

+0

Я уже пробовал WithEvents, прежде чем публиковать этот вопрос. Это не проблема. –