Собственный обратный вызов C # с указателем на параметр структуры

#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 # все равно может их найти.