dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.16k stars 4.71k forks source link

COM automation with dynamic keyword - creating delegates with reflection not working #46742

Open smezger opened 3 years ago

smezger commented 3 years ago

Description

I'm using the DLR to work with COM objects with .NET Framework and used the partial workaround with NET Core 3 described in #12587 (without events). Now with NET5 and as the PR #33060 is included there, I've tried to use the original code from NET Framework however I'm struggling with the code to add event handlers through reflected delegates. The code I'm using successful with .NET Framework and doesn't work with NET5 looks like this:

    private void Run()
    {
      Type excelType = Type.GetTypeFromProgID("Excel.Application");
      dynamic excel = Activator.CreateInstance(excelType);
      AddEventHandler(excel, "AppEvents_Event_NewWorkbook", new Action<dynamic>(OnNewWorkbook), this);
      excel.Workbooks.Add();
    }

    private void OnNewWorkbook(dynamic workbook)
    {
      Console.WriteLine("new workbook!");
    }

    static void AddEventHandler(dynamic comObject, string eventName, Delegate method, object firstArgument)
    {
      if (comObject == null)
        return;
      EventInfo eInfo = comObject.GetType().GetEvent(eventName);
      MethodInfo evHandler = method.GetMethodInfo();
      Delegate del = Delegate.CreateDelegate(eInfo.EventHandlerType, firstArgument, evHandler);
      eInfo.AddEventHandler(comObject, del);
    }

In NET5 the call comObject.GetType().GetEvent(eventName) returns null.

What would be the right way to handle that with NET5? Am I missing something obviously or is this a known limitation?

@elinor-fung: As you have added the support with the PR, is that something you are aware of? Thanks!

Configuration

.NET5 (5.0.101), x64

Regression?

works in .NET Framework

elinor-fung commented 3 years ago

I think this is COM and reflection in .NET 5 in general - not specifically related to dynamic. For:

Type excelType = Type.GetTypeFromProgID("Excel.Application");

.NET 5: System.__ComObject. .NET Framework: Microsoft.Office.Interop.Excel.ApplicationClass

In .NET Framework, the runtime would try to look up and load the assembly and type corresponding to the registered class - and the resulting type has those events. In .NET 5, that doesn't happen (https://github.com/dotnet/runtime/blob/v5.0.1/src/coreclr/src/vm/interoputil.cpp#L1894), so the type is just __ComObject and doesn't have those events.

cc @AaronRobinsonMSFT

AaronRobinsonMSFT commented 3 years ago

@smezger @elinor-fung's description of the issue is correct. Falling back to query the ITypeInfo is no longer performed so the reflection mechanisms are effectively broken on dynamic COM objects. Can you try explicitly using Microsoft.Office.Interop.Excel.ApplicationClass instead of dynamic?

smezger commented 3 years ago

@AaronRobinsonMSFT ; Yes when using the COM object directly to create the initial instance it works i.e. Microsoft.Office.Interop.Excel.ApplicationClass excel = new Microsoft.Office.Interop.Excel.ApplicationClass(); However as a result we have to of course add a reference to the corresponding interop and have to distribute as well. Bottom line: The code changes seem not too big, but having a direct reference to the COM Interop is a huge step back compared to the original solution! Any chance to get back a more beautiful solution without the need of that reference?

@elinor-fung : Thanks for your analysis!