在通过 P/Invoke 获取的 C++ 结构上设置 C# 回调
Set C# callback on a C++ struct obtained via P/Invoke
我正在尝试使用已提供的外部 C++ dll(我没有源代码)。
DLL 有一个函数,returns 一个指向 struct
的指针。
struct
定义了一系列函数指针,供我的应用程序用作回调。
根据我收到的 "documentation",我只是 "register" 通过将指针设置为我自己的回调方法来进行回调,如下所示:
server->OnConnectionRequest = &myObj.OnConnectionRequest;
不过,我正试图在 C# 中实现这一点。
我已经部分成功了。我可以:
- 加载DLL;
- 从函数中获取
struct*
指针;
- 调用对象上预定义的一些方法。
我不能做的是在对象上设置自己的回调:编译器不报错,运行时间不报错,但回调仍然没有被调用。
我这样定义了委托类型和 class(请注意,之前它被定义为一个结构,但它的工作方式并不相同):
// Original C++ signature from the header:
// void (*OnRemoteConnectionRequest)(void *caller, const char *ip, int &accept);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void OnRemoteConnectionRequestDelegate(IntPtr caller, string ip, ref int accept);
[StructLayout(LayoutKind.Sequential)]
public class RemoteServerPluginI
{
public OnRemoteConnectionRequestDelegate OnRemoteConnectionRequest;
// another dozen callbacks omitted
}
我有一个静态助手来从 dll 中检索实例:
public static class RemoteControlPlugin
{
[DllImport("data/remoteplugin.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr GetServerPluginInterface();
private static RemoteServerPluginI _instance = null;
public static RemoteServerPluginI Instance
{
get
{
if (_instance != null)
return _instance;
var ptr = GetServerPluginInterface();
_instance = Marshal.PtrToStructure<RemoteServerPluginI>(ptr);
if (_instance == null)
throw new InvalidOperationException("Could not obtain the server instance");
return _instance;
}
}
}
最后,这是我用来注册回调的方式:
public class CallBacks
{
public CallBacks(RemoteServerPluginI server)
{
server.OnRemoteConnectionRequest = this.OnRemoteConnectionRequest;
}
public void OnRemoteConnectionRequest(IntPtr caller, string ip, ref int accept)
{
Console.WriteLine($"Remote connection request from {ip}");
// I try to force a reject to see an error on the client,
// but the client always connects successfully, implying
// we never get to run this
accept = 0;
}
}
static void Main()
{
var cb = new Callbacks(RemoteControlPlugin.Instance);
RemoteControlPlugin.Instance.StartServer();
}
然而,当我使用客户端应用程序尝试连接到我的服务器时,我的回调永远不会 运行。如您所见,在我的回调中我拒绝连接,因此客户端应该退出并出现错误,但事实并非如此。
我做错了什么?
这个:
_instance = Marshal.PtrToStructure<RemoteServerPluginI>(ptr);
将创建 RemoteServerPluginI
的副本,因此您将处理该副本。明显错了。
使用Marshal.WriteIntPtr()
直接写入ptr
,如:
Marshal.WriteIntPtr(remoteServerPluginIPtr, 0, Marshal.GetFunctionPointerForDelegate(OnRemoteConnectionRequest));
您应该将委托指针的偏移量放在 struct
中而不是 0
。
那么你没有向我们展示回调的 C 签名......也许你甚至在那里犯了一些错误。
正如 Voigt 所写,另一个 非常 重要的事情是委托必须一直保持活动状态,以便本机库可以使用它。标准的做法是将它放在一个对象的 field/property 中,然后确保该对象保持活动状态(例如保持对它的引用)。您正在使用 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
对象。
我正在尝试使用已提供的外部 C++ dll(我没有源代码)。
DLL 有一个函数,returns 一个指向 struct
的指针。
struct
定义了一系列函数指针,供我的应用程序用作回调。
根据我收到的 "documentation",我只是 "register" 通过将指针设置为我自己的回调方法来进行回调,如下所示:
server->OnConnectionRequest = &myObj.OnConnectionRequest;
不过,我正试图在 C# 中实现这一点。 我已经部分成功了。我可以:
- 加载DLL;
- 从函数中获取
struct*
指针; - 调用对象上预定义的一些方法。
我不能做的是在对象上设置自己的回调:编译器不报错,运行时间不报错,但回调仍然没有被调用。
我这样定义了委托类型和 class(请注意,之前它被定义为一个结构,但它的工作方式并不相同):
// Original C++ signature from the header:
// void (*OnRemoteConnectionRequest)(void *caller, const char *ip, int &accept);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void OnRemoteConnectionRequestDelegate(IntPtr caller, string ip, ref int accept);
[StructLayout(LayoutKind.Sequential)]
public class RemoteServerPluginI
{
public OnRemoteConnectionRequestDelegate OnRemoteConnectionRequest;
// another dozen callbacks omitted
}
我有一个静态助手来从 dll 中检索实例:
public static class RemoteControlPlugin
{
[DllImport("data/remoteplugin.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr GetServerPluginInterface();
private static RemoteServerPluginI _instance = null;
public static RemoteServerPluginI Instance
{
get
{
if (_instance != null)
return _instance;
var ptr = GetServerPluginInterface();
_instance = Marshal.PtrToStructure<RemoteServerPluginI>(ptr);
if (_instance == null)
throw new InvalidOperationException("Could not obtain the server instance");
return _instance;
}
}
}
最后,这是我用来注册回调的方式:
public class CallBacks
{
public CallBacks(RemoteServerPluginI server)
{
server.OnRemoteConnectionRequest = this.OnRemoteConnectionRequest;
}
public void OnRemoteConnectionRequest(IntPtr caller, string ip, ref int accept)
{
Console.WriteLine($"Remote connection request from {ip}");
// I try to force a reject to see an error on the client,
// but the client always connects successfully, implying
// we never get to run this
accept = 0;
}
}
static void Main()
{
var cb = new Callbacks(RemoteControlPlugin.Instance);
RemoteControlPlugin.Instance.StartServer();
}
然而,当我使用客户端应用程序尝试连接到我的服务器时,我的回调永远不会 运行。如您所见,在我的回调中我拒绝连接,因此客户端应该退出并出现错误,但事实并非如此。
我做错了什么?
这个:
_instance = Marshal.PtrToStructure<RemoteServerPluginI>(ptr);
将创建 RemoteServerPluginI
的副本,因此您将处理该副本。明显错了。
使用Marshal.WriteIntPtr()
直接写入ptr
,如:
Marshal.WriteIntPtr(remoteServerPluginIPtr, 0, Marshal.GetFunctionPointerForDelegate(OnRemoteConnectionRequest));
您应该将委托指针的偏移量放在 struct
中而不是 0
。
那么你没有向我们展示回调的 C 签名......也许你甚至在那里犯了一些错误。
正如 Voigt 所写,另一个 非常 重要的事情是委托必须一直保持活动状态,以便本机库可以使用它。标准的做法是将它放在一个对象的 field/property 中,然后确保该对象保持活动状态(例如保持对它的引用)。您正在使用 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
对象。