У нас есть старое 32-разрядное решение Visual Studio C# Windows Forms, которое мы хотим теперь скомпилировать в 64-разрядном формате. К сожалению, наше приложение использует некоторые внешние dll-ки (для сканеров, камер и т.д.), которые доступны только в 32-битной версии. Доступ к 32-битным DLL из 64-битного кода не является простым, особенно когда мы хотим обрабатывать также события, вызванные этими dll-ами. Наших знаний в этой области недостаточно для создания реализации на основе этой статьи, поэтому мы ищем более подробные инструкции или примеры.
Наша первая попытка была основана на эта статья. Мы обернули сторонние DLL-файлы в 32-битный COM-сервер с поздней привязкой и использовали его из нашего 64-битного приложения, как описано здесь (с соответствующими изменениями, потому что нам пришлось поменять местами роли 32-битной и 64-битной) . Эта попытка была успешной, но неполной, поскольку это решение не доставляет события с COM-сервера на 64-разрядный клиент. Итак, мы начали сосредотачиваться на событиях. Мы нашли множество статей и примеров, касающихся использования событий, вызванных COM-объектом, но ни одна из них не дает нам полного решения. Часть исходников имеет дело исключительно с клиентом, либо исключительно с сервером, но они несовместимы ни друг с другом, ни с нашей средой (WinForm, c#).
Например,
Возможно, некоторые из них мы можем использовать с некоторым усилием, но какие и как?
Редактировать
Сейчас я склоняюсь к гибридному решению: моя новейшая идея обратной связи от 32-битного COM-объекта к вызывающему 64-битному приложению состоит в том, чтобы поместить сервер именованного канала в 64-битное приложение, а клиент именованного канала — в COM-объект и каждый раз, когда в COM-объекте возникает событие, он отправляет сообщение Named Pipe на сервер Named Pipe. Код, который я нашел для этого, доступен здесь (проекты CSNamedPipeServer и CSNamedPipeClient).
Разве вы не задавали этот вопрос несколько дней назад (и удалили его вместе с комментариями)?
@SimonMourier Почти. Проблема та же, но вопрос теперь другой и (надеюсь) более проработанный.
Я могу предоставить образец C# с 64-битным COM-сервером с событием и 32-битным COM-клиентом, которые работают, если хотите.
@SimonMourier Круто! Я с нетерпением жду вашего решения.
Вот пример с 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.
Генерирует ли эта DLL визуальные компоненты или просто генерирует события обратно в ваш код? 32-разрядная => 64-разрядная версия - это куча червей (как вы уже обнаружили), поскольку она требует от вас пересечения границ процесса (32-разрядная версия - это отдельная виртуальная среда в 64-разрядных окнах). Пробовали ли вы 32-битную оболочку на C#, говорящую вне процесса вашей 64-битной программе?