Пример моста COM для вызова 64-битной DLL из 32-битного приложения

· · COM, Разработка Windows, 32-бит, 64-бит

Желание вызвать 64-битную DLL из 32-битного приложения — довольно типичное требование в 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 «типобезопасно» (взаимодействие через прокси/маршалинг).

Однако есть и нюансы.

  • Регистрация для 32-бит и 64-бит выполняется отдельно (включая WOW6432Node);
  • пользовательские структуры требуют отдельного проектирования маршалинга;
  • есть накладные расходы на IPC, поэтому при высокочастотных вызовах нужна осторожность.

Иными словами, проверенный подход — «вынести 64-битную обработку в отдельный процесс и связать её мостом через COM».

3. Последовательность обработки (диаграмма последовательности)

Ниже показана последовательность действий, когда 32-битное приложение вызывает обработку из 64-битной DLL.

Обрабатывается зарегистрированной подсистемой маршалинга COM64-битная DLL64-битный COM-сервер(EXE)COM Stub(64-битная сторона)RPC/IPC(межпроцессное взаимодействие)COM Proxy(32-битная сторона)32-битное клиентское приложение64-битная DLL64-битный COM-сервер(EXE)COM Stub(64-битная сторона)RPC/IPC(межпроцессное взаимодействие)COM Proxy(32-битная сторона)32-битное клиентское приложениеМаршалинг параметровДемаршалинг параметровМаршалинг возвращаемого значенияДемаршалинг возвращаемого значенияICalcService.Add(1, 2)Сериализованные данныеПередача через границу процессовAdd(1, 2)Вызов нативной функцииРезультат: 3Результат: 3Сериализованный результатПередача через границу процессовРезультат: 3

Ключевые моменты:

  • 32-битное приложение может делать типобезопасные вызовы через интерфейс ICalcService;
  • COM-подсистема пересекает границу процессов, используя зарегистрированную DLL прокси/заглушки, маршалер библиотеки типов, стандартный маршалер и так далее;
  • из-за накладных расходов на межпроцессное взаимодействие предпочтительнее группировать обработку, а не делать множество мелких вызовов.

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 или определить, где провести границы процессов, мы можем сравнить варианты вместе в рамках технической консультации и ревью архитектуры.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Может ли 32-битное приложение загрузить 64-битную DLL?
Нет. 32-битный процесс не может загрузить 64-битную DLL — это ограничение на уровне ОС Windows, а не то, что можно обойти хитрыми приёмами. Путь внутри одного процесса закрыт изначально. Если нужна функциональность, находящаяся в 64-битной DLL, обработку приходится выполнять в отдельном 64-битном процессе, и мост COM — проверенный способ связать их между собой.
Как мост COM связывает 32-битное приложение с 64-битной DLL?
Вы создаёте 64-битный COM LocalServer (EXE-файл), который вызывает 64-битную DLL внутри себя, публикуете общий интерфейс COM через IDL или библиотеку типов, чтобы раскрыть типы, и заставляете 32-битное приложение вызывать его через COM. COM-подсистема пересекает границу процессов с помощью прокси, заглушек и маршалинга, поэтому 32-битная сторона может делать типобезопасные вызовы через общий интерфейс.
Какие основные подводные камни у моста COM между процессами?
Их три. Во-первых, регистрация COM для 32-битной и 64-битной версий выполняется раздельно, включая раздел реестра WOW6432Node. Во-вторых, пользовательские структуры требуют отдельного проектирования маршалинга, а не работают автоматически. В-третьих, межпроцессное взаимодействие добавляет накладные расходы на каждый вызов, поэтому в сценариях с высокой частотой обращений предпочтительнее группировать работу, а не делать множество мелких вызовов.
Есть ли рабочий пример моста COM из 32-бит в 64-бит?
Да. Полная рабочая реализация опубликована на GitHub в репозитории Call64bitDLLFrom32bitProc. В нём есть 64-битный EXE-файл COM LocalServer, 64-битная DLL с реальной обработкой, 32-битный клиент на WinForms и скрипты регистрации. Если собрать и зарегистрировать всё по инструкции из README, можно увидеть, как 32-битный процесс фактически вызывает 64-битную DLL.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог