A Worked Example of a COM Bridge for Calling a 64-bit DLL from a 32-bit App
· Updated: · Go Komura · COM, Windows Development, 32bit, 64bit
Revision history (2 updates, last updated Sep 1, 2026)
A log of the changes made to this article. Where a pre-update version was archived, it stays readable at a permanent DOI link.
- Retranslated as a full translation of the Japanese original. The previous English version was an abridgement that carried only part of the source, so sections, tables, Mermaid diagrams, figure captions and FAQ entries were missing. All of them have been restored to match the Japanese original, and the technical claims are the same as in the Japanese version. Read the version before this update (DOI: 10.5281/zenodo.22170260)
- Added a paragraph ahead of section 3. If what you want to reach from the 64-bit side is itself an in-proc COM server (a DLL registered through InprocServer32), then adding an AppID to its CLSID and an empty DllSurrogate under that AppID hosts it in the dllhost.exe that ships with Windows, and you may not have to write an EXE server at all. It also notes that the surrogate which starts follows the bitness of the DLL rather than the client, and that a registered LocalServer32 means the surrogate is not used. When the thing you want to call is a plain native DLL, as in this article, there is no COM server to host in the first place, so the EXE-server approach described below is still required. The registration steps, and the line between what a surrogate covers and what needs an EXE of your own, are left to links to the other two articles. Read the version before this update (DOI: 10.5281/zenodo.21614447)
- First published
Cite this article(DOI: 10.5281/zenodo.21614446)
This article is archived on Zenodo. Below are both the DOI that always resolves to the latest version and the DOI pinned to the version you are reading.
Go Komura (2026). A Worked Example of a COM Bridge for Calling a 64-bit DLL from a 32-bit App. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21614446 https://comcomponent.com/en/blog/2026/01/25/002-com-case-study-32bit-to-64bit/
- DOI (latest version)
- 10.5281/zenodo.21614446
- DOI (this version)
- 10.5281/zenodo.22217115
Wanting to call a 64-bit DLL from a 32-bit app is a fairly typical requirement on Windows. Especially when you want to keep existing assets in place and use only the functionality on the 64-bit side, a COM bridge tends to be the practical answer.
Who this is for: Anyone maintaining an existing 32-bit Windows app who wants to use a DLL or library that lives on the 64-bit side. It is written so that you can follow it if you have heard of COM but have never built anything with it yourself.
Prerequisites: 64-bit Windows (x64) and a development environment where you can write C# (Visual Studio, for example). Registering a COM server machine-wide (under HKEY_LOCAL_MACHINE) requires administrator rights. The basic ideas behind COM are covered in “What Is COM? - Why the Design of Windows COM Is Still Beautiful Today”.
Table of Contents
- The Scenario
- The Solution
- Processing Flow (Sequence Diagram)
- Sample Code (Conceptual)
- Complete Sample Code
- Summary
- References
Knowledge map for this article
A 32-bit app cannot load a 64-bit DLL directly as in-proc COM because the bitness does not match, so this article separates the processing on the 64-bit side into another process as a COM LocalServer (an EXE server) and calls it in a typed way through a COM interface shared via IDL and a TypeLib. Marshaling and the proxy/stub bridge the calls that cross the process boundary, and the approach assumes registration of the CLSID and the ProgID, plus registration on both sides of the WOW64 registry redirector, where the CLSID subkey is separate in the 32-bit view and the 64-bit view. Registering for the whole machine requires administrator rights, and because the standard EnableComHosting in .NET (5 and later) is for in-proc only, the registration logic for an out-of-proc server has to be written yourself.
flowchart LR
accTitle: 32-bit to 64-bit COM bridge
accDescr: Diagram showing how an arrangement in which a 32-bit app calls a 64-bit DLL through a COM LocalServer EXE server relates to the type library, marshaling, CLSID and ProgID registration, and the WOW64 registry redirector
com_bridge_32_64["32-bit/64-bit COM bridge"]
com_localserver["COM LocalServer (out-of-process server)"]
type_library["Type Library (TLB)"]
com_marshaling["Marshaling"]
proxy_stub["Proxy/Stub"]
in_proc_com["In-Proc COM (DLL Server)"]
bitness_match_requirement["Bitness Match Requirement"]
clsid["CLSID (Class ID)"]
progid["ProgID (Programmatic Identifier)"]
wow64_registry_redirector["WOW64 Registry Redirector"]
wow6432node["WOW6432Node"]
reg_exe["reg.exe (/reg:32, /reg:64)"]
class_factory_registration["Class Factory Registration"]
admin_rights["Administrator Privileges"]
regasm["Regasm.exe"]
dotnet[".NET (Core and Later)"]
dotnet_framework[".NET Framework"]
dotnet_comhost[".NET 5+ COM Hosting (.comhost.dll)"]
com["COM (Component Object Model)"]
com_bridge_32_64 -->|"uses"| com_localserver
com_bridge_32_64 -->|"requires"| type_library
com_localserver -->|"uses"| com_marshaling
com_marshaling -->|"uses"| proxy_stub
in_proc_com -->|"requires"| bitness_match_requirement
com_localserver -->|"mitigates"| bitness_match_requirement
com_localserver -->|"requires"| clsid
progid -->|"requires"| clsid
com_localserver -.->|"requires"| wow64_registry_redirector
clsid -.->|"stored in"| wow6432node
wow64_registry_redirector -->|"configured by"| reg_exe
com_localserver -->|"requires"| class_factory_registration
com_bridge_32_64 -.->|"requires"| admin_rights
regasm -->|"not recommended for"| dotnet
regasm -->|"requires"| dotnet_framework
dotnet_comhost -->|"uses"| dotnet
dotnet_comhost -->|"not recommended for"| com_bridge_32_64
dotnet -.->|"requires"| clsid
com -.->|"uses"| type_library
In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (19 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle
1. The Scenario
You want to keep the existing 32-bit app exactly as it is, but use processing that lives in a 64-bit DLL. The problem is that a 32-bit process cannot load a 64-bit DLL. This is an OS-level constraint, not something you can work around with clever tricks.
The situation usually looks like this.
- The existing 32-bit app is a large asset and cannot be migrated any time soon
- The 64-bit DLL has new functionality, or its dependencies are 64-bit only
- You want to call it from the 32-bit side with full type information
With this combination, the in-process route is closed off from the start.
flowchart TB
accTitle: The shape of the scenario
accDescr: A diagram showing that an existing 32-bit app wants to use processing in a 64-bit DLL, but a 32-bit process cannot load a 64-bit DLL, an OS-level constraint that closes off the in-process route.
app["Existing 32bit app"] --> want["Wants to use processing in a 64bit DLL"]
want --> deny["Cannot be loaded in the same process"]
deny -.-> os["An OS-level constraint with no way around it"]
Figure 1: A 32-bit process cannot load a 64-bit DLL, so the in-process route never existed in the first place.
2. The Solution
This section leans on a fair amount of jargon, so here is the minimum vocabulary up front.
| Term | Meaning |
|---|---|
| In-proc COM (DLL server) | Loading the COM component into the same process as the caller. Fast, but it cannot be loaded unless the bitness matches |
| Out-of-proc COM (EXE server) | Running the COM component as a separate process. Works even when the bitness differs |
| LocalServer | A COM server that runs as a separate process on the same PC. The path to the EXE goes into the registry under the LocalServer32 key |
| IDL / TypeLib | The definition of the interface shape (method names, argument types) written in IDL, plus its binary form (TypeLib). Both sides use them to look at the same contract |
| Marshaling | Repacking arguments and return values into a form that can travel across a process boundary. The reverse is unmarshaling |
| Proxy / Stub | The stand-in code that actually performs the marshaling. A proxy sits on the caller side, a stub on the server side |
| WOW6432Node | The place on 64-bit Windows where registry content for 32-bit apps physically lives. The same key name holds different content on the 32-bit and 64-bit sides |
The basic solution is to separate the two with out-of-proc COM (an EXE server). The 64-bit DLL is called from a 64-bit COM server (an EXE), and the 32-bit app uses that server through COM.
flowchart TB
accTitle: Basic structure of the COM bridge
accDescr: A diagram showing the separate-process structure in which a 32-bit app calls a 64-bit COM server EXE through COM and that server calls the 64-bit DLL internally.
a32["32bit app"] -->|"Calls through COM"| srv["64bit COM server (EXE)"]
srv -->|"Calls internally"| dll["64bit DLL"]
Figure 2: The 64-bit DLL is hosted by a 64-bit EXE server, and the 32-bit app uses that server through COM.
The flow is as follows.
- Build a 64-bit COM LocalServer (an EXE) that calls the 64-bit DLL internally
- Share the COM interface (IDL/TypeLib) and expose the types
- The 32-bit app calls COM with full type information (the exchange goes through a proxy and marshaling)
There are caveats, though.
- 32-bit and 64-bit registrations are separate (including WOW6432Node)
- Custom structs require marshaling design
- IPC overhead exists, so be careful with high-frequency calls
In short, the proven approach is to move the 64-bit processing into a separate process and bridge it with COM.
flowchart TB
accTitle: Three steps to build the bridge
accDescr: A diagram showing the three-step flow of preparing a 64-bit COM LocalServer that calls the 64-bit DLL internally, exposing the interface types through IDL and TypeLib, and having the 32-bit app call it with full type information.
s1["Prepare a 64bit COM LocalServer"] --> s2["Expose the types with IDL / TypeLib"]
s2 --> s3["The 32bit app calls it with type information"]
s3 -.-> ps["Exchange goes through proxy / marshaling"]
Figure 3: The bridge comes together in three stages: prepare the LocalServer, expose the types, make typed calls.
That said, if what you want to use on the 64-bit side is already an in-proc COM server (a DLL registered under InprocServer32), you may be able to avoid writing an EXE server at all. Attach an AppID to the CLSID and write an empty-string DllSurrogate value under that AppID key, and the DLL is hosted in the surrogate process that ships with Windows (System32\dllhost.exe for a 64-bit DLL); to a 32-bit client it then looks like an out-of-proc COM server running in its own process (you are not registering the path of an EXE in LocalServer32 - in fact, if LocalServer32 is present, the surrogate is not used). The mechanism works the same way in the other direction (using a 32-bit COM DLL from a 64-bit app): the bitness of the surrogate that starts is decided by the DLL, not by the client. However, when the thing you want to call is just a plain native DLL, as in this article, there is no COM server to host in the surrogate in the first place, so you need the EXE server approach described from here on. The registration steps are in 3.5 of Registration and Bitness Pitfalls in COM/OCX/ActiveX Development (that article assumes a 32-bit DLL, so only the view you write the AppID value into needs to be read the other way round), and where to draw the line between a surrogate being enough and writing your own EXE is covered in 5.2 of How to Handle ActiveX / OCX Today - A Keep / Wrap / Replace Decision Table.
3. Processing Flow (Sequence Diagram)
The following shows the flow when the 32-bit app invokes processing in the 64-bit DLL.
sequenceDiagram
participant App as 32bit client app
box rgba(100,100,255,0.1) Handled by the registered COM marshaling infrastructure
participant Proxy as COM Proxy<br/>(32bit side)
participant RPC as RPC/IPC<br/>(inter-process communication)
participant Stub as COM Stub<br/>(64bit side)
end
participant Server as 64bit COM Server<br/>(EXE)
participant DLL as 64bit DLL
App->>Proxy: ICalcService.Add(1, 2)
rect rgba(100,100,255,0.1)
Note over Proxy: Marshal the parameters
Proxy->>RPC: Serialized data
RPC->>Stub: Transferred across the process boundary
Note over Stub: Unmarshal the parameters
end
Stub->>Server: Add(1, 2)
Server->>DLL: Native function call
DLL-->>Server: Result: 3
Server-->>Stub: Result: 3
rect rgba(100,100,255,0.1)
Note over Stub: Marshal the return value
Stub-->>RPC: Serialized result
RPC-->>Proxy: Transferred across the process boundary
Note over Proxy: Unmarshal the return value
end
Proxy-->>App: Result: 3
Figure 4: The call from the 32-bit app travels through the proxy, inter-process communication, and the stub to reach the 64-bit server and the 64-bit DLL, and the result comes back along the same path.
Key points:
- The 32-bit app can make type-safe calls through the
ICalcServiceinterface - The COM runtime crosses the process boundary using the registered proxy/stub DLL, the TypeLib marshaler, the standard marshaler, and so on
- Because inter-process communication has overhead, batching work is preferable to many fine-grained calls
flowchart TB
accTitle: How to think about call granularity
accDescr: A diagram showing that because inter-process communication has overhead, repeating fine-grained calls at high frequency lets the cost pile up, so leaning toward batched processing is preferable.
ipc["Overhead of inter-process communication"] --> fine["Piles up when fine-grained calls repeat"]
ipc --> batch["Has little impact when work is batched"]
batch -.-> rec["This is the preferable side"]
Figure 5: Every crossing of the process boundary costs something, so it pays to group calls into a coarser granularity.
4. Sample Code (Conceptual)
4.1. The Shared Interface, the Server, and the Client
The following is a conceptual sketch. To make it run, you also need the registration described in 4.2.
// Shared interface (IDL equivalent)
[ComVisible(true)]
[Guid("7A4B5B23-0A2F-4D2B-9D4D-8A2A92B8B001")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICalcService
{
int Add(int a, int b);
}
// 64bit COM LocalServer (EXE side)
[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)
{
// Call the 64bit DLL here
return a + b;
}
}
// 32bit app side (client)
Type t = Type.GetTypeFromProgID("KomuraSoft.CalcService");
var calc = (ICalcService)Activator.CreateInstance(t);
int result = calc.Add(1, 2);
With this shape, the 32-bit side gets to work with full type information. COM uses proxies and stubs internally and makes the call over IPC for you.
[ProgId("KomuraSoft.CalcService")] is there so that the client can find the class with Type.GetTypeFromProgID("KomuraSoft.CalcService"). A ProgID is nothing more than a human-readable alias; what actually locates the server is the CLSID registration described next.
flowchart LR
accTitle: From ProgID to the server
accDescr: A diagram showing the resolution order in which the ProgID the client specifies is only a human-readable alias, the CLSID is looked up from it, and the CLSID registration is what locates the actual server.
progid["ProgID (human-readable alias)"] --> clsid["CLSID registration"]
clsid --> srv["The server is found"]
Figure 6: The ProgID is the alias at the front door; what really points at the server is the CLSID registration.
4.2. The Minimum Registration Steps
COM works by having the COM runtime look up a CLSID in the registry and start the server it points to, so code that is not registered will never run (Type.GetTypeFromProgID returns null, or CreateInstance fails with REGDB_E_CLASSNOTREG). Boiled down, an EXE server (a LocalServer) needs only these three keys.
| What you register | Key | Value |
|---|---|---|
| ProgID -> CLSID mapping | HKEY_CLASSES_ROOT\KomuraSoft.CalcService\CLSID |
{1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11} |
| CLSID -> path to the EXE | HKEY_CLASSES_ROOT\CLSID\{1C9B6F4D-...}\LocalServer32 |
Full path to the 64-bit COM server EXE |
| CLSID -> ProgID reverse lookup | HKEY_CLASSES_ROOT\CLSID\{1C9B6F4D-...}\ProgID |
KomuraSoft.CalcService |
Here is the pitfall that is the very subject of this article. HKEY_LOCAL_MACHINE\SOFTWARE\Classes is shared between 32-bit and 64-bit apps, but Microsoft’s documentation states explicitly that the CLSID subkey underneath it (along with Interface and others) is separate for the 32-bit and 64-bit sides (the 32-bit side physically living in WOW6432Node). In other words, the ProgID key only has to be written once and both sides can see it, but unless the CLSID registration is written into both the 32-bit and the 64-bit view, a 32-bit client will not find the server.
flowchart TB
accTitle: Which parts of the registry are shared and which are split
accDescr: A diagram showing that the ProgID key directly under Classes is visible from both 32-bit and 64-bit once written, while the entries under CLSID are separate per view and must be written to both, and that a missing 32-bit side means a 32-bit client cannot find the server.
progk["ProgID key (directly under Classes)"] --> shared["Visible from both sides"]
clsk["Registration under CLSID"] --> v64["Write it in the 64bit view"]
clsk --> v32["Write it in the 32bit view"]
v32 -.-> wow["Physically stored in WOW6432Node"]
v32 -.-> warn["If missing, invisible from 32bit"]
Figure 7: The ProgID key is shared, but everything under CLSID is per-view, so register it in both.
The reliable way is an administrator command prompt and the /reg:32 and /reg:64 switches of the reg command (writing Wow6432Node into the path yourself is something Microsoft discourages).
:: Run this in an administrator command prompt
set CLSID={1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11}
set PROGID=KomuraSoft.CalcService
set SERVER=C:\Program Files\KomuraSoft\CalcServer.exe
:: 1) ProgID -> CLSID (everything directly under HKLM\SOFTWARE\Classes is shared by 32/64)
reg add "HKLM\SOFTWARE\Classes\%PROGID%\CLSID" /ve /d "%CLSID%" /f
:: 2) CLSID -> LocalServer32 and ProgID (entries under CLSID are separate for 32/64, so write both)
:: The LocalServer32 value must contain the executable path together with its quotes.
:: Writing /d "%SERVER%" makes reg.exe strip the quotes during argument parsing, and the
:: value is stored as plain C:\Program Files\... . COM interprets that as a command line,
:: so it first goes looking for C:\Program.exe, cut off at the space
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
:: Check the stored value. It is correct if it comes back quoted, as "C:\Program Files\..."
reg query "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\LocalServer32" /ve /reg:64
The quotes around LocalServer32 are not just a matter of tidiness. Without them, C:\Program Files\... can legitimately be read as passing Files\... as an argument to C:\Program. As a result, in an environment where someone has the rights to create C:\Program.exe, that executable can be started instead. Always add quotes when the path contains a space.
flowchart TB
accTitle: How the presence of quotes changes the reading of LocalServer32
accDescr: A diagram showing that storing the path in LocalServer32 without quotes allows a reading that cuts it off at the space so that C:\Program.exe can be started first in an environment where that file can be placed, while storing it with quotes starts the intended EXE.
noq["Stored without quotes"] --> cut["A reading that cuts off at the space becomes valid"]
cut --> evil["C:\Program.exe can be started first"]
q["Stored with quotes"] --> safe["The intended EXE is started"]
Figure 8: Registering a path containing a space without quotes leaves room for a different executable to be started.
Unregistering is just deleting the same keys.
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
On top of that, the EXE has to announce to COM, when it starts, that it is the one serving this CLSID. In C/C++ that is CoRegisterClassObject; in .NET Framework it is RegistrationServices.RegisterTypeForComClients. Registry registration only takes care of getting COM to launch the EXE, so if this announcement is missing you get a confusing failure in which the EXE starts but no object can be created.
flowchart TB
accTitle: Registry registration and the EXE announcing itself
accDescr: A diagram showing that registry registration only covers COM launching the EXE, that objects can be created only once the launched EXE announces it is serving this CLSID, and that a missing announcement produces a failure where the EXE starts but no object can be created.
regd["Registry registration"] --> boot["COM launches the EXE"]
boot --> ann["The EXE announces it serves the CLSID"]
ann --> ok["Objects can be created"]
boot -.->|"If the announcement is missing"| ng["It starts but nothing can be created"]
Figure 9: Registry registration reaches as far as launching the EXE; beyond that, no object exists without the EXE announcing itself.
If you only need something to try during development, writing the same structure under HKCU\SOFTWARE\Classes instead of HKLM lets you register without administrator rights (HKEY_CLASSES_ROOT is a merged view of HKLM and HKCU). Note, though, that HKCU\SOFTWARE\Classes\CLSID is likewise handled separately for 32-bit and 64-bit, so you still have to write into both views.
4.3. .NET Framework and .NET (5 and Later) Are Built Differently
The code above is C#, but which .NET you use changes the procedure substantially. Mixing the two is where people get stuck.
| .NET Framework | .NET (Core 3.0 / 5 and later) | |
|---|---|---|
| Registration tool | RegAsm.exe exists (but what it creates is an in-proc InprocServer32 registration, so you end up writing LocalServer32 yourself anyway) |
There is no equivalent of RegAsm |
| Standard way to expose COM | Put attributes on the assembly and run RegAsm |
<EnableComHosting>true</EnableComHosting> generates *.comhost.dll, which you register with regsvr32 (in-proc only) |
| Generating a TypeLib (.tlb) | Can be generated with TlbExp or RegAsm /tlb |
Not supported. Write the IDL by hand and compile it with MIDL (from .NET 6 on, the resulting .tlb can be embedded in the comhost) |
| Specifying the CLSID | Optional | An explicit CLSID is required for classes COM is meant to create |
| How AnyCPU behaves | Usable from both 32-bit and 64-bit clients | The accompanying *.comhost.dll is 64-bit by default, so it is usable only from 64-bit clients |
The architecture in this article (an EXE server) falls outside what the standard EnableComHosting covers on .NET (5 and later), which means writing the registration logic yourself. Microsoft publishes an official sample, OutOfProcCOM, so that is the starting point if you build this on .NET.
flowchart TB
accTitle: The route to an EXE server on .NET (5 and later)
accDescr: A diagram showing that the EXE server architecture in this article falls outside the standard EnableComHosting on .NET 5 and later, so the registration logic has to be written by hand, with the official OutOfProcCOM sample as the starting point.
exe["EXE server on .NET (5 and later)"] --> range["Outside the scope of the standard EnableComHosting"]
range --> self["Write the registration logic yourself"]
self -.-> smp["The official OutOfProcCOM sample is the starting point"]
Figure 10: An EXE server on .NET (5 and later) sits outside the built-in feature set, so plan on writing the registration yourself.
5. Complete Sample Code
A working implementation of the concepts above is published on GitHub.
Call64bitDLLFrom32bitProc - GitHub
The repository contains the following:
- Call64bitDLLFrom32bitProc/ - 64bit COM LocalServer (EXE)
- X64DLL/ - 64bit DLL (the actual processing)
- X86App/ - 32bit client (WinForms)
- scripts/ - COM server registration and unregistration scripts
If you build and register it following the steps in the README, you can watch a 32-bit process actually call a 64-bit DLL.
6. Summary
A COM bridge is not a universal answer; it is an architecture where the jobs it suits and the jobs it does not are clearly separated. Before committing to it, hold your own case up against the table below.
| Cases it suits | Cases it does not suit |
|---|---|
| The 32-bit app itself cannot be rebuilt (the cost of reworking it does not pay off) | The 32-bit side can simply be rebuilt as 64-bit (that is the shortest path) |
| Calls are coarse-grained (one image per call, one file per call, and so on) | Tens of thousands of calls one element at a time, or any high-frequency fine-grained pattern (IPC overhead dominates) |
| What you exchange are numbers, strings, arrays, and other types that marshal easily | Raw pointers or complex custom structs shuttled back and forth in bulk |
| You want the app itself to stay alive even when the 64-bit processing crashes (process isolation becomes an advantage) | You do not want to write recovery logic for server crashes and restarts |
| You want to keep typed calls (IntelliSense and compile-time checking) | A one-shot batch job is enough, and passing data through standard I/O or files will do |
As the flip side of that last row, the plain alternative of making the 64-bit processing a simple console EXE and exchanging data through arguments and files is always worth considering. A COM bridge earns its keep when you want to keep typed calls and when you want to call a stateful server repeatedly.
flowchart TB
accTitle: Where the COM bridge and the plain alternative diverge
accDescr: A diagram showing the dividing line where a COM bridge earns its place if typed calls must be kept or a stateful server has to be called repeatedly, while a one-shot batch job is better served by the plain alternative of a console EXE exchanging data through arguments and files.
q{"Are typed calls or retained state required?"} -->|"Yes"| br["COM bridge"]
q -->|"No"| alt["Console EXE passing arguments and files"]
Figure 11: Whether you need typed calls and retained state is what separates the bridge from the plain alternative.
For next steps, here is the order I recommend.
- First, clone the sample repository from section 5, build and register it exactly as the README says, and get one working setup in your own hands.
- Pick a single function from your own 64-bit DLL and add one method to the interface that corresponds to the sample’s
ICalcService, then get it working end to end. - Once it works, measure how many calls you make and how much data each one carries. Settling the design decision to move toward coarse granularity here (merging several calls into one) up front saves you from backtracking later.
7. References
- Component Object Model (COM) overview https://learn.microsoft.com/en-us/windows/win32/com/component-object-model–com–portal
- Registering COM LocalServer32 https://learn.microsoft.com/en-us/windows/win32/com/localserver32
- COM interface basics https://learn.microsoft.com/en-us/windows/win32/com/the-component-object-model
- COM Interop (use from .NET) https://learn.microsoft.com/en-us/dotnet/standard/native-interop/cominterop
- The WOW64 registry redirector (
HKLM\SOFTWARE\Classesis shared, entries underCLSIDare separate for 32/64) https://learn.microsoft.com/en-us/windows/win32/winprog64/shared-registry-keys - Exposing .NET (Core / 5 and later) components to COM https://learn.microsoft.com/en-us/dotnet/core/native-interop/expose-components-to-com
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
Registration and Bitness Pitfalls in COM/OCX/ActiveX Development
A practical look at the 32bit/64bit, Visual Studio 2022, regsvr32/Regasm, administrator-rights, HKCR, and STA/MTA pitfalls that trip up C...
Why ActiveX Stops Working in Office 2024/Microsoft 365 and How to Diagnose It
When ActiveX stops working in Office 2024/Microsoft 365, work through the causes in order: default disablement, 32-bit/64-bit mismatch, C...
How the Clipboard and Drag & Drop Work — Handling OLE Data Transfer Correctly in Business Apps
Paste an Excel table and the formatting falls apart; close the source app and you can no longer paste — both come from the clipboard plac...
Windows Shell Integration Today — Context Menus, File Associations, and What Changed in Windows 11
Why a Windows 11 context menu hides items behind "Show more options", explained from the extension → ProgID → verb association basics thr...
Do Business Apps Run on Windows on Arm? — The Reality of x64 Emulation (Prism) and Native DLLs/COM
An answer, aimed at developers and IT staff, to 'will our business app run on Windows on Arm?' Covers how x64 emulation (Prism) works, th...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
ActiveX Migration
Topic page for staged decisions around keeping, wrapping, or replacing COM / ActiveX / OCX assets.
Where This Topic Connects
This article connects naturally to the following service pages.
Legacy Asset Reuse & Migration Support
This is about building a bridge to the 64-bit side while keeping 32-bit assets in place, so it ties directly into our legacy asset reuse and migration support.
Technical Consulting & Design Review
If you first want to sort out the COM bridge design or where to draw process boundaries, we can compare options together through technical consulting and design review.
Frequently Asked Questions
Common questions about the topic of this article.
- Can I call a 64-bit DLL directly from a 32-bit app?
- No. A 32-bit process cannot load a 64-bit DLL. This is an OS-level constraint, not something you can work around with clever tricks. The in-process route is closed off from the start, so you need an architecture that moves the 64-bit processing into a separate process.
- How can I use the functionality of a 64-bit DLL from a 32-bit app?
- The proven approach is to separate the two with out-of-proc COM (an EXE server). The 64-bit DLL is called from a 64-bit COM LocalServer (an EXE), and the 32-bit app uses it through a COM interface with full type information. You share the COM interface (IDL/TypeLib) to expose the types, and the COM runtime crosses the boundary for you with proxies, stubs, and inter-process communication.
- What should I watch out for with a COM bridge architecture?
- There are three main points. 32-bit and 64-bit registrations are separate (including WOW6432Node), custom structs require marshaling design, and inter-process communication adds overhead, so high-frequency fine-grained calls need care. Rather than pushing large numbers of small calls through the bridge, lean toward batching the work.
- Is there sample code that actually runs?
- Yes. The Call64bitDLLFrom32bitProc repository on GitHub publishes a complete sample that includes a 64-bit COM LocalServer (EXE), a 64-bit DLL, a 32-bit client (WinForms), and scripts for registering and unregistering the COM server. Build and register it following the steps in the README, and you can watch a 32-bit process call a 64-bit DLL.