2017-10-18 109 views
-1

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

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); 

和它的作品!可能是一个抵消的问题。

+0

您需要OnNewChannelConnection的c#类。回调将返回一个指向结构的指针。你应该指定c语言参数调用,默认是windows调用约定。在c语言中,参数列表按相反的顺序推入堆栈。这将从结构顺序倒退。为了您的代码工作,我认为您需要更改参数列表的顺序。 – jdweng

+0

@jdweng我不知道我的理解是否正确,但方法是用__cdecl调用约定(通过代表)指定的。另外所有其他参数都没有任何失真,只有这一个指针。 –

+0

我错过了cdecl。抱歉。 ListenerCallbackNewConnectionDelegate方法是传递参数列表的唯一方法。结构具有按声明顺序的元素。调用函数时的参数列表也会按照参数的顺序创建一个结构。堆栈从内存顶部开始向下移动,以便参数以与结构相同的顺序出现(因为我忘记了堆栈开始时我错误了在内存顶部)。问题是函数返回时,这些值在堆栈上丢失。我会在打电话时工作,但返回时不工作。 – jdweng

回答

0

这是一个抵消的问题。 C/C++ ULONGtypedef unsigned long,我错误地认为它对应于C#ulong,但实际上第一个是Visual中的4个字节,而另一个是8个字节,这导致了4个字节的偏移量。通过更改ulonguint并添加[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);