Этот:
_instance = Marshal.PtrToStructure<RemoteServerPluginI>(ptr);
создаст копию RemoteServerPluginI
, поэтому вы будете работать с ней.Совершенно неверно.
Используйте Marshal.WriteIntPtr()
для прямой записи в ptr
, например:
Marshal.WriteIntPtr(remoteServerPluginIPtr, 0, Marshal.GetFunctionPointerForDelegate(OnRemoteConnectionRequest));
Где вместо 0
вы должны поместить смещение указателя делегата в struct
.
Тогда вы не показываете С-сигнатуру обратного вызова ... Возможно, вы допустили некоторые ошибки даже там.
Как написано Фойгтом, другой очень важно то, что делегат должен оставаться в живых все время, пока его может использовать нативная библиотека.Стандартный способ сделать это - поместить его в поле / свойство объекта, а затем обязательно сохранить объект (например, сохранив ссылку на него).Вы делаете это с class RemoteServerPluginI
.Еще один способ сделать это - GCHandle.Alloc(yourdelegate, GCHandleType.Normal)
, а затем GCHandle.Free()
, когда вы уверены, что нативный код не будет вызывать его когда-либо.
Несколько простых примеров кода.
C сторона:
extern "C"
{
typedef struct _RemoteServerPluginI
{
void(*OnRemoteConnectionRequest)(void *caller, wchar_t *ip, int *accept);
void(*StartServer)(void);
} RemoteServerPluginI;
void StartServer();
RemoteServerPluginI _callbacks = { NULL, StartServer };
void StartServer()
{
int accept = 0;
_callbacks.OnRemoteConnectionRequest(NULL, L"127.0.0.1", &accept);
wprintf(L"Accept: %d", accept);
}
__declspec(dllexport) RemoteServerPluginI* GetServerPluginInterface()
{
return &_callbacks;
}
}
Сторона C #:
[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr GetServerPluginInterface();
public static void RemoteConnectionRequestTest(IntPtr caller, string ip, ref int accept)
{
Console.WriteLine("C#: ip = {0}", ip);
accept = 1;
}
public class Callbacks
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void OnRemoteConnectionRequestDelegate(IntPtr caller, [MarshalAs(UnmanagedType.LPWStr)]string ip, ref int accept);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void StartServerDelegate();
public OnRemoteConnectionRequestDelegate RemoteConnectionRequest { get; set; }
public StartServerDelegate StartServer { get; set; }
}
, а затем:
IntPtr rsp = GetServerPluginInterface();
var callbacks = new Callbacks
{
RemoteConnectionRequest = RemoteConnectionRequestTest
};
Marshal.WriteIntPtr(rsp, 0, Marshal.GetFunctionPointerForDelegate(callbacks.RemoteConnectionRequest));
callbacks.StartServer = Marshal.GetDelegateForFunctionPointer<Callbacks.StartServerDelegate>(Marshal.ReadIntPtr(rsp, IntPtr.Size));
callbacks.StartServer();
Обратите внимание, что в вашем примере StartServer
является делегатом, содержащимся в RemoteServerPluginI
C структура.Поэтому мы должны получить его значение с помощью Marshal.ReadIntPtr
и создать для него делегат .NET.Обратите внимание на использование GC.KeepAlive()
, чтобы убедиться, что объект остается живым до определенной точки в коде.Другой распространенный метод - использование переменной static
(время жизни переменных static
- до конца программы)
Пример с инкапсуляцией различных Marshal
:
public class Callbacks
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void OnRemoteConnectionRequestDelegate(IntPtr caller, [MarshalAs(UnmanagedType.LPWStr)]string ip, ref int accept);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void StartServerDelegate();
private IntPtr ptr;
public static implicit operator Callbacks(IntPtr ptr)
{
return new Callbacks(ptr);
}
public Callbacks(IntPtr ptr)
{
this.ptr = ptr;
{
IntPtr del = Marshal.ReadIntPtr(ptr, 0);
if (del != IntPtr.Zero)
{
remoteConnectionRequest = Marshal.GetDelegateForFunctionPointer<OnRemoteConnectionRequestDelegate>(del);
}
}
{
IntPtr del = Marshal.ReadIntPtr(ptr, IntPtr.Size);
if (del != IntPtr.Zero)
{
startServer = Marshal.GetDelegateForFunctionPointer<StartServerDelegate>(del);
}
}
}
private OnRemoteConnectionRequestDelegate remoteConnectionRequest;
private StartServerDelegate startServer;
public OnRemoteConnectionRequestDelegate RemoteConnectionRequest
{
get => remoteConnectionRequest;
set
{
if (value != remoteConnectionRequest)
{
remoteConnectionRequest = value;
Marshal.WriteIntPtr(ptr, 0, remoteConnectionRequest != null ? Marshal.GetFunctionPointerForDelegate(remoteConnectionRequest) : IntPtr.Zero);
}
}
}
public StartServerDelegate StartServer
{
get => startServer;
set
{
if (value != startServer)
{
startServer = value;
Marshal.WriteIntPtr(ptr, IntPtr.Size, startServer != null ? Marshal.GetFunctionPointerForDelegate(startServer) : IntPtr.Zero);
}
}
}
}
а затем
Callbacks callbacks = GetServerPluginInterface();
callbacks.RemoteConnectionRequest = RemoteConnectionRequestTest;
callbacks.StartServer();
while (true)
{
}
Обратите внимание, что тогда я бы сделал все строго типизированным, полностью скрыв IntPtr
:
[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern CallbacksPtr GetServerPluginInterface();
[StructLayout(LayoutKind.Sequential)]
public struct CallbacksPtr
{
public IntPtr Ptr;
}
public class Callbacks
{
public static implicit operator Callbacks(CallbacksPtr ptr)
{
return new Callbacks(ptr.Ptr);
}
private Callbacks(IntPtr ptr)
{
...
}
Добавление CallbacksPtr
, которое является прокладкой для IntPtr
, который может быть неявно преобразован в полный Callbacks
объект.