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

4

我有一个32位的dll文件(没有源代码),需要从64位的C#应用程序中进行访问。我已经阅读了这篇文章,并查看了这里对应的代码。我还阅读了这篇帖子。我不确定我是否在问正确的问题,所以请帮助我。

有三个项目: dotnetclientx86Libraryx86x64x86x64有一个x86LibraryProxy.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位构建时,则无效。 有人可以看一下吗?
2个回答

3
在我看来,最简单的方法是使用Windows自带的COM+(Component Services)。它已经存在大约20年了(之前的版本称为MTS...),提供了您所需的代理基础设施、工具和用户界面等一切内容。
但这意味着您必须使用COM,因此最好对COM有所了解。
首先,创建一个x86 COM DLL,我使用ATL实现。创建ATL项目,添加ATL simple object,再将该方法添加到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
    }
}

你可以使用组件服务界面来配置你的代理(激活、关闭等)。它还有一个API,因此你可以编写程序来创建COM+应用。

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

0

你无法直接从64位代码(或反过来)调用32位代码,这是不可能的。

有替代方案,例如创建一个32位COM宿主程序,然后将调用转发到DLL。与此同时,您可以使用DCOM标准编排,使您的64位进程可以连接到32位宿主。

但是,如果重新编译32位DLL是可行的选项,那几乎肯定是最好的选择。


对不起,这怎么回答我的问题呢?我知道你不能在64位空间中加载32位dll。我知道我需要将该dll加载到32位进程中,并使用IPC从64位进程调用32位进程中的函数。这正是我在帖子中提到的示例项目所要做的。但是,如果作者将dll构建为64位,我不太明白它是如何实现的。 - theateist
你能提供一个简单的例子或者一步一步的说明吗?谢谢。 - theateist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接