Как стать автором
Обновить

Комментарии 27

Обратите внимание, что здесь явно указаны размеры типов данных и конвенции вызовов. Поскольку мы взаимодействуем с другим языком, нам приходится это знать, и правила написания портируемого кода на С++ здесь не работают. Зато, в отличие от типов вроде size_t, мы всегда знаем, какому типу на C# фиксированного размера он соответствует.

Для size_t используйте в шарповых декларациях IntPtr. Он соответствует размеру указателя на целевой платформе.

Если у вас предполагается кроссплатформенность, то смысл городить огород с stdcall и с подчёркиваниями перед именами? Если выставить cdecl, то никаких проблем на никсах не возникнет.

А что насчёт short, например?
И вообще, подход будем справедлив и переходе на другие архитектуры процессоров (мы же доживём до того счастливого момента, когда ARM-ы и прочие Байкалы будут поддерживаться в дотнере полном объёме?).

Не будет, сейчас принято писать имя библиотеки как есть и класть в nuget-пакет версии оной под поддерживаемые платформы. В дальнейшем загрузчик CoreCLR и dotnet publish сами разбираются, какую версию использовать.


Если так хочется это разруливать в рантайме — дёргайте вручную LoadLibrary/GetProcAddress/dlopen/dlsym и Marshal.GetDelegateFromFunctionPointer вместо ваших тонн копипасты.

Осталось дождаться только, когда на них можно будет создавать полноценные UI для desktop-приложений.
Я знаю и смиренно жду.

Что касается использования на десктопном фреймворке — это тоже решается средствами NuGet и MSBuild. Рекомендую посмотреть в сторону SkiaSharp, где не прибегают к подобного рода "ухищрениям" с несколькими декларациями.

Посмотрю, спасибо.
Если так хочется это разруливать в рантайме — дёргайте вручную LoadLibrary/GetProcAddress/dlopen/dlsym и Marshal.GetDelegateFromFunctionPointer вместо ваших тонн копипасты.

Копипаста появится в другом месте, и читабельности явно не добавится.
Разве я что-то говорил про копипасту в коллбэках?

А вы где-то видите колбеки?

Тупанул.
Загрузка полей класса Native через рефлексию идёт?
Чтобы такого не происходило, нужно увеличить время жизни делегата, либо сохранив его как поле в объекте, либо каким-то иным образом, в зависимости от обстоятельств.

Чтобы такого не происходило, в сишных библиотеках используется паттерн вида:


typedef void(* callback_func)(void*);
void foo(callback_func* func, void* user_data)

При использовании оных из C# callback_func является статической функцией, делегат к которой аллоцируется в единственном экземпляре. В user_data в дальнейшем передаётся свежеаллоцированый GCHandle, который в этой статической функции разименуется. Это даёт возможность чёткого контроля за временем жизни объекта в нативной среде.

В целом — согласен, но представим себе, что библиотека уже существует и ковырять её дорого и/или геморройно.
Чтобы такого не происходило, нужно увеличить время жизни делегата, либо сохранив его как поле в объекте, либо каким-то иным образом, в зависимости от обстоятельств.

Мне попадалась информация, что сохранение как поле в объекте не гарантирует что он(делегат) не будет перемещен в памяти.
Я делал так:
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 передавать (с ручным копированием).

Со структурами проблем нет, надо маппить правильно и не забывать обвешивать атрибутом StructLayout со стороны .NET-а, а ещё учитывать выравнивание (или бороться с ним) со стороны Си.
можно idl использовать, тогда объявление будет одно.
А он вообще кроссплатформенный? Что-то не гуглятся утилиты под линукс…
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.