Обработка событий при вызове сторонней 32-разрядной библиотеки DLL из 64-разрядного приложения C# WinForms

У нас есть старое 32-разрядное решение Visual Studio C# Windows Forms, которое мы хотим теперь скомпилировать в 64-разрядном формате. К сожалению, наше приложение использует некоторые внешние dll-ки (для сканеров, камер и т.д.), которые доступны только в 32-битной версии. Доступ к 32-битным DLL из 64-битного кода не является простым, особенно когда мы хотим обрабатывать также события, вызванные этими dll-ами. Наших знаний в этой области недостаточно для создания реализации на основе этой статьи, поэтому мы ищем более подробные инструкции или примеры.

Наша первая попытка была основана на эта статья. Мы обернули сторонние DLL-файлы в 32-битный COM-сервер с поздней привязкой и использовали его из нашего 64-битного приложения, как описано здесь (с соответствующими изменениями, потому что нам пришлось поменять местами роли 32-битной и 64-битной) . Эта попытка была успешной, но неполной, поскольку это решение не доставляет события с COM-сервера на 64-разрядный клиент. Итак, мы начали сосредотачиваться на событиях. Мы нашли множество статей и примеров, касающихся использования событий, вызванных COM-объектом, но ни одна из них не дает нам полного решения. Часть исходников имеет дело исключительно с клиентом, либо исключительно с сервером, но они несовместимы ни друг с другом, ни с нашей средой (WinForm, c#).

Например,

  1. этот ответ рассказывает нам, как создать COM-сервер, который предоставляет события .NET клиенту VBA, но я не знаю, как его использовать из клиента C#.
  2. Напротив, эта статья дает хороший работающий клиент C# для существующего COM-сервера, но не говорит, как сделать такой COM-сервер (этот COM-сервер заметно отличается от предыдущего примера)
  3. этот ответ не сообщает никаких деталей решения.
  4. эта статья для С++ вместо С#.
  5. Этот ответ относится к эта статья, но последний снова использует клиент VB вместо c#.
  6. эта статья смешивает разные вещи неуловимым образом.

Возможно, некоторые из них мы можем использовать с некоторым усилием, но какие и как?

Редактировать

Сейчас я склоняюсь к гибридному решению: моя новейшая идея обратной связи от 32-битного COM-объекта к вызывающему 64-битному приложению состоит в том, чтобы поместить сервер именованного канала в 64-битное приложение, а клиент именованного канала — в COM-объект и каждый раз, когда в COM-объекте возникает событие, он отправляет сообщение Named Pipe на сервер Named Pipe. Код, который я нашел для этого, доступен здесь (проекты CSNamedPipeServer и CSNamedPipeClient).

Генерирует ли эта DLL визуальные компоненты или просто генерирует события обратно в ваш код? 32-разрядная => 64-разрядная версия - это куча червей (как вы уже обнаружили), поскольку она требует от вас пересечения границ процесса (32-разрядная версия - это отдельная виртуальная среда в 64-разрядных окнах). Пробовали ли вы 32-битную оболочку на C#, говорящую вне процесса вашей 64-битной программе?

Steve Todd 08.04.2019 19:09

Разве вы не задавали этот вопрос несколько дней назад (и удалили его вместе с комментариями)?

Simon Mourier 08.04.2019 21:10

@SimonMourier Почти. Проблема та же, но вопрос теперь другой и (надеюсь) более проработанный.

mma 09.04.2019 11:54

Я могу предоставить образец C# с 64-битным COM-сервером с событием и 32-битным COM-клиентом, которые работают, если хотите.

Simon Mourier 09.04.2019 12:06

@SimonMourier Круто! Я с нетерпением жду вашего решения.

mma 09.04.2019 12:22
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
421
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вот пример с 64-разрядным сервером, реализованным как класс C# в проекте библиотеки классов, размещенном на системном суррогате Windows: dllhost.

Это код класса (вы можете скомпилировать как «любой процессор», не нужно компилировать как x64):

namespace NetComClassLibrary3
{
    // technically, we don't *have to* define an interface, we could do everything using dynamic stuff
    // but it's more practical so we can reference this .NET dll from our client
    [ComVisible(true)]
    [Guid("31dd1263-0002-4071-aa4a-d226a55116bd")]
    public interface IMyClass
    {
        event OnMyEventDelegate OnMyEvent;
        object MyMethod();
    }

    // same remark than above.
    // This *must* match the OnMyEvent signature below
    [ComVisible(true)]
    [Guid("31dd1263-0003-4071-aa4a-d226a55116bd")]
    public delegate void OnMyEventDelegate(string text);

    // this "event" interface is mandatory
    // note from the .NET perspective, no one seems to implement it
    // but it's referenced with the ComSourceInterfaces attribute on our COM server (below)
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    [Guid("31dd1263-0000-4071-aa4a-d226a55116bd")]
    public interface IMyEvents
    {
        // dispids are mandatory here otherwise you'll get a DISP_E_UNKNOWNNAME error
        [DispId(1)]
        void OnMyEvent(string text);
    }

    [ComVisible(true)]
    [ComSourceInterfaces(typeof(IMyEvents))]
    [Guid("31dd1263-0001-4071-aa4a-d226a55116bd")]
    public class MyClass : IMyClass
    {
        public event OnMyEventDelegate OnMyEvent;

        public object MyMethod()
        {
            // we use the current running process to test out stuff
            // this should be Windows' default surrogate: dllhost.exe
            var process = Process.GetCurrentProcess();
            var text = "MyMethod. Bitness: " + IntPtr.Size + " Pid: " + process.Id + " Name: " + process.ProcessName;
            Console.WriteLine(text); // should not be displayed when running under dllhost
            OnMyEvent?.Invoke("MyEvent. " + text);
            return text;
        }
    }
}

Вот как я его регистрирую (обратите внимание, что я нацелен на 64-битный реестр):

%windir%\Microsoft.NET\Framework64\v4.0.30319\regasm.exe NetComClassLibrary3.dll /codebase /tlb

Это .reg, чтобы убедиться, что он будет работать вне процесса в dllhost.exe (guid — это guid coclass COM MyClass):

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\AppID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
"DllSurrogate" = ""

[HKEY_CLASSES_ROOT\CLSID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
"AppID" = "{31dd1263-0001-4071-aa4a-d226a55116bd}"

А вот и клиент, скомпилированный под x86:

using System;
using NetComClassLibrary3; // we can reference the .net dll as is

namespace ConsoleApp10
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Bitness: " + IntPtr.Size);
            // note we don't use new MyClass() otherwise we may go inprocess
            var type = Type.GetTypeFromCLSID(typeof(MyClass).GUID);
            var obj = (IMyClass)Activator.CreateInstance(type);

            // note I'm using the beloved dynamic keyword here. for some reason obj.OnMyEvent works but locally raises a cast error I've not investigated further...
            dynamic d = obj;
            d.OnMyEvent += (OnMyEventDelegate)((t) =>
            {
                Console.WriteLine(t);
            });
            Console.WriteLine(obj.MyMethod());
        }
    }
}

Когда я запускаю его, это вывод:

Bitness: 4 // running as 32-bit
MyEvent. MyMethod. Bitness: 8 Pid: 23780 Name: dllhost // from 64-bit world
MyMethod. Bitness: 8 Pid: 23780 Name: dllhost // from 64-bit world

Когда мы поменяем местами 32-битную и 64-битную в решении Саймона Мурье, то помимо изменения разрядности компиляции мы должны изменить 4 вещи.

(1) изменение регистрации

от

%windir%\Microsoft.NET\Framework64\v4.0.30319\regasm.exe NetComClassLibrary3.dll /codebase /tlb

к

%windir%\Microsoft.NET\Framework\v4.0.30319\regasm.exe NetComClassLibrary3.dll /codebase /tlb

(2) изменение элементов реестра

от

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\AppID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
"DllSurrogate" = ""

[HKEY_CLASSES_ROOT\CLSID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
"AppID" = "{31dd1263-0001-4071-aa4a-d226a55116bd}"

к

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Classes\AppID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
"DllSurrogate" = ""

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Classes\CLSID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
"AppID" = "{31dd1263-0001-4071-aa4a-d226a55116bd}"

(3) В 64-разрядном клиенте вместо регистрации 32-разрядной библиотеки NetComClassLibrary3.dll скопируйте определения IMyClass и OnMyEventDelegate в исходный код клиента.

(4) Также в клиенте,

изменять

var type = Type.GetTypeFromCLSID(typeof(MyClass).GUID);

к

var type = Type.GetTypeFromProgID("NetComClassLibrary3.MyClass");

поэтому клиент будет выглядеть так:

using System;
// removed by mma - using NetComClassLibrary3; // we can reference the .net dll as is

namespace ConsoleApp10
{
    // inserted by mma:
    [System.Runtime.InteropServices.Guid("31dd1263-0002-4071-aa4a-d226a55116bd")]
    public interface IMyClass
    {
        event OnMyEventDelegate OnMyEvent;
        object MyMethod();
    }
    [System.Runtime.InteropServices.Guid("31dd1263-0002-4071-aa4a-d226a55116bd")]
    public delegate void OnMyEventDelegate(string text);
    // end of insertion

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Bitness: " + IntPtr.Size);
            // note we don't use new MyClass() otherwise we may go inprocess
            // removed by mma var type = Type.GetTypeFromCLSID(typeof(MyClass).GUID);
            // inserted by mma:
            var type = Type.GetTypeFromProgID("NetComClassLibrary3.MyClass");
            // end of insertion
            var obj = (IMyClass)Activator.CreateInstance(type);

            // note I'm using the beloved dynamic keyword here. for some reason obj.OnMyEvent works but locally raises a cast error I've not investigated further...
            dynamic d = obj;
            d.OnMyEvent += (OnMyEventDelegate)((t) =>
            {
                Console.WriteLine(t);
            });
            Console.WriteLine(obj.MyMethod());
        }
    }
}

и поэтому вывод изменится с

Bitness: 4 // running as 32-bit
MyEvent. MyMethod. Bitness: 8 Pid: 23780 Name: dllhost // from 64-bit world
MyMethod. Bitness: 8 Pid: 23780 Name: dllhost // from 64-bit world

к

Bitness: 8 // running as 64-bit
MyEvent. MyMethod. Bitness: 4 Pid: 56140 Name: dllhost // from 32-bit world
MyMethod. Bitness: 4 Pid: 56140 Name: dllhost // from 32-bit world

Примечание Добавление определений IMyClass и OnMyEventDelegate в исходный код клиента вместо регистрации 32-разрядной библиотеки NetComClassLibrary3.dll работает также на версии 32-разрядного клиента + 64-разрядного COM-сервера, но ссылка на 32-разрядную COM-dll в версии 64 -bit клиент приводит к исключению BadImageFormat.

Другие вопросы по теме