我们有一个旧的32位Visual Studio C# Windows Forms解决方案,我们希望从现在开始以64位编译它。不幸的是,我们的应用程序使用了一些外部dll-s(用于扫描仪,相机等),这些dll-s仅在32位版本中可用。 从 64 位代码访问 32 位 DLL 并不简单,尤其是当我们想要处理这些 dll-s 引发的事件时。我们在这方面的知识不足以创建基于本文的实现,因此我们正在寻找更详细的说明或示例。
我们的第一次尝试是基于这篇文章。我们将第三方 dll-s 包装到后期绑定的 32 位 COM 服务器中,并从我们的 64 位应用程序中使用它,如此处所述(比照,因为我们必须交换 32 位和 64 位的角色)。此尝试成功,但不完整,因为此解决方案不会将事件从 COM 服务器传递到 64 位客户端。所以我们开始关注这些事件。我们发现了很多处理 COM 对象引发的使用事件的文章和示例,但没有一个为我们提供完整的解决方案。一部分源代码专门处理客户端或服务器,但它们彼此不兼容,也不兼容我们的环境(WinForm,c#)。
例如
- 这个答案告诉我们如何制作一个向 VBA 客户端公开 .NET 事件的 COM 服务器,但我不知道如何从 C# 客户端使用它。
- 相比之下,本文为现有的COM服务器提供了一个很好的C#客户端,但没有说明如何制作这样的COM服务器(这个COM服务器与前面的例子明显不同)
- 此答案没有说明解决方案的任何细节。
- 本文适用于 c++,而不是 c#。
- 这个答案指的是这篇文章,但后者再次使用 VB 客户端而不是 c#。
- 本文以无法追踪的方式混合了不同的东西。
也许其中一些可以被我们努力使用,但是哪些以及如何使用?
编辑
现在我倾向于创建一个混合解决方案:对于从 32 位 COM 对象到调用方 64 位应用程序的向后通信,我的最新想法是将命名管道服务器放入 64 位应用程序,将命名管道客户端放入 COM 对象中,并且每次在 COM 对象中引发事件时, 它将命名管道消息发送到命名管道服务器。我找到的代码可以在这里找到(项目CSNamedPipeServer和CSNamedPipeClient)。
下面是一个 64 位服务器的示例,该服务器在类库项目中作为 C# 类实现,由 Windows 的系统代理项托管:dllhost。
这是类代码(你可以编译为"任何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.NETFramework64v4.0.30319regasm.exe NetComClassLibrary3.dll /codebase /tlb
这是一个 .reg,以确保它将在 dllhost 中进程外运行.exe(guid 是 COM coclass MyClass 的 guid):
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOTAppID{31dd1263-0001-4071-aa4a-d226a55116bd}]
"DllSurrogate"=""
[HKEY_CLASSES_ROOTCLSID{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
当我们在 Simon Mourier 的解决方案中交换 32 位和 64 位的角色时,那么 - 除了改变编译位之外 - 我们应该改变 4 件事。
(1) 变更注册
从
%windir%Microsoft.NETFramework64v4.0.30319regasm.exe NetComClassLibrary3.dll /codebase /tlb
自
%windir%Microsoft.NETFrameworkv4.0.30319regasm.exe NetComClassLibrary3.dll /codebase /tlb
(2) 更改注册表项
从
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOTAppID{31dd1263-0001-4071-aa4a-d226a55116bd}]
"DllSurrogate"=""
[HKEY_CLASSES_ROOTCLSID{31dd1263-0001-4071-aa4a-d226a55116bd}]
"AppID"="{31dd1263-0001-4071-aa4a-d226a55116bd}"
自
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINESOFTWAREWow6432NodeClassesAppID{31dd1263-0001-4071-aa4a-d226a55116bd}]
"DllSurrogate"=""
[HKEY_LOCAL_MACHINESOFTWAREWow6432NodeClassesCLSID{31dd1263-0001-4071-aa4a-d226a55116bd}]
"AppID"="{31dd1263-0001-4071-aa4a-d226a55116bd}"
(3)在64位客户端中,不要注册32位NetComClassLibrary3.dll,而是将IMyClass
和OnMyEventDelegate
的定义复制到客户端的源代码中
(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
备注将IMyClass
和OnMyEventDelegate
的定义添加到客户端的源代码中,而不是注册 32 位 NetComClassLibrary3.dll也适用于 32 位客户端 + 64 位 COM 服务器版本,但在 64 位客户端中引用 32 位 COM dll 会导致 BadImageFormat 异常。