32 位应用调用 64 位 DLL 的 COM 桥接实例
· 更新日期: · 小村 豪 · COM, Windows 开发, 32bit, 64bit
更新记录(3 条,最后更新 2026年09月03日)
本文的修改记录。已保存的更新前版本,可通过带有 DOI 的永久链接阅读。
- 本文此前是日文原文的节译,缺少大量章节、表格、Mermaid 图、图题、脚注与 FAQ。现已改写为日文原文的完整译文,技术主张与日文版一致,并补上了此前缺失的图与表,同时统一了全篇的术语译法。 查看更新前的版本 (DOI: 10.5281/zenodo.22276551)
- 补充了日文原文中已有的咨询引导(consultation_services)。正文内容没有改动。 查看更新前的版本 (DOI: 10.5281/zenodo.22170266)
- 在第 3 节之前补充了一段。如果你想从 64 位这一侧调用的东西本身就是 in-proc 的 COM 服务器(通过 InprocServer32 注册的 DLL),那么只要给它的 CLSID 加上 AppID,并在该 AppID 键下写入空字符串的 DllSurrogate,它就能被 Windows 自带的 dllhost.exe 承载,有时根本不必自己写 EXE 服务器。这一段还指出,启动的 surrogate 的位数由 DLL 决定而非客户端,并且只要注册了 LocalServer32,surrogate 就不会被使用。不过,如果要调用的只是一个普通的原生 DLL(本文正是这种情况),那就压根没有可供承载的 COM 服务器,后文介绍的 EXE 服务器方案依然是必要的。注册步骤,以及 surrogate 是否够用、何时该自己写 EXE 的界线,分别交给了指向另外两篇文章的链接。 查看更新前的版本 (DOI: 10.5281/zenodo.21615409)
- 首次发布
引用本文(DOI: 10.5281/zenodo.21615408)
本文保存于 Zenodo。以下同时提供始终指向最新版本的 DOI,以及固定于您正在阅读版本的 DOI。
小村 豪(2026)。《32 位应用调用 64 位 DLL 的 COM 桥接实例》。小村软件有限公司。https://doi.org/10.5281/zenodo.21615408 https://comcomponent.com/zh-CN/blog/2026/01/25/002-com-case-study-32bit-to-64bit/
- DOI(最新版本)
- 10.5281/zenodo.21615408
- DOI(此版本)
- 10.5281/zenodo.22281915
想从 32 位应用调用 64 位 DLL,这类需求在 Windows 上相当典型。 尤其是想在保留现有资产的前提下只使用 64 位一侧的功能时,COM 桥接往往能成为现实可行的解决方案。
目标读者: 正在维护现有 32 位 Windows 应用,同时又想使用 64 位一侧的 DLL 或库的人。本文按照“听说过 COM,但没有自己动手搭过”的前提来写。
前提环境: 64 位版本的 Windows(x64),以及能编写 C# 的开发环境(例如 Visual Studio)。把 COM 服务器注册到整台机器范围(HKEY_LOCAL_MACHINE 之下)的操作需要管理员权限。COM 的基本思路已在“什么是 COM - 为什么 Windows COM 的设计至今依然优美”中做了梳理。
目录
图中实线表示始终成立的关系,虚线表示带条件的关系(成立条件写在详情页各关系的说明中)。关系的完整列表(共 19 条,附依据与确信度)以及主要概念的定义,汇总在知识地图详情页(日文)。数据:JSON-LD / Turtle
1. 假设情境
这是一种希望保留现有的 32 位应用不变,同时使用 64 位 DLL 的处理逻辑的情形。 但是,32 位进程无法加载 64 位 DLL。这是操作系统层面的限制,并不是动动脑筋就能绕开的问题。
常见的情况大致如下。
- 现有的 32 位应用作为资产体量较大,无法立刻迁移
- 新功能位于 64 位 DLL 一侧,或者依赖库只提供 64 位版本
- 希望从 32 位一侧以“类型化”的方式调用
在这种组合下,在同一进程内调用的路径从一开始就被封死了。
flowchart TB
accTitle: 假设情境的构图
accDescr: 该图表示:32 位的现有应用想使用 64 位 DLL 的处理逻辑,但 32 位进程无法加载 64 位 DLL,这一操作系统层面的限制封死了在同一进程内调用的路径。
app["32 位的现有应用"] --> want["想使用 64 位 DLL 的处理逻辑"]
want --> deny["同一进程内无法加载"]
deny -.-> os["操作系统层面的限制,无法绕开"]
图1:由于 32 位进程无法加载 64 位 DLL,在同一进程内调用的路径从一开始就不存在。
2. 解决方法
从本节开始会连续出现专业术语,所以先把最低限度的词汇列出来。
| 术语 | 含义 |
|---|---|
| In-proc COM(DLL 服务器) | 把 COM 组件加载到与调用方同一个进程中使用的形式。速度快,但位数不一致就无法加载 |
| Out-of-proc COM(EXE 服务器) | 把 COM 组件作为独立进程启动后使用的形式。位数不同也能协同工作 |
| LocalServer | 指在同一台电脑上以独立进程运行的 COM 服务器。要把 EXE 的路径注册到注册表的 LocalServer32 键 |
| IDL / TypeLib | 描述接口形态(方法名、参数类型)的定义(IDL),以及把它编译成二进制的产物(TypeLib)。用途是让两侧看到同一份“契约” |
| 封送处理(marshaling) | 为了跨越进程边界,把参数和返回值重新打包成可传输的形式。反向操作称为解封送 |
| Proxy / Stub | 实际执行封送处理的代理代码。调用方一侧竖起 Proxy,服务器一侧竖起 Stub |
| WOW6432Node | 在 64 位 Windows 上,面向 32 位应用的注册表内容实际存放的位置。即使键名相同,32 位一侧和 64 位一侧的内容也是分开的 |
解决问题的基本思路是用 Out-of-proc COM(EXE 服务器)进行分离。 64 位 DLL 由 64 位的 COM 服务器(EXE)负责调用,32 位应用则通过 COM 来使用它。
flowchart TB
accTitle: COM 桥接的基本构成
accDescr: 该图表示 32 位应用经由 COM 调用 64 位 COM 服务器 EXE,而该服务器在内部调用 64 位 DLL 这样一种独立进程分离的构成。
a32["32 位应用"] -->|"经由 COM 调用"| srv["64 位 COM 服务器(EXE)"]
srv -->|"在内部调用"| dll["64 位 DLL"]
图2:把 64 位 DLL 交给 64 位的 EXE 服务器持有,32 位应用经由 COM 使用该服务器。
流程如下。
- 准备一个 64 位的 COM LocalServer(EXE),在其内部调用 64 位 DLL
- 共享 COM 接口(IDL/TypeLib),公开类型
- 32 位应用以“类型化”的方式调用 COM(通过 Proxy/Marshal 进行交互)
不过也有需要注意的地方。
- 32 位/64 位的注册是分开的(包括 WOW6432Node)
- 自定义结构体需要设计封送处理
- 存在 IPC 开销,因此高频率调用需要留意
也就是说,“把 64 位的处理转移到另一个进程,再用 COM 搭桥连接”才是主流做法。
flowchart TB
accTitle: 搭建桥接的三个步骤
accDescr: 该图表示三个步骤的流程:准备 64 位的 COM LocalServer 并在其内部调用 64 位 DLL,用 IDL 和 TypeLib 公开接口的类型,再由 32 位应用以类型化的方式调用。
s1["准备 64 位的 COM LocalServer"] --> s2["用 IDL / TypeLib 公开类型"]
s2 --> s3["32 位应用以类型化的方式调用"]
s3 -.-> ps["通过 Proxy / Marshal 进行交互"]
图3:准备 LocalServer、公开类型、类型化调用这三个阶段共同构成了桥接。
另外,如果 64 位一侧想要使用的东西本来就是 in-proc 的 COM 服务器(用 InprocServer32 注册的 DLL),那么有时不必自己写 EXE 服务器也能解决。给 CLSID 加上 AppID,并往那个 AppID 键里写入空字符串的 DllSurrogate,该 DLL 就会被承载到 Windows 自带的 surrogate 进程(如果是 64 位 DLL,就是 System32\dllhost.exe)里,在 32 位客户端看来,它就是一个在独立进程中运行的 out-of-proc COM 服务器(这并不是把 EXE 的路径注册到 LocalServer32。恰恰相反,只要存在 LocalServer32,surrogate 就不会被使用)。反方向(从 64 位应用使用 32 位的 COM DLL)机制也一样,启动起来的 surrogate 的位数不是由客户端决定,而是由 DLL 一侧决定。不过,如果像本文这样想调用的只是一个普通的原生 DLL,那就根本不存在可以放进 surrogate 的 COM 服务器,因此仍然需要后文介绍的 EXE 服务器方案。注册步骤整理在 COM/OCX/ActiveX 开发中容易踩坑的注册与位数陷阱 的 3.5(那篇是以 32 位 DLL 为前提的,因此只需把写入 AppID 值的 view 换成对应的一侧来读即可);至于 surrogate 是否够用、还是要自己写 EXE,这条界线整理在 ActiveX / OCX 现在该如何处理 - 保留、封装、替换的判断表 的 5.2。
3. 处理流程(序列图)
以下是 32 位应用调用 64 位 DLL 处理逻辑时的流程。
sequenceDiagram
participant App as 32 位客户端应用
box rgba(100,100,255,0.1) 由已注册的 COM 封送基础设施处理
participant Proxy as COM Proxy<br/>(32 位侧)
participant RPC as RPC/IPC<br/>(进程间通信)
participant Stub as COM Stub<br/>(64 位侧)
end
participant Server as 64 位 COM Server<br/>(EXE)
participant DLL as 64 位 DLL
App->>Proxy: ICalcService.Add(1, 2)
rect rgba(100,100,255,0.1)
Note over Proxy: 对参数进行封送
Proxy->>RPC: 序列化后的数据
RPC->>Stub: 跨越进程边界转发
Note over Stub: 对参数进行解封送
end
Stub->>Server: Add(1, 2)
Server->>DLL: 调用原生函数
DLL-->>Server: 结果: 3
Server-->>Stub: 结果: 3
rect rgba(100,100,255,0.1)
Note over Stub: 对返回值进行封送
Stub-->>RPC: 序列化后的结果
RPC-->>Proxy: 跨越进程边界转发
Note over Proxy: 对返回值进行解封送
end
Proxy-->>App: 结果: 3
图4:32 位应用发出的调用经过 Proxy、进程间通信、Stub 到达 64 位服务器和 64 位 DLL,结果再沿同一条路径返回。
要点:
- 32 位应用可以通过
ICalcService接口以类型安全的方式进行调用 - COM 运行时会使用已注册的 Proxy/Stub DLL、TypeLib 封送器、标准封送器等机制来跨越进程边界
- 由于存在进程间通信的开销,比起细粒度调用,更推荐采用批量处理
flowchart TB
accTitle: 调用粒度的思路
accDescr: 该图表示这样一种思路:进程间通信存在开销,因此高频地进行细粒度调用会不断累加,把处理向批量方向靠拢更为可取。
ipc["进程间通信的开销"] --> fine["反复进行细粒度调用会不断累加"]
ipc --> batch["向批量处理靠拢则影响较小"]
batch -.-> rec["更可取的是这一侧"]
图5:每次跨越进程边界都要付出成本,因此把调用归拢成粗粒度更为可取。
4. 示例代码(概念示意)
4.1. 共享接口与服务器、客户端
以下是概念性的示意。要让它真正跑起来,还需要后面 4.2 的注册步骤。
// 共享接口(相当于 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)]
[ProgId("KomuraSoft.CalcService")]
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 完成调用。
之所以加上 [ProgId("KomuraSoft.CalcService")],是为了让客户端能用 Type.GetTypeFromProgID("KomuraSoft.CalcService") 找到它。ProgID 只不过是“人类可读的别名”,真正定位到服务器的,是接下来要说明的 CLSID 注册。
flowchart LR
accTitle: 从 ProgID 到达服务器的过程
accDescr: 该图表示解析顺序:客户端指定的 ProgID 只是人类可读的别名,由它查出 CLSID,再通过 CLSID 的注册找到真正的服务器。
progid["ProgID(人类可读的别名)"] --> clsid["CLSID 的注册"]
clsid --> srv["找到服务器"]
图6:ProgID 是入口处的别名,真正指向服务器的是 CLSID 的注册。
4.2. 最低限度的注册步骤
COM 的机制是“COM 运行时查询注册表中已注册的 CLSID,然后据此启动服务器”,所以没有注册过的代码绝对跑不起来(表现为 Type.GetTypeFromProgID 返回 null,或者 CreateInstance 报 REGDB_E_CLASSNOTREG)。EXE 服务器(LocalServer)所需的注册,归根结底只有下面这三个键。
| 注册内容 | 键 | 值 |
|---|---|---|
| ProgID → CLSID 的对应关系 | HKEY_CLASSES_ROOT\KomuraSoft.CalcService\CLSID |
{1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11} |
| CLSID → EXE 的路径 | HKEY_CLASSES_ROOT\CLSID\{1C9B6F4D-…}\LocalServer32 |
64 位 COM 服务器 EXE 的完整路径 |
| CLSID → ProgID 的反向查找 | HKEY_CLASSES_ROOT\CLSID\{1C9B6F4D-…}\ProgID |
KomuraSoft.CalcService |
这里就有本文主题本身的那个陷阱。Microsoft 的文档中明确写道:HKEY_LOCAL_MACHINE\SOFTWARE\Classes 由 32 位应用和 64 位应用共享,而它之下的 CLSID 子键(以及 Interface 等)在 32 位一侧和 64 位一侧是各自独立的(32 位一侧的实体就是 WOW6432Node)。也就是说,ProgID 的键写一次两边都能看到,但 CLSID 的注册必须同时写入 32 位视图和 64 位视图,否则 32 位客户端找不到服务器。
flowchart TB
accTitle: 注册表中共享的部分与分开的部分
accDescr: 该图表示:Classes 直下的 ProgID 键写一次就能被 32 位和 64 位两侧看到,而 CLSID 之下在 32 位视图和 64 位视图中是分开的,必须两边都写,缺了 32 位一侧,32 位客户端就找不到服务器。
progk["ProgID 的键(Classes 直下)"] --> shared["两侧都能看到"]
clsk["CLSID 之下的注册"] --> v64["写入 64 位视图"]
clsk --> v32["写入 32 位视图"]
v32 -.-> wow["实体是 WOW6432Node"]
v32 -.-> warn["缺失则 32 位一侧看不到"]
图7:ProgID 的键是共享的,而 CLSID 之下按 32 位/64 位视图各自独立,所以两边都要注册。
最稳妥的做法是在管理员权限的命令提示符中,使用 reg 命令的 /reg:32 /reg:64(Microsoft 不推荐自己在路径里写 Wow6432Node)。
:: 在管理员权限的命令提示符中执行
set CLSID={1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11}
set PROGID=KomuraSoft.CalcService
set SERVER=C:\Program Files\KomuraSoft\CalcServer.exe
:: 1) ProgID → CLSID(HKLM\SOFTWARE\Classes 直下由 32/64 共享)
reg add "HKLM\SOFTWARE\Classes\%PROGID%\CLSID" /ve /d "%CLSID%" /f
:: 2) CLSID → LocalServer32 与 ProgID(CLSID 之下 32/64 是分开的,所以两边都写)
:: LocalServer32 的值里要把可执行文件的路径连同引号一起存入。
:: 如果写成 /d "%SERVER%",引号会在 reg.exe 的参数解析阶段消失,值会以
:: C:\Program Files\... 的原样存入。COM 会把它当作命令行来解释,
:: 于是会先去寻找在空格前被截断的 C:\Program.exe
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\LocalServer32" /ve /d "\"%SERVER%\"" /f /reg:64
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\ProgID" /ve /d "%PROGID%" /f /reg:64
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\LocalServer32" /ve /d "\"%SERVER%\"" /f /reg:32
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\ProgID" /ve /d "%PROGID%" /f /reg:32
:: 确认存入的值。如果显示为带引号的 "C:\Program Files\..." 就是正确的
reg query "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\LocalServer32" /ve /reg:64
LocalServer32 的引号不只是书写礼仪的问题。没有引号时,C:\Program Files\... 就可能被解释成“把 Files\... 作为参数传给 C:\Program”。结果是,在存在有权创建 C:\Program.exe 的对象的环境里,那个程序有可能被抢先启动。只要路径中包含空格,就一定要加上引号。
flowchart TB
accTitle: LocalServer32 有无引号导致的解释差异
accDescr: 该图表示:如果 LocalServer32 的值中不带引号地写入路径,就会成立在空格前截断的解释,在可以放置 C:\Program.exe 的环境中那个程序可能被抢先启动;而连同引号一起存入,则会启动预期的 EXE。
noq["不带引号存入"] --> cut["成立在空格前截断的解释"]
cut --> evil["C:\Program.exe 可能被抢先启动"]
q["连同引号一起存入"] --> safe["启动预期的 EXE"]
图8:把含空格的路径不带引号地注册进去,就留下了让别的可执行文件被启动的余地。
注销时只要删掉同样的键即可。
set CLSID={1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11}
set PROGID=KomuraSoft.CalcService
reg delete "HKLM\SOFTWARE\Classes\CLSID\%CLSID%" /f /reg:64
reg delete "HKLM\SOFTWARE\Classes\CLSID\%CLSID%" /f /reg:32
reg delete "HKLM\SOFTWARE\Classes\%PROGID%" /f
除此之外,EXE 一侧在启动时还必须向 COM 声明“这个 CLSID 由我负责”。在 C/C++ 中对应 CoRegisterClassObject,在 .NET Framework 中对应 RegistrationServices.RegisterTypeForComClients。注册表登记只负责到 COM 把 EXE 启动起来为止,所以一旦漏掉这一步声明,就会出现 EXE 启动了却创建不出对象这种很难看懂的失败。
flowchart TB
accTitle: 注册表登记与 EXE 一侧的声明
accDescr: 该图表示:注册表登记只负责到 COM 启动 EXE 为止,启动后的 EXE 声明自己负责该 CLSID,才终于能创建出对象;漏掉声明就会变成 EXE 启动了却创建不出对象的失败。
regd["注册表登记"] --> boot["COM 启动 EXE"]
boot --> ann["EXE 声明自己负责该 CLSID"]
ann --> ok["能够创建对象"]
boot -.->|"漏掉声明时"| ng["启动了却创建不出来的失败"]
图9:注册表登记管到 EXE 启动为止,再往后如果没有 EXE 自己的声明,对象就创建不出来。
顺带一提,如果只是开发过程中试一试,把同样的结构写到 HKCU\SOFTWARE\Classes 而不是 HKLM,没有管理员权限也能完成注册(HKEY_CLASSES_ROOT 是 HKLM 与 HKCU 的合成视图)。不过 HKCU\SOFTWARE\Classes\CLSID 同样按 32 位/64 位分开对待,所以两个视图都要写这一点并没有变。
4.3. .NET Framework 与 .NET(5 及以上)的做法不同
上面的代码是 C#,但具体步骤会因为用哪一种 .NET 而有很大差别。把两者混在一起就会卡住。
| .NET Framework | .NET(Core 3.0 / 5 及以上) | |
|---|---|---|
| 注册工具 | 有 RegAsm.exe(但它生成的是 in-proc 用的 InprocServer32 注册,所以 LocalServer32 最终还得自己写) |
没有相当于 RegAsm 的工具 |
| 标准的 COM 公开方式 | 给程序集加上特性后使用 RegAsm |
用 <EnableComHosting>true</EnableComHosting> 生成 *.comhost.dll,再用 regsvr32 注册(仅限 in-proc) |
| TypeLib(.tlb)的生成 | 可以用 TlbExp / RegAsm /tlb 生成 |
不受支持。需要手写 IDL 并用 MIDL 编译(.NET 6 及以上可以把做好的 .tlb 嵌入 comhost) |
| CLSID 的指定 | 可以省略 | 对于要由 COM 创建的类,必须显式指定 CLSID |
| AnyCPU 的处理 | 32 位/64 位两种客户端都能使用 | 随附的 *.comhost.dll 默认是 64 位的,因此只有 64 位客户端能使用 |
本文的构成(EXE 服务器)在 .NET(5 及以上)中超出了标准 EnableComHosting 的适用范围,所以注册处理要自己写。Microsoft 官方提供了示例 OutOfProcCOM,因此在 .NET 一侧搭建时可以以它为起点。
flowchart TB
accTitle: 在 .NET(5 及以上)中搭建 EXE 服务器的路径
accDescr: 该图表示这样一条路径:本文的 EXE 服务器构成在 .NET 5 及以上超出了标准 EnableComHosting 的适用范围,因此注册处理要自己写,而官方示例 OutOfProcCOM 可以作为起点。
exe[".NET(5 及以上)中的 EXE 服务器"] --> range["超出标准 EnableComHosting 的适用范围"]
range --> self["注册处理自己写"]
self -.-> smp["官方示例 OutOfProcCOM 作为起点"]
图10:.NET(5 及以上)的 EXE 服务器超出了标准功能的适用范围,因此注册处理要以自己编写为前提。
5. 完整示例代码
我们把上述概念以实际可运行的形式实现出来,并在 GitHub 上公开了示例。
Call64bitDLLFrom32bitProc - GitHub
这个仓库中包含以下内容:
- Call64bitDLLFrom32bitProc/ - 64 位 COM LocalServer (EXE)
- X64DLL/ - 64 位 DLL(实际处理逻辑)
- X86App/ - 32 位客户端 (WinForms)
- scripts/ - COM 服务器注册、注销脚本
按照 README 中记载的步骤进行构建与注册,即可实际确认从 32 位进程调用 64 位 DLL 的运行效果。
6. 总结
COM 桥接并非万能,它是一种适合与不适合的工作界限相当分明的架构。在决定采用之前,请先用下表对照一下自己的情况。
| 适合的场景 | 不适合的场景 |
|---|---|
| 32 位应用本体无法重做(改造成本不划算) | 本来就能把 32 位一侧重新构建为 64 位(那才是最短路径) |
| 调用是粗粒度的(一次处理一张图像、一次处理一个文件等) | 逐个元素调用几万次等,高频进行细粒度调用(IPC 开销会占据主导) |
| 来回传递的是数值、字符串、数组等便于封送的类型 | 需要大量往返传递裸指针或复杂的自定义结构体 |
| 希望 64 位一侧的处理即使崩溃,应用本体也能继续存活(进程分离成为优点) | 不想编写处理服务器端崩溃与重启的恢复逻辑 |
| 希望保留类型化的调用(IntelliSense 与编译期检查) | 一次性的批处理就够了,通过标准输入输出或文件传递数据即可 |
作为最后一行的反面,“把 64 位一侧的处理做成一个普通的控制台 EXE,用参数和文件来交换数据”这种朴素的替代方案,也始终值得纳入考虑。COM 桥接真正发挥作用的场合,是想保留类型化调用的时候,以及想反复调用一个持有状态的服务器的时候。
flowchart TB
accTitle: COM 桥接与朴素替代方案的分水岭
accDescr: 该图表示这一分水岭:如果需要保留类型化调用或保持状态并反复调用服务器,COM 桥接就能发挥作用;如果一次性的批处理就够了,那么做成控制台 EXE 并用参数和文件交换数据这种朴素替代方案也值得考虑。
q{"需要类型化调用或保持状态吗?"} -->|"是"| br["COM 桥接"]
q -->|"否"| alt["控制台 EXE,用参数和文件传递数据"]
图11:是否需要类型化调用与状态保持,就是桥接与朴素替代方案之间的分水岭。
至于下一步的行动,推荐按这个顺序进行。
- 先把第 5 节的示例仓库 clone 下来,按 README 构建并注册,在手头做出一个能跑起来的状态。
- 从自己的 64 位 DLL 中只挑一个函数,往相当于示例中
ICalcService的接口上加一个方法,把它打通。 - 打通之后,测量调用次数与每次的数据量。在这里先做完“向粗粒度靠拢”的设计决策(把多次调用合并成一次),后续返工就会减少。
7. 参考资料
- 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
- WOW64 的注册表重定向器(
HKLM\SOFTWARE\Classes共享,CLSID之下 32/64 分开) https://learn.microsoft.com/en-us/windows/win32/winprog64/shared-registry-keys - 把 .NET(Core / 5 及以上)的组件公开给 COM https://learn.microsoft.com/en-us/dotnet/core/native-interop/expose-components-to-com
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
COM/OCX/ActiveX 开发中容易踩坑的注册与位数陷阱
本文从实务角度整理 COM、OCX、ActiveX 开发中容易踩坑的 32bit/64bit、Visual Studio 2022、regsvr32/Regasm、管理员权限、HKCR、STA/MTA 等问题。
Office 2024/Microsoft 365 中 ActiveX 无法运行的原因与排查步骤
梳理在 Office 2024/Microsoft 365 中 ActiveX 无法运行时,按默认禁用、32bit/64bit、COM 注册、依赖 DLL、IE 模式、Click-to-Run 日志的顺序进行排查的方法。
WinRT 就是 COM —— IInspectable、.winmd、语言投影,以及 WinUI 至今仍立在二进制契约之上的原因
WinRT 不是托管运行时,而是在 COM 之上加了元数据(.winmd)与语言投影的 ABI。本文从 IUnknown 与 IInspectable 的关系,一直讲到桌面应用里 HWND 初始化与 package identity 的卡点。
什么是 OLE 对象 —— 嵌入与链接的机制以及业务文档中的陷阱
在 Word 中嵌入 Excel 表格的功能,本质就是 OLE 对象。本文从嵌入与链接的区别、复合文件与结构化存储、In-Place Activation 的机制,一直讲到链接断开、文件膨胀与安全对策,全部立足于实务视角。
剪贴板与拖放的工作原理——在业务应用中正确处理 OLE 数据传输
粘贴 Excel 表格会散架、关掉复制源就贴不上,根源都是剪贴板把同一内容放成多种格式的机制。本文讲解标准格式、延迟渲染、OLE 拖放,直到剪贴板历史与云同步的策略。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
ActiveX 迁移
整理保留、包装或替换 COM / ActiveX / OCX 资产的阶段性判断的主题页面。
32 位 / 64 位互通
整理 32 位 / 64 位互通、原生边界与相关 Windows 设计判断的主题页面。
与本主题相关的服务
本文与以下服务页面相关联,欢迎从最接近的入口查看。
既有资产活用 & 迁移支持
本文讲的是在保留 32 位资产的同时向 64 位一侧搭桥,因此与既有资产利用、迁移支援这一主题直接相关。
技术咨询 & 设计评审
如果现在还处于想先梳理清楚 COM 桥接与进程边界该怎么划分的阶段,可以作为技术咨询、设计评审来比较和评估。
常见问题
汇总了咨询这一主题时常见的问题。
- 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 的实际运行效果。