Комментарии 27
Обратите внимание, что здесь явно указаны размеры типов данных и конвенции вызовов. Поскольку мы взаимодействуем с другим языком, нам приходится это знать, и правила написания портируемого кода на С++ здесь не работают. Зато, в отличие от типов вроде size_t, мы всегда знаем, какому типу на C# фиксированного размера он соответствует.
Для size_t
используйте в шарповых декларациях IntPtr
. Он соответствует размеру указателя на целевой платформе.
Если у вас предполагается кроссплатформенность, то смысл городить огород с stdcall и с подчёркиваниями перед именами? Если выставить cdecl, то никаких проблем на никсах не возникнет.
И вообще, подход будем справедлив и переходе на другие архитектуры процессоров (мы же доживём до того счастливого момента, когда ARM-ы и прочие Байкалы будут поддерживаться в дотнере полном объёме?).
Не будет, сейчас принято писать имя библиотеки как есть и класть в nuget-пакет версии оной под поддерживаемые платформы. В дальнейшем загрузчик CoreCLR и dotnet publish
сами разбираются, какую версию использовать.
Если так хочется это разруливать в рантайме — дёргайте вручную LoadLibrary
/GetProcAddress
/dlopen
/dlsym
и Marshal.GetDelegateFromFunctionPointer
вместо ваших тонн копипасты.
Если так хочется это разруливать в рантайме — дёргайте вручную LoadLibrary/GetProcAddress/dlopen/dlsym и Marshal.GetDelegateFromFunctionPointer вместо ваших тонн копипасты.
Копипаста появится в другом месте, и читабельности явно не добавится.
https://github.com/AvaloniaUI/Avalonia/blob/master/src/Gtk/Avalonia.Gtk3/Interop/Native.cs#L23 — покажите мне тут прибавившуюся копипасту.
А вы где-то видите колбеки?
Загрузка полей класса Native через рефлексию идёт?
https://github.com/AvaloniaUI/Avalonia/blob/master/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs#L123
https://github.com/AvaloniaUI/Avalonia/blob/master/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs
Всё под MIT-лицензией, можно забирать и использовать.
Чтобы такого не происходило, нужно увеличить время жизни делегата, либо сохранив его как поле в объекте, либо каким-то иным образом, в зависимости от обстоятельств.
Чтобы такого не происходило, в сишных библиотеках используется паттерн вида:
typedef void(* callback_func)(void*);
void foo(callback_func* func, void* user_data)
При использовании оных из C# callback_func является статической функцией, делегат к которой аллоцируется в единственном экземпляре. В user_data в дальнейшем передаётся свежеаллоцированый GCHandle, который в этой статической функции разименуется. Это даёт возможность чёткого контроля за временем жизни объекта в нативной среде.
вот проектик github.com/flaksirus/CrossPlatformLibraryLoader
Чтобы такого не происходило, нужно увеличить время жизни делегата, либо сохранив его как поле в объекте, либо каким-то иным образом, в зависимости от обстоятельств.
Мне попадалась информация, что сохранение как поле в объекте не гарантирует что он(делегат) не будет перемещен в памяти.
Я делал так:
private GCHandle gch_ppp_output;
internal void SetupCallbacksPPP(ppp_output_d ppp_output)
{
CleanUpPPP();
gch_ppp_output = GCHandle.Alloc(ppp_output);
var p_ppp_output = Marshal.GetFunctionPointerForDelegate(ppp_output);
//здесь можно передать нативной функции значение p_ppp_output для callback
}
//не забываем освободить
internal void CleanUpPPP()
{
if (gch_ppp_output != null && gch_ppp_output.IsAllocated)
gch_ppp_output.Free();
}
Простой GCHandle.Alloc
тоже не защищает от перемещения в памяти; его имеет смысл использовать только если вы полностью передаете владение управляемым объектом неуправляемому коду. В противном случае он ничем не лучше статического поля.
Но в данном случае это не требуется. Ведь перемещается же делегат в управляемой куче — а исполнение кода в ней запрещено. Следовательно, при маршалинге для делегата генерируется код-заглушка в совершенно другой области памяти, где нет никакого сборщика мусора и перемещения объектов. Все что требуется от управляемого кода — удержать в памяти сам делегат достаточно долго.
Я чуть не рехнулся, пока с++ dll прикрутил к C#!
На самом деле, после разборок с вызовом функции из dll наступает неприятность с передачей структур. Вот об этом еще напиши.
А что не так с передачей структур? Там самое нудное — объявлять одинаковые структуры в двух языках, но не сказал бы что это прямо уж так сложно...
Передаются структуры как ref
или out
, можно еще как IntPtr
передавать (с ручным копированием).
Взаимодействие C# и C++ кроссплатформенно