从 64 位应用程序调用 32 位 DLL 的函数

Posted

技术标签:

【中文标题】从 64 位应用程序调用 32 位 DLL 的函数【英文标题】:Calling a function of a 32-bit DLL from a 64-bit Application 【发布时间】:2019-03-07 04:11:45 【问题描述】:

我有一个需要从 64 位 C# 应用程序访问的 32 位 dll(无源代码)。我已经阅读了this 的文章并查看了here 的相应代码。我也读过this 的帖子。 我不确定我问的问题是否正确,所以请帮助我。

有 3 个项目:dotnetclientx86Libraryx86x64x86x64x86LibraryProxy.cpp 加载 x86library.dll 并调用 GetTemperature 函数:

STDMETHODIMP Cx86LibraryProxy::GetTemperature(ULONG sensorId, FLOAT* temperature)

    *temperature = -1;
    typedef float (__cdecl *PGETTEMPERATURE)(int);
    PGETTEMPERATURE pFunc;
    TCHAR buf[256];
    HMODULE hLib = LoadLibrary(L"x86library.dll");
    if (hLib != NULL)
    
        pFunc = (PGETTEMPERATURE)GetProcAddress(hLib, "GetTemperature");
        if (pFunc != NULL)

dotnetclient 调用 GetTemperature 函数并打印结果:

static void Main(string[] args)

    float temperature = 0;
    uint sensorId = 2;
    var svc = new x86x64Lib.x86LibraryProxy();
    temperature = svc.GetTemperature(sensorId);
    Console.WriteLine($"temperature of sensorId is temperature, press any key to exit...");

如果我将所有项目构建为 x86 或 x64,这一切都有效。我得到的温度结果是20。但是,整个想法是使用 32 位 x86x64Lib.dll。这意味着dotnetclient 应该构建为 x64,x86Libraryx86x64 构建为 x86,对吗?如果我这样做,我会得到-1

我应该将x86Libraryx86x64 构建为x86 并将dotnetclient 构建为x64 吗?如果我这样做了,那么我得到-1 的问题是什么?

澄清 似乎提供的示例仅在客户端和服务器均以 32 位或 64 位构建时才有效。但当客户端构建为 64 位而服务器构建为 32 位时则不然。有人可以看看吗?

【问题讨论】:

【参考方案1】:

恕我直言,最简单的方法是使用 COM+ (Component Services),它是 Windows 的一部分,已有 20 年左右的时间了(以前的版本曾经被称为 MTS ...)。它为您提供了代理基础设施,包括工具、UI 和您需要的一切。 但这意味着您必须使用 COM,因此最好了解一些 COM。

首先创建一个 x86 COM DLL。我为此使用了ATL。创建了一个 ATL 项目,向其中添加了一个 ATL 简单对象,将方法添加到 IDL 和实现中。

.idl(注意 [out, retval] 属性,因此温度被视为包括 .NET 在内的高级语言的返回值):

import "oaidl.idl";
import "ocidl.idl";

[
  object,
  uuid(f9988875-6bf1-4f3f-9ad4-64fa220a5c42),
  dual,
  nonextensible,
  pointer_default(unique)
]
interface IMyObject : IDispatch

  HRESULT GetTemperature(ULONG sensorId, [out, retval] FLOAT* temperature);
;
[
  uuid(2de2557f-9bc2-42ef-8c58-63ba77834d0f),
  version(1.0),
]
library x86LibraryLib

  importlib("stdole2.tlb");
  [
    uuid(b20dcea2-9b8f-426d-8d96-760276fbaca9)
  ]
  coclass MyObject
  
    [default] interface IMyObject;
  ;
;

import "shobjidl.idl";

用于测试目的的方法实现:

STDMETHODIMP GetTemperature(ULONG sensorId, FLOAT* temperature)

  *temperature = sizeof(void*); // should be 4 in x86 :-)
  return S_OK;

现在,您必须在 32 位注册表中注册此组件(事实上,如果您在没有管理员权限的情况下运行 Visual Studio,它会在编译时抱怨该组件无法注册,这是意料之中的),所以在 64 位操作系统上,你必须以管理员权限运行类似这样的东西(注意 SysWow64):

c:\Windows\SysWOW64\regsvr32 x86Library.dll

完成后,运行“组件服务”,浏览“计算机/我的电脑/COM+ 应用程序”,右键单击并创建一个新应用程序。选择一个名称和一个“服务器应用程序”。这意味着您的组件将托管在 COM+ 代理进程中。

完成后,浏览“组件”,右键单击并创建一个新组件。确保选择“32 位注册表”。您应该看到对象的 ProgId。在我的情况下,当我创建我的 ATL 项目时,我将“MyObject”添加为 Progid,但否则它可能被命名为“x86Library.MyObject”或“x86LibraryLib.MyObject”......如果它不存在,那么你犯了一些错误早一点。

就是这样。现在,这个 .NET 程序将始终能够运行,编译为 AnyCpu 或 x86 或 x64:

class Program

    static void Main(string[] args)
    
        var type = Type.GetTypeFromProgID("MyObject"); // the same progid
        dynamic o = Activator.CreateInstance(type);
        Console.WriteLine(o.GetTemperature(1234)); // always displays 4
    

您可以使用组件服务 UI 来配置您的代理(激活、关闭等)。它还有一个 API,因此您可以创建 COM+ apps programmatically。

【讨论】:

在我的情况下,GetTemperature 的实现将通过调用LoadLibrary(...) 然后从该 dll 调用正确的函数来加载我的 32 位 dll,对吧? 另外,您使用 ATL 创建了进程内 COM dll。为什么不使用 ATL 创建进程外 COM exe?当我双击它时,它不会自动加载COM exe吗? 是的,对于 LoadLibrary,没问题,只要它们都是 32 位二进制文​​件。对于带有 ATL 的 COM exe,我几乎从未这样做过,因为编写 COM dll 并受益于组件服务功能(激活、安全、关闭等)要容易得多。另外,当您编写 DLL 时,您仍然可以在其他主机的进程内使用它以用于其他场景。【参考方案2】:

您将无法从 64 位代码(或相反的方式)直接调用 32 位代码,这根本不会发生。

还有其他替代方法,例如创建一个 32 位 COM 主机程序,然后将调用转发到 DLL。再加上您使用 DCOM 标准编组,因此您的 64 位进程可以连接到 32 位主机。

但如果重新编译 32 位 DLL 完全是一个选项,那几乎可以肯定是您的最佳选择。

【讨论】:

很抱歉,这如何回答我的问题?我知道您不能在 64 位空间中加载 32 位 dll。我知道您需要将该 dll 加载到 32 位进程中并使用 IPC 将函数从 64 位进程调用到 32 位进程中。这正是他们在我的帖子中提到的示例项目应该做的事情。但是,我不太明白如果汽车人将 dll 构建为 64 位,它是如何实现的。 你能提供一个简单的例子或分步说明如何做吗?

以上是关于从 64 位应用程序调用 32 位 DLL 的函数的主要内容,如果未能解决你的问题,请参考以下文章

32位程序注入64位DLL到64位进程

64 位 PowerShell 调用 32 位 DLL

64位进程调用32位dll的解决方法

将 32 位 dll 转换为 64 位

使用 C++ 从 32 位进程访问 64 位 dll

64位windows下不能调用32bit dll