Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Никита Цуканов "Кросс-платформенное объектно-ориентированное взаимодействие C# и C++"

Никита Цуканов "Кросс-платформенное объектно-ориентированное взаимодействие C# и C++"

При всей простоте использования технологии P/Invoke, она, увы, пригодна лишь для использования библиотек, предоставляющих функции в C-стиле. Объектно-ориентированное же взаимодействие с нативным кодом традиционно реализовывалось средствами C++/CLI, который не совместим с .NET Core (пока) и с не-Windows платформами (вообще).

С движением.NET на другие платформы возникает проблема взаимодействия с неуправляемой частью приложения, которую как-то надо решать.

В докладе будет представлено решение, которое:

- позволяет двухстороннее прозрачное взаимодействие между C++ и C# на уровне интерфейсов, которые могут реализовывать как классы C#, так и классы C++;
- обеспечивает контроль использования памяти на основе подсчёта ссылок;
- не изобретает новый велосипед, а не совсем традиционно использует давно проверенные временем и тысячами приложений технологии и принципы;
- на текущий момент успешно используется в проекте Avalonia для взаимодействия C#/C++/Objective-C без завязок на хитропропатченные рантаймы типа Xamarin.Mac.

DotNetRu

August 15, 2019
Tweet

More Decks by DotNetRu

Other Decks in Programming

Transcript

  1. 2

  2. 3

  3. 6 Легкость интеропа с C • Накидал [DllImpoDllImport] и в

    д] и в дамки • Все типы данных C представимы в C#, некоторые ещё и конвертируются автоматически (string st] и в дring -> wchar*/char*)
  4. 7 Легкость интеропа с C • Накидал [DllImpoDllImport] и в

    д] и в дамки • Все типы данных C представимы в C#, некоторые ещё и конвертируются автоматически (string st] и в дring -> wchar*/char*) • Структуры данных — это просто структуры (string понятные предсказуемые смещения всех полей)
  5. 8 Легкость интеропа с C • Накидал [DllImpoDllImport] и в

    д] и в дамки • Все типы данных C представимы в C#, некоторые ещё и конвертируются автоматически (string st] и в дring -> wchar*/char*) • Структуры данных — это просто структуры (string понятные предсказуемые смещения всех полей) • Можно спокойно вызывать любую C-функцию, а C передать любой метод, лямбду итп.
  6. 11 Проблемы интеропа с C • Управление памятью (string два

    вопроса: «кто?» и «как?») char* get_file_name(char* path);
  7. 12 Проблемы интеропа с C • Управление памятью (string два

    вопроса: «кто?» и «как?») char* get_file_name(char* path); • Управление временем жизни делегатов
  8. 13 public delegate void MyCallback(int arg); [DllImport(Consts.LibraryPath)] static extern void

    SetCCallback(MyCallback cb); SetCCallback(i => Console.WriteLine("Called: " + i));
  9. 16 public delegate void MyTargetedCallback(int arg); private static MyCallback _staticCallback

    = MyCallbackHandler; static void MyCallbackHandler(int arg, IntPtr userData) { var cb = (MyTargetedCallback) GCHandle.FromIntPtr(userData).Target; cb(arg); } public static GCHandle SetCallback(MyTargetedCallback cb) { var handle = GCHandle.Alloc(cb); SetCCallback(_staticCallback, GCHandle.ToIntPtr(handle)); return handle; }
  10. 17 public delegate void MyTargetedCallback(int arg); private static MyCallback _staticCallback

    = MyCallbackHandler; static void MyCallbackHandler(int arg, IntPtr userData) { var cb = (MyTargetedCallback) GCHandle.FromIntPtr(userData).Target; cb(arg); } public static GCHandle SetCallback(MyTargetedCallback cb) { var handle = GCHandle.Alloc(cb); SetCCallback(_staticCallback, GCHandle.ToIntPtr(handle)); return handle; }
  11. 18 public delegate void MyTargetedCallback(int arg); private static MyCallback _staticCallback

    = MyCallbackHandler; static void MyCallbackHandler(int arg, IntPtr userData) { var cb = (MyTargetedCallback) GCHandle.FromIntPtr(userData).Target; cb(arg); } public static GCHandle SetCallback(MyTargetedCallback cb) { var handle = GCHandle.Alloc(cb); SetCCallback(_staticCallback, GCHandle.ToIntPtr(handle)); return handle; }
  12. 19 public delegate void MyTargetedCallback(int arg); private static MyCallback _staticCallback

    = MyCallbackHandler; static void MyCallbackHandler(int arg, IntPtr userData) { var cb = (MyTargetedCallback) GCHandle.FromIntPtr(userData).Target; cb(arg); } public static GCHandle SetCallback(MyTargetedCallback cb) { var handle = GCHandle.Alloc(cb); SetCCallback(_staticCallback, GCHandle.ToIntPtr(handle)); return handle; }
  13. 20 public delegate void MyTargetedCallback(int arg); private static MyCallback _staticCallback

    = MyCallbackHandler; static void MyCallbackHandler(int arg, IntPtr userData) { var cb = (MyTargetedCallback) GCHandle.FromIntPtr(userData).Target; cb(arg); } public static GCHandle SetCallback(MyTargetedCallback cb) { var handle = GCHandle.Alloc(cb); SetCCallback(_staticCallback, GCHandle.ToIntPtr(handle)); return handle; }
  14. 21 public delegate void MyTargetedCallback(int arg); private static MyCallback _staticCallback

    = MyCallbackHandler; static void MyCallbackHandler(int arg, IntPtr userData) { var cb = (MyTargetedCallback) GCHandle.FromIntPtr(userData).Target; cb(arg); } public static GCHandle SetCallback(MyTargetedCallback cb) { var handle = GCHandle.Alloc(cb); SetCCallback(_staticCallback, GCHandle.ToIntPtr(handle)); return handle; }
  15. 22 public delegate void MyTargetedCallback(int arg); private static MyCallback _staticCallback

    = MyCallbackHandler; static void MyCallbackHandler(int arg, IntPtr userData) { var cb = (MyTargetedCallback) GCHandle.FromIntPtr(userData).Target; cb(arg); } public static GCHandle SetCallback(MyTargetedCallback cb) { var handle = GCHandle.Alloc(cb); SetCCallback(_staticCallback, GCHandle.ToIntPtr(handle)); return handle; }
  16. 25 Поговорим про C++ • Накидал [DllImpoDllImport] и в д]

    и • ?Test] и в д@@YAXV?$basic_st] и в дring@DU?$char_t] и в дrait] и в дs@D@st] и в дd@@V? $allocat] и в дor@D@2@@st] и в дd@@V?$set] и в д@V?$basic_st] и в дring@DU? $char_t] и в дrait] и в дs@D@st] и в дd@@V?$allocat] и в дor@D@2@@st] и в дd@@U? $less@V?$basic_st] и в дring@DU?$char_t] и в дrait] и в дs@D@st] и в дd@@V? $allocat] и в дor@D@2@@st] и в дd@@@2@V?$allocat] и в дor@V? $basic_st] и в дring@DU?$char_t] и в дrait] и в дs@D@st] и в дd@@V? $allocat] и в дor@D@2@@st] и в дd@@@2@@2@@Z? Test] и в д2@MyClass@@QAEHAAHPBD@Z
  17. 26

  18. 29 Сложности интеропа с C++ • Мэнглинг имён (string ht]

    и в дt] и в дp://demangler.com/) • Структуры не структуры
  19. 30 Сложности интеропа с C++ • Мэнглинг имён (string ht]

    и в дt] и в дp://demangler.com/) • Структуры не структуры • Разные модели наследования
  20. 31 Сложности интеропа с C++ • Мэнглинг имён (string ht]

    и в дt] и в дp://demangler.com/) • Структуры не структуры • Разные модели наследования • Разные модели управления памятью (string GC в C# vs. (string «полу»-)ручное в C++)
  21. 34 Исторически это решалось • На Windows — Managed C++,

    затем C++/CLI • На *nix — glue-код + P/Invoke
  22. 35 Исторически это решалось • На Windows — Managed C++,

    затем C++/CLI • На *nix — glue-код + P/Invoke extern "C" int call_MyClass_void_Test2_intptr_charptr (MyClass* cl, int* arg, const char* arg2) { cl->Test2(arg, arg2); }
  23. 36 Проблемы, толком не решаемые glue-кодом • в случае с

    экспортом C++-объекта в C# не всегда ясно, кто "владеет" объектом • в случае с экспортом C#-объекта в C++ всё ещё веселее
  24. 43

  25. 44

  26. 45

  27. 47 А давайте попробуем использовать нечто, что поймут как C#,

    так и C++? • Этим «нечто» будет интерфейс
  28. 53 IFoo* IFoo* class Foo IFooVTable* _field1 _field2 _field3 _field4

    ... IFooVTable for Foo void Foo::Bar(string ) void Foo::Test] и в д(string ) ...
  29. 55 Остались 2 проблемы • Не понятно, как управлять памятью

    • Не понятно, как кастовать интерфейсы
  30. 60 IUnknown struct IUnknown { virtual HRESULT STDMETHODCALLTYPE QueryInterface( REFIID

    riid, void **ppvObject) = 0; virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0; virtual ULONG STDMETHODCALLTYPE Release( void) = 0; };
  31. 62 На кой нам FORWARD_IUNKNOWN(string )? struct IBase { virtual

    void BaseMethod() = 0; }; class CBase : public IBase { public: virtual void BaseMethod() override { /* Do something */ } };
  32. 63 На кой нам FORWARD_IUNKNOWN(string )? struct IDerived : IBase

    { virtual void DerivedMethod() = 0; }; class CDerived : public IDerived, public CBase { public: virtual void DerivedMethod() override { /* Do something */ } };
  33. 66 На кой нам FORWARD_UNKNOWN(string )? struct IBase { virtual

    void BaseMethod() = 0; }; class CBase : public virtual IBase { public: virtual void BaseMethod() override { /* Do something */ } };
  34. 67 На кой нам FORWARD_IUNKNOWN(string )? struct IDerived : virtual

    IBase { virtual void DerivedMethod() = 0; }; class CDerived : public IDerived, public CBase { public: virtual void DerivedMethod() override { /* Do something */ } };
  35. 68

  36. 69 На кой нам FORWARD_IUNKNOWN(string )? class CDerived : public

    IDerived, public CBase { public: virtual void BaseMethod() override { CBase::BaseMethod(); } virtual void DerivedMethod() override { /* Do something */ } };
  37. 70 ATL делает так: template <typename Itf> class IBaseImpl :

    public Itf { public: void BaseMethod() override; }; class CBase : public IBaseImpl<IBase> {}; class CDerived : public IBaseImpl<IDerived> { public: void DerivedMethod() override; };
  38. 71 ATL делает так: IBase CBase IDerived CDerived Ист: ht]

    и в дt] и в дps://st] и в дackoverflow.com/q/42576982 IBaseImpl<IBase> IBaseImpl<IBase>
  39. 76 Проблемы встроенной поддержки COM • Неудобно освобождать unmanaged-объекты •

    Нет уведомления об ненужности managed-объекта • Не поддерживается в CoreCLR на не-Windows
  40. 77 Проблемы встроенной поддержки COM • Неудобно освобождать unmanaged-объекты •

    Нет уведомления об ненужности managed-объекта • Не поддерживается в CoreCLR на не-Windows • Тормозит
  41. 78

  42. 80 Ограничения • Нет событий, придётся работать как на Java

    (string в тот момент, когда в COM добавили события всё покатилось в трубу) • Неудобно делать иерархии наследования интерфейсов и классов в C++-коде
  43. 85 Где COM используется на *nix? • Отладчик CoreCLR (string

    libdbgshim.so/dylib) • Avalonia.Nat] и в дive (string OSX-бэкэнд)
  44. 86 Спикер Никита Цуканов nikit] и в дa.d.t] и в

    д[email protected] Telegram: kekekeks Примеры ht] и в дt] и в дps://git] и в дhub.com/kekekeks/dont] и в дext] и в д2019spb SharpGenTools ht] и в дt] и в дps://sharpgent] и в дools.readt] и в дhedocs.io/en/lat] и в дest] и в д/ ht] и в дt] и в дps://git] и в дhub.com/SharpGenTools/SharpGenTools/