【问题标题】:Event handling at calling a third party 32-bit dll from a 64-bit c# WinForms application从 64 位 c# WinForms 应用程序调用第三方 32 位 dll 时的事件处理
【发布时间】:2019-04-10 20:57:06
【问题描述】:

我们有一个旧的 32 位 Visual Studio C# Windows Forms 解决方案,我们希望从现在开始以 64 位编译它。不幸的是,我们的应用程序使用了一些仅在 32 位版本中可用的外部 dll-s(用于扫描仪、相机等)。 Accessing 32-bit DLLs from 64-bit code 并不简单,尤其是当我们还想处理那些 dll-s 引发的事件时。我们在这方面的知识不足以创建基于本文的实现,因此我们正在寻找更详细的说明或示例。

我们的第一次尝试是基于this article。我们将第三方 dll-s 包装到一个后期绑定的 32 位 COM 服务器中,并从我们的 64 位应用程序中使用它,如此处所述(经过必要修改,因为我们必须交换 32 位和 64 位的角色) .此尝试成功,但未完成,因为此解决方案不会将事件从 COM 服务器传递到 64 位客户端。所以我们开始关注事件。我们发现了很多文章和例子处理由 COM 对象引发的消费事件,但没有一个为我们提供完整的解决方案。一部分源代码专门处理客户端,或者专门处理服务器,但它们彼此不兼容,或者与我们的环境(WinForm、c#)不兼容。

例如,

  1. this answer 告诉我们如何制作一个向 VBA 客户端公开 .NET 事件的 COM 服务器,但我不知道如何从 c# 客户端使用它。
  2. 相比之下,this article 为现有的 COM 服务器提供了一个运行良好的 C# 客户端,但没有说明如何制作这样的 COM 服务器(这个 COM 服务器与前面的示例明显不同)
  3. this answer 没有透露解决方案的任何细节。
  4. This article 用于 c++ 而不是 c#。
  5. This answer 指的是 this article,但后者再次使用 VB 客户端而不是 c#。
  6. This article 以无法追踪的方式混合不同的东西。

也许我们可以通过一些努力来使用其中的一些,但是哪些以及如何使用?

编辑

现在我倾向于创建一个混合解决方案:对于从 32 位 COM 对象到调用者 64 位应用程序的反向通信,我的最新想法是将一个命名管道服务器放入 64 位应用程序和一个命名管道客户端进入 COM 对象,并且每次在 COM 对象中引发事件时,它都会向命名管道服务器发送一个命名管道消息。我为此找到的代码可用 here(项目 CSNamedPipeServer 和 CSNamedPipeClient)。

【问题讨论】:

  • 这个 DLL 是生成可视化组件,还是只是将事件生成回您的代码? 32 位 => 64 位是一堆蠕虫(正如您所发现的),因为它需要您跨越进程边界(32 位是 64 位窗口中的单独虚拟环境)。您是否尝试过 C# 中的 32 位包装器,与您的 64 位程序进行进程外对话?
  • 你不是在几天前问过这个问题吗(并删除了它,连同它的 cmets)?
  • @SimonMourier 差不多。问题是一样的,但现在问题不同了,而且(希望)更详细。
  • 如果需要,我可以提供一个 C# 示例,其中包含一个带有事件的 64 位 COM 服务器和一个可以工作的 32 位 COM 客户端。
  • @SimonMourier 酷!我期待您的解决方案。

标签: c# winforms event-handling com-interop


【解决方案1】:

这是一个带有 64 位服务器的示例,在类库项目中实现为 C# 类,由 Windows 的系统代理:dllhost 托管。

这是类代码(你可以编译成'any cpu',不需要编译成x64):

namespace NetComClassLibrary3
{
    // technically, we don't *have to* define an interface, we could do everything using dynamic stuff
    // but it's more practical so we can reference this .NET dll from our client
    [ComVisible(true)]
    [Guid("31dd1263-0002-4071-aa4a-d226a55116bd")]
    public interface IMyClass
    {
        event OnMyEventDelegate OnMyEvent;
        object MyMethod();
    }

    // same remark than above.
    // This *must* match the OnMyEvent signature below
    [ComVisible(true)]
    [Guid("31dd1263-0003-4071-aa4a-d226a55116bd")]
    public delegate void OnMyEventDelegate(string text);

    // this "event" interface is mandatory
    // note from the .NET perspective, no one seems to implement it
    // but it's referenced with the ComSourceInterfaces attribute on our COM server (below)
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    [Guid("31dd1263-0000-4071-aa4a-d226a55116bd")]
    public interface IMyEvents
    {
        // dispids are mandatory here otherwise you'll get a DISP_E_UNKNOWNNAME error
        [DispId(1)]
        void OnMyEvent(string text);
    }

    [ComVisible(true)]
    [ComSourceInterfaces(typeof(IMyEvents))]
    [Guid("31dd1263-0001-4071-aa4a-d226a55116bd")]
    public class MyClass : IMyClass
    {
        public event OnMyEventDelegate OnMyEvent;

        public object MyMethod()
        {
            // we use the current running process to test out stuff
            // this should be Windows' default surrogate: dllhost.exe
            var process = Process.GetCurrentProcess();
            var text = "MyMethod. Bitness: " + IntPtr.Size + " Pid: " + process.Id + " Name: " + process.ProcessName;
            Console.WriteLine(text); // should not be displayed when running under dllhost
            OnMyEvent?.Invoke("MyEvent. " + text);
            return text;
        }
    }
}

这是我注册它的方式(注意我的目标是 64 位注册表):

%windir%\Microsoft.NET\Framework64\v4.0.30319\regasm.exe NetComClassLibrary3.dll /codebase /tlb

这是一个 .reg,以确保它会在 dllhost.exe 中以进程外运行(guid 是 COM coclass MyClass' guid):

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\AppID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
"DllSurrogate"=""

[HKEY_CLASSES_ROOT\CLSID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
"AppID"="{31dd1263-0001-4071-aa4a-d226a55116bd}"

这是客户端,编译为 x86:

using System;
using NetComClassLibrary3; // we can reference the .net dll as is

namespace ConsoleApp10
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Bitness: " + IntPtr.Size);
            // note we don't use new MyClass() otherwise we may go inprocess
            var type = Type.GetTypeFromCLSID(typeof(MyClass).GUID);
            var obj = (IMyClass)Activator.CreateInstance(type);

            // note I'm using the beloved dynamic keyword here. for some reason obj.OnMyEvent works but locally raises a cast error I've not investigated further...
            dynamic d = obj;
            d.OnMyEvent += (OnMyEventDelegate)((t) =>
            {
                Console.WriteLine(t);
            });
            Console.WriteLine(obj.MyMethod());
        }
    }
}

当我运行它时,这是输出:

Bitness: 4 // running as 32-bit
MyEvent. MyMethod. Bitness: 8 Pid: 23780 Name: dllhost // from 64-bit world
MyMethod. Bitness: 8 Pid: 23780 Name: dllhost // from 64-bit world

【讨论】:

    【解决方案2】:

    当我们在 Simon Mourier 的解决方案中交换 32 位和 64 位的角色时,除了改变编译位数之外,我们还应该改变 4 件事。

    (1)更改注册

    来自

    %windir%\Microsoft.NET\Framework64\v4.0.30319\regasm.exe NetComClassLibrary3.dll /codebase /tlb
    

    %windir%\Microsoft.NET\Framework\v4.0.30319\regasm.exe NetComClassLibrary3.dll /codebase /tlb
    

    (2) 更改注册表项

    来自

    Windows Registry Editor Version 5.00
    
    [HKEY_CLASSES_ROOT\AppID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
    "DllSurrogate"=""
    
    [HKEY_CLASSES_ROOT\CLSID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
    "AppID"="{31dd1263-0001-4071-aa4a-d226a55116bd}"
    

    Windows Registry Editor Version 5.00
    
    [HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Classes\AppID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
    "DllSurrogate"=""
    
    [HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Classes\CLSID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
    "AppID"="{31dd1263-0001-4071-aa4a-d226a55116bd}"
    

    (3)在64位客户端,不注册32位NetComClassLibrary3.dll,将IMyClassOnMyEventDelegate的定义复制到客户端源码中

    (4) 同样在客户端,

    改变

    var type = Type.GetTypeFromCLSID(typeof(MyClass).GUID);
    

    var type = Type.GetTypeFromProgID("NetComClassLibrary3.MyClass");
    

    所以客户端看起来是这样的:

    using System;
    // removed by mma - using NetComClassLibrary3; // we can reference the .net dll as is
    
    namespace ConsoleApp10
    {
        // inserted by mma:
        [System.Runtime.InteropServices.Guid("31dd1263-0002-4071-aa4a-d226a55116bd")]
        public interface IMyClass
        {
            event OnMyEventDelegate OnMyEvent;
            object MyMethod();
        }
        [System.Runtime.InteropServices.Guid("31dd1263-0002-4071-aa4a-d226a55116bd")]
        public delegate void OnMyEventDelegate(string text);
        // end of insertion
    
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Bitness: " + IntPtr.Size);
                // note we don't use new MyClass() otherwise we may go inprocess
                // removed by mma var type = Type.GetTypeFromCLSID(typeof(MyClass).GUID);
                // inserted by mma:
                var type = Type.GetTypeFromProgID("NetComClassLibrary3.MyClass");
                // end of insertion
                var obj = (IMyClass)Activator.CreateInstance(type);
    
                // note I'm using the beloved dynamic keyword here. for some reason obj.OnMyEvent works but locally raises a cast error I've not investigated further...
                dynamic d = obj;
                d.OnMyEvent += (OnMyEventDelegate)((t) =>
                {
                    Console.WriteLine(t);
                });
                Console.WriteLine(obj.MyMethod());
            }
        }
    }
    

    所以输出会改变

    Bitness: 4 // running as 32-bit
    MyEvent. MyMethod. Bitness: 8 Pid: 23780 Name: dllhost // from 64-bit world
    MyMethod. Bitness: 8 Pid: 23780 Name: dllhost // from 64-bit world
    

    Bitness: 8 // running as 64-bit
    MyEvent. MyMethod. Bitness: 4 Pid: 56140 Name: dllhost // from 32-bit world
    MyMethod. Bitness: 4 Pid: 56140 Name: dllhost // from 32-bit world
    

    备注IMyClassOnMyEventDelegate的定义添加到客户端的源代码中,而不是注册32位NetComClassLibrary3.dll也适用于32位客户端+64位COM服务器版本,但引用32位COM 64 位客户端中的 dll 导致 BadImageFormat 异常。

    【讨论】:

      猜你喜欢
      • 2019-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-11
      • 1970-01-01
      • 1970-01-01
      • 2021-03-06
      • 1970-01-01
      相关资源
      最近更新 更多