#c# #c #interop #pinvoke
#c# #c #взаимодействие #pinvoke
Вопрос:
Я пишу оболочку C # для собственной библиотеки. Он содержит эту функцию обратного вызова:
typedef application_event_result(*application_event_ptr)(application_request* request);
Параметр определяется как таковой:
typedef struct {
uint32_t query;
const char* client;
bool isAuthenticated;
bool isGuest;
} application_request;
Я определил делегат обратного вызова C # следующим образом:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate application_event_result application_event([MarshalAs(UnmanagedType.Struct)]
ref application_request request);
Структура в C#:
[StructLayout(LayoutKind.Sequential)]
public struct application_request
{
public UInt32 query;
[MarshalAs(UnmanagedType.LPStr)]
public string client;
[MarshalAs(UnmanagedType.I1)]
public bool isAuthenticated;
[MarshalAs(UnmanagedType.I1)]
public bool isGuest;
}
Кажется, все это работает. Запускается обратный вызов в C #, и члены структуры имеют ожидаемые значения.
Но при возврате к машинному коду возникает исключение повреждения кучи (0xc0000374).
Очевидно, я хотел бы избежать этого.
Если я изменю сигнатуру обратного вызова C #, чтобы использовать IntPtr вместо параметра «ref application_request», а затем маршалирую его вручную, используя следующий код, он работает.
var request = Marshal.PtrToStructure<application_request>(requestptr);
Но я хотел бы, чтобы подпись была как можно более точной, и мне не нужно было самому использовать маршалер.
Могу ли я изменить подпись делегата обратного вызова, чтобы .net мог автоматически преобразовать структуру?
Комментарии:
1. Ошибка возникает из-за несоответствия размера возвращаемого параметра. IntPtr равен четырем байтам, поэтому вы должны объявить в c # возвращаемый параметр как четыре байта. Расположение указателя также должно находиться в пространстве статической памяти, чтобы код c и C # работал вместе. IntPtr будет работать, поскольку он находится в неуправляемом пространстве статической памяти. Объект c # управляется, который выдаст ошибку, если поместить его в неуправляемое пространство.
2. Проблема не в возвращаемом значении, а во входном параметре pointer-to-struct . Есть ли способ пометить это, чтобы оно помещалось в неуправляемое пространство?
3. @jdweng возвращаемое значение выглядит как перечисление, нет?
4. Возвращаемое значение — это перечисление. Извините, это было непонятно.
Ответ №1:
Ваша проблема заключается в char*
члене struct
. Маршалер C # предполагает, что он отвечает за освобождение этой памяти. Он делает это путем вызова CoTaskMemFree
. Я думаю, довольно ясно, что память вообще не предназначена для уничтожения кодом C #.
Маршалируйте этот элемент как IntPtr
вместо:
[StructLayout(LayoutKind.Sequential)]
public struct application_request
{
public UInt32 query;
public IntPtr client;
[MarshalAs(UnmanagedType.I1)]
public bool isAuthenticated;
[MarshalAs(UnmanagedType.I1)]
public bool isGuest;
}
Внутри вашего метода обратного вызова вы можете прочитать значение строки, вызвав Marshal.PtrToStringAnsi
.
Вы завершаете это, делая элементы закрытыми и предоставляя их через свойства. Это позволит вам инкапсулировать преобразование из указателя в строку.
Комментарии:
1. Потрясающе, это работает. И спасибо за хорошую идею обернуть IntPtr в свойство, которое выполняет преобразование: общедоступная строка Client => Marshal . PtrToStringAnsi(клиент); Я не знал, что свойства struct могут быть частными, и маршалер C # все равно может их найти.