【问题标题】:Passing a struct pointer in C# interop results in NULL在 C# 互操作中传递结构指针会导致 NULL
【发布时间】:2018-03-30 04:16:37
【问题描述】:

我正在构建一个用于非托管环境的托管 DLL(C/C++ 应用程序 - FreeRDP)。互操作在大多数情况下都可以正常工作,但在一个特殊情况下,我无法将指针传递给 struct。 在 API 中,我有一个结构:

typedef struct _IWTSListenerCallback IWTSListenerCallback;
struct _IWTSListenerCallback
{
    UINT(*OnNewChannelConnection)(IWTSListenerCallback* pListenerCallback,
                              IWTSVirtualChannel* pChannel,
                              BYTE* Data,
                              BOOL* pbAccept,
                              IWTSVirtualChannelCallback** ppCallback);
};

以及我正在调用的函数:

UINT(*CreateListener)(IWTSVirtualChannelManager* pChannelMgr,
                      const char* pszChannelName,
                      ULONG ulFlags,
                      IWTSListenerCallback* pListenerCallback);

都翻译成C#:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate uint ListenerCallbackNewConnectionDelegate(IntPtr listenerCallback, IntPtr channel, [MarshalAs(UnmanagedType.LPArray)] byte[] data, IntPtr accept, ref IntPtr channelCallback);

[StructLayout(LayoutKind.Sequential)]
public struct IWTSListenerCallback
{
    [MarshalAs(UnmanagedType.FunctionPtr)]
    public ListenerCallbackNewConnectionDelegate OnNewChannelConnection;
}

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate uint ChannelManagerCreateListenerDelegate(IntPtr channelManager, [MarshalAs(UnmanagedType.LPStr)] string channelName, ulong flags, IntPtr listenerCallback);

[MarshalAs(UnmanagedType.FunctionPtr)]
public ChannelManagerCreateListenerDelegate CreateListener;

及执行代码:

var callback = new IWTSListenerCallback();
callback.OnNewChannelConnection = NewChannelConnection;
var pCallback = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IWTSListenerCallback)));
Marshal.StructureToPtr(callback, pCallback, false);
var ret = channelManager.CreateListener(pChannelManager, "TestChannel", 0, pCallback);

虽然 pChannelManager(这是我从调用我的 DLL 的非托管代码中获得的指针)和字符串发送时没有任何问题,但我在此处创建的指针 (pCallback) 在 C# 中已成功分配,但它会导致 NULL在非托管代码中。

我认为问题在于我如何定义结构或如何定义函数(尽管该函数在非托管代码中被成功调用)。我使用该方法以与 DLL 的另一部分完全相同的方式创建指针,并且当传递给非托管函数时它在那里工作得非常好。

编辑: 由@jdweng 建议:

[StructLayout(LayoutKind.Sequential)]
public struct TestCall
{
    public IntPtr channelManager;
    [MarshalAs(UnmanagedType.LPStr)]
    public string channelName;
    public ulong flags;
    public IntPtr listenerCallback;
}

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate uint ChannelManagerCreateListenerDelegate(IntPtr testStructure);

var test = new TestCall();
test.channelManager = pChannelManager;
test.channelName = "TestChannel";
test.flags = 0;
test.listenerCallback = pCallback;
var pTest = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FreeRDPTypes.TestCall)));
Marshal.StructureToPtr(test, pTest, false);
var ret = channelManager.CreateListener(pTest);

没用。

EDIT2:解决方法!仅当您有权访问原始非托管代码时。我重新排列了函数参数,所以结构指针在前,如下所示:

UINT(*CreateListener)(IWTSVirtualChannelManager* pChannelMgr,
                      IWTSListenerCallback* pListenerCallback,
                      const char* pszChannelName,
                      ULONG ulFlags);

而且它有效!可能是偏移的问题。

【问题讨论】:

  • 您需要一个用于 OnNewChannelConnection 的 c# 类。回调将返回一个指向结构的指针。你应该指定c语言参数调用默认是windows调用约定。在 c 语言中,参数列表以相反的顺序被压入堆栈。这将从结构顺序倒退。为了让您的代码正常工作,我认为您需要更改参数列表的顺序。
  • @jdweng 我不确定我是否理解正确,但方法是使用 __cdecl 调用约定(通过代表)指定的。此外,所有其他参数都没有任何失真,只有这一个指针。
  • 我错过了 cdecl。对不起。 ListenerCallbackNewConnectionDelegate 方法是唯一传递参数列表的方法。结构具有按声明顺序排列的元素。调用函数时的参数列表也会按参数的顺序创建一个结构体。堆栈从内存顶部开始并向下移动,因此参数以与结构体相同的顺序出现(我之前错了,因为我忘记了堆栈开始在内存顶部)。问题是当函数返回时,这些值在堆栈上会丢失。打电话时我会工作,但回来时我不会工作。
  • 答案是为 CreateListener 创建一个 c# 结构,并将指针传递给这个结构。然后去掉 ListenerCallbackNewConnectionDelegate 的参数列表,只传递结构指针。
  • @jdweng 如果这是您的建议,请参阅问题中的编辑。如果是,那么不幸的是,这会导致非托管代码中的所有参数出现垃圾值。

标签: c# pointers interop unmanaged


【解决方案1】:

这是一个偏移量的问题。 C/C++ ULONGtypedef unsigned long 我错误地认为对应于 C# ulong,但实际上第一个在 Visual 中是 4 个字节,而另一个是 8 个字节,这导致了 4 个字节的偏移。通过将ulong 更改为uint 并添加[MarshalAs(UnmanagedType.U4)] 进行修复以进行良好的测量。我在 C# 中调用的函数的最终外观:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate uint ChannelManagerCreateListenerDelegate(IntPtr channelManager, [MarshalAs(UnmanagedType.LPStr)] string channelName, [MarshalAs(UnmanagedType.U4)] uint flags, IntPtr listenerCallback);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-09-27
    • 2015-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-14
    • 1970-01-01
    • 2011-08-18
    相关资源
    最近更新 更多