Страницы

Поиск по вопросам

суббота, 21 декабря 2019 г.

Как подружить C++ и C# через COM наиболее простым способом?

#c_sharp #cpp #net #visual_studio #com


Все рецепты использования COM включают в себя создание библиотеки типов, ее регистрацию
в реестре, регистрацию в реестре сервера...

В DLL-ке при этом надо создавать фабрику объектов.

Если есть исполнимый файл на C# и DLL на C++ - можно ли обойтись безо всех этих шагов
и просто загрузить dll-ку?
    


Ответы

Ответ 1



Да, можно. Технология COM не требует для своего использования какой бы то ни было регистрации в реестре! Все перечисленные в вопросе шаги нужны для уменьшения связности модулей. Если вам эти шаги не подходят, не нужны или вам просто лень их делать - их можно пропустить. Вот работающий минимальный пример. C# using System; using System.Runtime.InteropServices; [ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface ICallback { void execute(); } class Program : ICallback { static void Main(string[] args) { SetCallbacks(new Program()); } [DllImport("mylib", CallingConvention=CallingConvention.StdCall)] static extern void SetCallbacks(ICallback callback); void ICallback.execute() { Console.WriteLine("Hello, world!"); } } С++ #include "windows.h" interface ICallback : IUnknown { virtual HRESULT __stdcall execute() = 0; }; extern "C" __declspec(dllexport) void __stdcall SetCallbacks(ICallback *cb) { cb->execute(); } В примере выше интерфейсы на двух языках были составлены независимо. В принципе, это является двойной работой - поэтому имеет смысл составить интерфейс только 1 раз, после чего его импортировать. Если интерфейс составляется в C++-проекте на языке IDL - то достаточно потом подключить библиотеку типов в C#-проект как зависимость. Регистрировать ее при этом не обязательно! Если интерфейс составляется в C#-проекте, то надо обязательно поставить ему кроме перечисленных выше атрибут Guid и сделать его публичным. После этого можно вытащить библиотеку типов через tlbexp и подключить в C++-проект. Регистрировать библиотеку типов в реестре, опять-таки, не требуется.

Ответ 2



Добавлю про возможность взаимодействие С++ кода с .Net Core. В .Net Core есть возможность вызвать статические .Net методы через использование coreclr.dll из натива. Посмотреть примеры и ссылки можно здесь Кроссплатформенное использование классов .Net из неуправляемого кода. Или аналог IDispatch на Linux В итоге можно передавать нативные методы в управляемый код и обратно. Но меня заинтересовало использование объектов и виртуальные методы. Поэтому задал вопрос и получил ответ .Net Core Вызов виртуальных методов нативных объектов При этом на 1 вызов метода идет куча QueryInterface, AddRef и Release это нужно учитывать Я приведу свой пример. Опишем класс на С++ struct ICallback : IUnknown { public: // Унаследовано через IUnknown virtual HRESULT __stdcall QueryInterface(REFIID riid, void ** ppvObject) override; virtual ULONG __stdcall AddRef(void) override; virtual ULONG __stdcall Release(void) override; virtual HRESULT __stdcall execute(int value); }; typedef void(STDMETHODCALLTYPE *ManagedRunCallback)(ICallback*); реализация // {FFB46654-083E-486A-94B8-E28B5C01561D} static const GUID IID_ICallback = { 0xffb46654, 0x83e, 0x486a,{ 0x94, 0xb8, 0xe2, 0x8b, 0x5c, 0x1, 0x56, 0x1d } }; HRESULT __stdcall ICallback::execute(int value) { wprintf_s(L"ICallback from.Net %d\n", value); return NOERROR; } HRESULT __stdcall ICallback::QueryInterface(REFIID riid, void ** ppvObject) { if (!ppvObject) return E_INVALIDARG; if (riid == IID_IUnknown) { *ppvObject = static_cast(this); return S_OK; } else if (riid == IID_ICallback) { *ppvObject = static_cast(this); return S_OK; } *ppvObject = nullptr; return E_NOINTERFACE; } // Меня интересует только вызов виртуальных методов // без отслеживания подсчета ссылок ULONG __stdcall ICallback::AddRef(void) { return 1; } ULONG __stdcall ICallback::Release(void) { return 0; } теперь описание на C# [ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("FFB46654-083E-486A-94B8-E28B5C01561D")] public interface ICallback { void execute(int value); } и статический метод public static void CallInterface(IntPtr cb) { var cb2 = Marshal.GetObjectForIUnknown(cb) as ICallback; cb2?.execute(555); } теперь я могу вызвать .Net метод из С++ ManagedRunCallback pRunCallback; if (!CreateDelegate(domainId, L"CallInterface", (INT_PTR*)&pRunCallback)) return false; ICallback* cb = new ICallback(); pRunCallback(cb);

Ответ 3



Добавлю еще один вариант с использованием VMT в .Net Core. Вызов интерфейса очень дорогое удовольствие. С для приведение к нужному интерфейсу и подсчета ссылок идет куча вызовов QueryInterface Addref,Release Вызов методов интерфейса из C# Определим С++ класс struct TestThisCall { public: virtual int execute(int value1, int value2); }; typedef void(STDMETHODCALLTYPE *ManagedRunCallback2)(TestThisCall*); Создадим метод для получения метода в VMT public static IntPtr ПолучитьАдресВиртуальногоМетода(IntPtr Объект, int ИндексВТаблицеВиртуальныхМетодов ) { int размерIntPtr = Marshal.SizeOf(); // Первым полем объекта идет ссылка на VMT // Прочитаем её var АдресVMT = Marshal.ReadIntPtr(Объект); // получим адрес ссылки на метод по смещению в VMT var АдресМетодаVMT = АдресVMT + ИндексВТаблицеВиртуальныхМетодов * размерIntPtr; var АдресМетода = Marshal.ReadIntPtr(АдресМетодаVMT); return АдресМетода; } Теперь определим описание делегата [UnmanagedFunctionPointer(CallingConvention.ThisCall)] internal delegate int ВиртуальныйМетодОбъекта2Delegate(IntPtr self, int Число1, int Число2); И статический метод который будем вызывать из натива public static void CallTestThisCall(IntPtr ttc) { // Метод execute идет первым в VMT // Передаем индекс 0 var АдресМетода = ПолучитьАдресВиртуальногоМетода(ttc, 0); // Получим делегат по дресу var execute = Marshal.GetDelegateForFunctionPointer<ВиртуальныйМетодОбъекта2Delegate>(АдресМетода); // И вызовем метод var res= execute(ttc, ttc.ToInt32(), 777); execute(ttc, ttc.ToInt32(), res); } Теперь вызовем этот метод из C++ ManagedRunCallback2 pRunCallback2; if (!CreateDelegate(domainId, L"CallTestThisCall", (INT_PTR*)&pRunCallback2)) return false; TestThisCall* ttc = new TestThisCall(); pRunCallback2(ttc); Ну и чудесный пример на RSDN .Net Core Вызов виртуальных методов нативных объектов

Комментариев нет:

Отправить комментарий