Иногда есть желание использовать из C# код на плюсах (т.е. построенный на классах). Традиционно варианта три:
- использовать С++/CLI, чтобы завернуть native классы в классы CLR.
- Сделать (вручную или с помощью SWIG) обертку вокруг классов, и использовать P/Invoke для доступа к ним.
- оформить классы в виде COM-объектов.
Первый вариант напряжен тем, что требует наличия компилятора Visual C++ (реализации для mono, например, нет). Кроме того, C++/CLI – язык специфичный, ему нужно учиться (я как-то огреб утечку памяти, не разобравшись в разнице между !Class() и ~Class()).
Второй проблемен уже тем, что на выходе вы получите библиотеку с процедурами, оперирующими хендлами объектов, и, скорее всего, вам придется заново строить объектную модель вокруг всего этого. Удовольствие то еще.
Недостаток третьего подхода в том, что COM, вообще говоря, штука тяжелая, со своими требованиями к оформлению кода, вдобавок, обычно требующая регистрации в реестре (хотя это можно обойти). Достоинство – прямое отображение COM-интерфейсов на интерфейсы .NET.
Вот тут я прочитал про интересный подход, “скрещивающий” второй и третий варианты взаимодействия. Принцип следующий - С++ объект допиливается так, чтобы реализовывать IUnknown (т.е. иметь в начале vtable методы QueryInterface, AddRef, Release), но создается посредством фабричной функции, экспортированной из DLL.
Выглядит это примерно так (я разбил код на два класса, один из которых инкапсулирует работу COM, а другой – полезную логику). Обратите внимание, что мы накручиваем счетчик ссылок каждый раз, когда QueryInterface срабатывает успешно, а когда счетчик ссылок уменьшается до нуля, мы освобождаем память.
Также обратите внимание, что сигнатура метода ComputePi() имеет привычный вид, а не безобразие типа
HRESULT __stdcall ComputePi(float* pResult);
Клиентский код выглядит ещё проще. Обратите внимание, что для поддержки не-COM-овской сигнатуры на методе выставлен атрибут [PreserveSig]. Значение атрибута [Guid] должно совпадать с GUID-ом, обрабатываемым в QueryInterface.
При загрузке .NET создаст прокси, называемый RCW (runtime callable wrapper), вызовет QueryInterface для заданного GUID’а, после чего станет возможным работать RCW как с обычным объектом.
Достоинства:
- минимум кода пишется с обоих сторон
- никакого реестра, регистрации и пр.
- кроссплафтоменно (работает и в MS.NET и в mono). Максимум – придется описать самому GUID для IUnknown.
Недостатки:
- Обычный С++ класс (не содержащий в начале vtable методы IUnknown) так использовать нельзя – система тупо рассчитывает на то, что vtable имеет заявленную структуру. На крайний случай, вы можете делегировать вызовы своему объекту.
- В стандартном COM-е коды HRESULT используются для сообщений об ошибках. При interop-е по ним строятся исключения .NET. Здесь такая схема не работает, нужно использовать свой механизм сигнализации об ошибках. Исключения С++, по крайней мере, в случае MinGW, приводят к завершению работы программы (думаю, в случае VC++ они могут сработать, но не тестил).
- Сложное межобъектное взаимодействие (передачу объектов в виде параметров) делать таким образом можно задолбаться. Но это общее место любого interop-а.