32 位应用调用 64 位 DLL 的 COM 桥接实例

· · COM, Windows 开发, 32bit, 64bit

「想从 32 位应用调用 64 位 DLL」这一需求,在 Windows 中相当典型。 尤其是想在保留现有资产的前提下,只使用 64 位侧的功能时,COM 桥接架构往往是一个现实可行的解决方案。

目录

  1. 假设情境
  2. 解决方法
  3. 处理流程(序列图)
  4. 示例代码(概念示意)
  5. 完整示例代码
  6. 参考资料

1. 假设情境

这是一种希望保留现有的 32 位应用不变,同时 使用 64 位 DLL 的处理逻辑 的情形。 但是,32 位进程无法加载 64 位 DLL。这是操作系统层面的限制,并非靠某种技巧就能解决的问题。

常见的情况大致如下。

  • 现有的 32 位应用作为资产体量较大,无法立即迁移
  • 新功能位于 64 位 DLL 一侧,或者依赖库仅提供 64 位版本
  • 希望从 32 位一侧以「类型化」的方式调用

在这种组合下,在同一进程内调用的路径从一开始就被封死了

2. 解决方法

解决的基本思路是 用 Out-of-proc COM(EXE 服务器)进行分离。 64 位 DLL 由 64 位的 COM 服务器(EXE)负责调用,32 位应用则通过 COM 来使用它。

流程如下。

  1. 准备一个 64 位的 COM LocalServer(EXE),在其内部调用 64 位 DLL
  2. 共享 COM 接口(IDL/TypeLib),公开类型
  3. 32 位应用以「类型化」的方式调用 COM(通过 Proxy/Marshal 进行交互)

不过也有需要注意的地方。

  • 32 位/64 位的注册是分开的(包括 WOW6432Node)
  • 自定义结构体需要设计封送处理
  • 存在 IPC 开销,因此高频率调用需要留意

也就是说,「把 64 位的处理转移到另一个进程,并用 COM 搭桥连接」 才是王道做法。

3. 处理流程(序列图)

以下是 32 位应用调用 64 位 DLL 处理逻辑时的流程。

由已注册的 COM 封送基础设施处理64位 DLL64位 COM Server(EXE)COM Stub(64位侧)RPC/IPC(进程间通信)COM Proxy(32位侧)32位客户端应用64位 DLL64位 COM Server(EXE)COM Stub(64位侧)RPC/IPC(进程间通信)COM Proxy(32位侧)32位客户端应用封送参数解封参数封送返回值解封返回值ICalcService.Add(1, 2)序列化后的数据跨越进程边界转发Add(1, 2)调用原生函数结果: 3结果: 3序列化后的结果跨越进程边界转发结果: 3

要点:

  • 32 位应用可以通过 ICalcService 接口以类型安全的方式进行调用
  • COM 运行时会使用已注册的 Proxy/Stub DLL、TypeLib 封送器、标准封送器等机制来跨越进程边界
  • 由于存在进程间通信的开销,比起细粒度调用,更推荐采用批量处理

4. 示例代码(概念示意)

以下是 概念性的示意。实际使用时还需要进行注册、生成 TypeLib 等操作。

// 共享接口(相当于 IDL)
[ComVisible(true)]
[Guid("7A4B5B23-0A2F-4D2B-9D4D-8A2A92B8B001")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICalcService
{
    int Add(int a, int b);
}

// 64位 COM LocalServer(EXE 侧)
[ComVisible(true)]
[Guid("1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11")]
[ClassInterface(ClassInterfaceType.None)]
public class CalcService : ICalcService
{
    public int Add(int a, int b)
    {
        // 在这里调用64位 DLL
        return a + b;
    }
}

// 32位应用侧(客户端)
Type t = Type.GetTypeFromProgID("KomuraSoft.CalcService");
var calc = (ICalcService)Activator.CreateInstance(t);
int result = calc.Add(1, 2);

采用这种形式后,32 位一侧就可以以 「类型化」 的方式处理。 COM 会在内部使用代理/存根,并通过 IPC 完成调用。

5. 完整示例代码

我们在 GitHub 上公开了将上述概念以实际可运行形式实现的示例。

Call64bitDLLFrom32bitProc - GitHub

这个仓库中包含以下内容:

  • Call64bitDLLFrom32bitProc/ - 64 位 COM LocalServer (EXE)
  • X64DLL/ - 64 位 DLL(实际处理逻辑)
  • X86App/ - 32 位客户端 (WinForms)
  • scripts/ - COM 服务器注册・注销脚本

按照 README 中记载的步骤进行构建与注册,即可实际确认从 32 位进程调用 64 位 DLL 的运行效果。

6. 参考资料

  • Component Object Model (COM) 概述 https://learn.microsoft.com/en-us/windows/win32/com/component-object-model–com–portal
  • COM LocalServer32 的注册 https://learn.microsoft.com/en-us/windows/win32/com/localserver32
  • COM 接口基础 https://learn.microsoft.com/en-us/windows/win32/com/the-component-object-model
  • COM Interop(从 .NET 使用) https://learn.microsoft.com/en-us/dotnet/standard/native-interop/cominterop

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

COM / ActiveX / OCX 是什么 - 区别与关系整理

从实务角度厘清 COM、ActiveX、OCX 三者的区别与关系:COM 是 Windows 组件互操作的二进制契约底层,ActiveX 是以 COM 为基础的可嵌入控件语境,OCX 则是 ActiveX 控件常见的扩展名。读完就能分清机制、组件、文件这三层边界。

常见问题

汇总了咨询这一主题时常见的问题。

32 位应用能直接调用 64 位 DLL 吗?
不能。32 位进程无法加载 64 位 DLL,这是操作系统层面的限制,并不是靠某种技巧就能规避的问题。由于在同一进程内调用的路径从一开始就被封死,因此需要把 64 位侧的处理分离到另一个进程中的架构设计。
32 位应用要使用 64 位 DLL 的功能,该怎么做?
王道做法是用 Out-of-proc COM(EXE 服务器)进行分离。64 位 DLL 由 64 位的 COM LocalServer(EXE)负责调用,32 位应用则通过 COM 接口以类型化的方式使用它。共享 COM 接口(IDL/TypeLib)以公开类型,COM 运行时会通过代理/存根与进程间通信为你跨越进程边界。
COM 桥接架构有哪些注意事项?
主要有三点。第一,32 位与 64 位的注册是分开的(包括 WOW6432Node);第二,自定义结构体需要设计封送处理(marshaling);第三,进程间通信存在开销,因此需要留意高频率的细粒度调用。与频繁发出大量细小调用相比,更推荐把处理汇总为批量操作。
有实际可运行的示例代码吗?
有。GitHub 上的 Call64bitDLLFrom32bitProc 仓库公开了一整套示例,包含 64 位 COM LocalServer(EXE)、64 位 DLL、32 位客户端(WinForms),以及 COM 服务器注册・注销脚本。按照 README 中的步骤进行构建与注册,即可确认 32 位进程调用 64 位 DLL 的实际运行效果。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表