2017-10-18 52 views
-1

Je construis une DLL managée à utiliser dans un environnement non géré (application C/C++ - FreeRDP). Interop fonctionne très bien dans la plupart des cas, mais dans un cas particulier je ne suis pas capable de passer un pointeur sur struct. Dans l'API j'ai une struct:Passer un pointeur struct dans C# interop aboutit à 0ULL

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

Outre une fonction que je vous appelle:

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

deux traduis à 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; 

et le code d'exécution:

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

Et tandis que pChannelManager (qui est un pointeur que j'obtiens du code non managé appelant ma DLL) et la chaîne sont envoyés sans aucun problème, le pointeur que je crée ici (pCallback) est assigné avec succès en C#, mais il en résulte une NULL dans le code non managé .

Je suppose que le problème est soit avec la façon dont j'ai défini la structure, ou comment j'ai défini la fonction (bien que la fonction soit appelée avec succès dans le code non géré). J'utilise la méthode pour créer le pointeur exactement de la même manière que dans une autre partie de la DLL, et cela fonctionne parfaitement bien lorsqu'il est passé à une fonction non managée.

EDIT: Par suggestion @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); 

ne fonctionne pas.

EDIT2: Solution de contournement! Seulement si vous avez accès au code original non géré. J'ai réarrangé les arguments de la fonction afin que les pointeurs de structure soient les premiers, comme ceci:

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

Et ça marche! Probablement un problème avec offset.

+0

Vous avez besoin d'une classe C# pour OnNewChannelConnection. Le rappel va retourner un pointeur vers la structure. Vous devez spécifier les appels de paramètres de langage c, la valeur par défaut est la convention d'appel de Windows. En langage C, la liste des paramètres est poussée dans l'ordre inverse sur la pile. Ce qui serait en arrière de l'ordre de la structure. Pour que le code fonctionne, je pense que vous devez changer l'ordre de la liste des paramètres. – jdweng

+0

@jdweng Je ne suis pas sûr d'avoir bien compris, mais les méthodes sont spécifiées avec la convention d'appel __cdecl (via les délégués). Tous les autres paramètres passent sans aucune distorsion, seulement ce pointeur. –

+0

J'ai raté le cdecl. Pardon. La méthode ListenerCallbackNewConnectionDelegate est la seule à transmettre une liste de paramètres. Une structure a les éléments dans l'ordre des déclarations. Une liste de paramètres lors de l'appel d'une fonction crée également une structure dans l'ordre des paramètres. La pile commence en haut de la mémoire et descend pour que les paramètres apparaissent dans le même ordre que la structure (je me trompais en haut de la mémoire). Le problème est lorsque la fonction retourne ces valeurs qui sont perdues sur la pile.Je vais travailler lors de l'appel, mais ne fonctionne pas au retour. – jdweng

Répondre

0

C'était une question de décalage. C/C++ ULONG était typedef unsigned long que j'ai supposé à tort correspond à C# ulong, mais en fait le premier est de 4 octets dans Visual, tandis que l'autre est de 8 octets, ce qui a entraîné 4 octets de décalage. Fixé en changeant ulong à uint et en ajoutant [MarshalAs(UnmanagedType.U4)] pour une bonne mesure. Aspect final de la fonction que j'appelais à l'intérieur de C#:

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