Обработка событий при вызове сторонней 32-битной DLL из 64-битного приложения c # WinForms - PullRequest
2 голосов
/ 08 апреля 2019

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

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

Например,

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

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

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

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

Ответы [ 2 ]

3 голосов
/ 09 апреля 2019

Вот пример с 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 COM-класса MyClass COM):

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
0 голосов
/ 10 апреля 2019

Когда мы меняем роль 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-библиотеки в 64-битном клиенте возникает исключение BadImageFormat.

...