2009-03-29 6 views
0

Je travaille sur un système qui nécessite une interaction avec une API C native à l'aide de P/Invoke. Maintenant, je suis (encore une fois) tombé sur un problème que je n'arrive pas à résoudre d'une manière ou d'une autre. La fonction d'origine est conçue pour renvoyer deux types de structures, en fonction d'un paramètre qui spécifie la structure à utiliser.Problème d'appel de fonction P/Invoke

Le fichier d'en-tête C définit les structures et la fonction comme suit:

#pragma pack(1) 
typedef struct { 
    DWORD JobId; 
    DWORD CardNum; 
    HANDLE hPrinter; 
} CARDIDTYPE, FAR *LPCARDIDTYPE; 
#pragma pack() 

typedef struct { 
    BOOL  bActive; 
    BOOL  bSuccess; 
} CARD_INFO_1, *PCARD_INFO_1, FAR *LPCARD_INFO_1; 

typedef struct { 
    DWORD  dwCopiesPrinted; 
    DWORD  dwRemakeAttempts; 
    SYSTEMTIME TimeCompleted; 
} CARD_INFO_2, *PCARD_INFO_2, FAR *LPCARD_INFO_2; 

BOOL ICEAPI GetCardId(HDC hdc, LPCARDIDTYPE pCardId); 
BOOL ICEAPI GetCardStatus(CARDIDTYPE CardId, DWORD level, LPBYTE pData, DWORD cbBuf, LPDWORD pcbNeeded); 

J'ai essayé de mettre en œuvre P/Invoke emballages comme ceci:

[StructLayout(LayoutKind.Sequential, Pack=1)] 
public class CARDIDTYPE { 
    public UInt32 JobId; 
    public UInt32 CardNum; 
    public IntPtr hPrinter; 
} 

[StructLayout(LayoutKind.Sequential)] 
public class CARD_INFO_1 { 
    public bool bActive; 
    public bool bSuccess; 
} 

[StructLayout(LayoutKind.Sequential)] 
public class CARD_INFO_2 { 
    public UInt32 dwCopiesPrinted; 
    public UInt32 dwRemakeAttempts; 
    public Win32Util.SYSTEMTIME TimeCompleted; 
} 
[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardId(HandleRef hDC, [Out]CARDIDTYPE pCardId); 

[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardStatus(CARDIDTYPE CardId, UInt32 level, [Out] byte[] pData, UInt32 cbBuf, out UInt32 pcbNeeded); 

L'appel de la "GetCardId" semble fonctionner bien. Je reçois des données plausibles dans l'instance de CARDIDTYPE après l'avoir appelée. Cependant quand j'appelle "GetCardStatus" les problèmes commencent. Le type de structure qui doit être renvoyé est défini par le paramètre "level", et une valeur de 1 doit entraîner une structure CARD_INFO_1 à retourner dans "pData".

La documentation contient l'exemple C suivant:

CARD_INFO_1 ci1; 
DWORD cbNeeded; 
ci1.bActive = TRUE; 
if (GetCardStatus(*lpCardID, 1, (LPBYTE)&ci1, sizeof(ci1), &cbNeeded)) { /* success */ } 

Mon équivalent implémentation C# est comme ceci:

uint needed; 
byte[] byteArray = new byte[Marshal.SizeOf(typeof(CARD_INFO_1))]; 
if (GetCardStatus(cardId, 1, byteArray, (uint)byteArray.Length, out needed)) { /* success */ } 

Lorsque j'exécute ce code C#, la méthode retourne false et Marshal.GetLastWin32Error () return -1073741737 (ce qui n'a pas beaucoup de sens pour moi). Je ne vois pas pourquoi cet appel devrait échouer, et certainement pas avec ce code d'erreur. Donc, je pense que j'ai quelque chose de mal dans mon emballage P/Invoke.

Je sais que l'utilisation de "byte []" comme type de pData n'est probablement pas correcte, mais selon certains googling un "LPBYTE" se traduit par "[Out] byte []". Je suppose que la façon correcte de le faire est d'avoir pData comme IntPtr, et de créer la structure en utilisant Marshal.PtrToStructure (...). J'ai essayé cela, mais le résultat est le même. Voici le code pour ce scénario:

[DllImport(@"ICE_API.DLL", CharSet = CharSet.Auto, EntryPoint = "[email protected]", CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardStatus(CARDIDTYPE CardId, UInt32 level, IntPtr pData, UInt32 cbBuf, out UInt32 pcbNeeded); 

uint needed; 
int memSize = Marshal.SizeOf(typeof(CARD_INFO_1)); 
IntPtr memPtr = Marshal.AllocHGlobal(memSize); 
if (!GetCardStatus(cardId, 1, memPtr, (uint)memSize, out needed)) { 
    int lastError = Marshal.GetLastWin32Error(); 
    // error code is -1073741737 
} 
CARD_INFO_1 info = (CARD_INFO_1)Marshal.PtrToStructure(memPtr, typeof(CARD_INFO_1)); 
Marshal.FreeHGlobal(memPtr); 

Edit: Une chose que j'oublié de mentionner est que, pour une raison quelconque, l'appel GetCardStatus échoue avec une exception de point d'entrée inconnue si je ne précise pas EntryPoint = « _GetCardStatus @ 28 ". Cela n'est arrivé à aucune autre fonction que j'ai enveloppée, alors ça m'a fait me demander un peu.

Répondre

3

[email protected] m'a donné une idée. Sauf si vous utilisez Windows 64 bits, le nombre d'arguments est incorrect. Votre P/Invoke pour GetCardStatus serait [email protected], car il a 5 arguments de 32 bits.Votre déclaration C de GetCardStatus semble accepter le cardId par valeur plutôt que par référence. Puisque CARDIDTYPE est long de 12 octets, cela donnerait la bonne longueur de la liste d'arguments (28). De plus, cela expliquerait à la fois votre réception d'un code d'erreur de -1073741737 (C0000057, STATUS_INVALID_PARAMETER) - puisque vous n'êtes pas passer valide cardId - et la violation d'accès - GetCardStatus tente d'écrire pcbNeeded, ce qui est des ordures parce que le marshaleur n'a même pas poussé ça!

Ergo:

[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, 
    CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardStatus (
    IntPtr hPrinter, UInt32 cardNum, UInt32 jobId, UInt32 level, 
    [In, Out] CARD_INFO_1 data, UInt32 cbBuf, out UInt32 pcbNeeded) ; 
[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, 
    CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardStatus (
    IntPtr hPrinter, UInt32 cardNum, UInt32 jobId, UInt32 level, 
    [In, Out] CARD_INFO_2 data, UInt32 cbBuf, out UInt32 pcbNeeded) ; 

Remarque ordre inverse des trois CARDIDTYPE membres: stdcall repousse les paramètres de gauche à droite (vers des adresses en bas) et je suppose qu'une struct est « poussé » comme une unité.

De plus, si vous fermez plus tard, la poignée de l'imprimante avec CloseHandle, je vous suggère de recevoir la poignée CARDIDTYPE dans un SafeHandle approprié, non pas dans un IntPtr nu, et en déclarant GetCardStatus pour recevoir la poignée de sécurité.

+0

C'était en fait ce que j'ai fait au début. L'utilisation des signatures telles que décrites ici entraîne une exception: System.AccessViolationException: Tentative de lecture ou d'écriture de mémoire protégée. C'est souvent une indication que l'autre mémoire est corrompue. –

+0

J'ai lu votre question plus attentivement et édité ma réponse. –

+0

Merci pour vos commentaires. Votre réponse m'a conduit à résoudre le problème finalement. Je vais poster la solution complète dans une nouvelle réponse :-) –

1

Le problème est que vous utilisez [Out] où vous devez utiliser rien

[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardStatus(CARDIDTYPE CardId, UInt32 level, byte[] pData, UInt32 cbBuf, out UInt32 pcbNeeded); 

Le Out/In attributs indiquent la Marshaller CLR dans quelle direction la variable sera immédiatement mobilisée. Dans le cas de l'octet [], le paramètre ne fait vraiment rien. Un de ses sous-éléments est en train d'être déplacé. Le stockage de tableaux est une entreprise délicate, surtout lorsqu'il est utilisé directement dans une signature par rapport à une structure. Il peut être préférable d'utiliser un IntPtr, d'y allouer de la mémoire et de déplacer manuellement le tableau hors de l'IntPtr.

[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardStatus(CARDIDTYPE CardId, UInt32 level, IntPtr pData, UInt32 cbBuf, out UInt32 pcbNeeded); 

public void Example(uint size) { 
    // Get other params 
    var ptr = Marshal.AllocHGlobal(size); 
    GetCardStatus(cardId, level, ptr, size, out needed); 
    // Marshal back the byte array here 
    Marshal.FreeHGlobal(ptr); 
} 
+0

je tente, mais le résultat est le même. La fonction est rétablie false et le code d'erreur est le même. –

+0

@Johnny, la première ou la deuxième solution? – JaredPar

+0

Désolé ... La deuxième solution. Je vais modifier mon message pour inclure ce code. –

2

Comme le suggère Anton, le problème réside dans les paramètres transmis à la fonction. Je n'ai pas remarqué cela hier, mais la structure CARDIDTYPE est passée par pointeur dans la fonction GetCardID, et par valeur dans la fonction GetCardStatus. Dans mes appels j'ai passé le CARDIDTYPE par le pointeur vers le GetCardStatus également, forçant le cadre de P/Invoke pour localiser la fonction correcte en spécifiant le nom de fonction exact comme trouvé dans Dependecy Walker. J'ai résolu cela en définissant le CARDIDTYPE comme une structure au lieu d'une classe, et le transmettre en référence à la fonction GetCardId. De plus, le CARDIDTYPE est marshalé en tant que Struct lorsqu'il est passé à la fonction GetCardStatus. Cela en plus de la technique Antons d'utiliser deux définitions de fonction avec différents types de pData (CARD_INFO_1 et CARD_INFO_2) fonctionne maintenant correctement. Voici les définitions finales:

[StructLayout(LayoutKind.Sequential, Pack = 1)] 
public struct CARDIDTYPE { 
    public UInt32 JobId; 
    public UInt32 CardNum; 
    public IntPtr hPrinter; 
} 

[StructLayout(LayoutKind.Sequential)] 
public class CARD_INFO_1 { 
    public bool bActive; 
    public bool bSuccess; 
} 

[StructLayout(LayoutKind.Sequential)] 
public class CARD_INFO_2 { 
    public UInt32 dwCopiesPrinted; 
    public UInt32 dwRemakeAttempts; 
    public Win32Util.SYSTEMTIME TimeCompleted; 
} 

[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardId(HandleRef hDC, ref CARDIDTYPE pCardId); 

[DllImport(@"ICE_API.DLL", EntryPoint = "GetCardStatus", CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardStatus([MarshalAs(UnmanagedType.Struct)]CARDIDTYPE CardId, UInt32 level, 
    [In, Out] CARD_INFO_1 pData, UInt32 cbBuf, out UInt32 pcbNeeded); 

[DllImport(@"ICE_API.DLL", EntryPoint = "GetCardStatus", CallingConvention = CallingConvention.Winapi, SetLastError = true)] 
public static extern bool GetCardStatus([MarshalAs(UnmanagedType.Struct)]CARDIDTYPE CardId, UInt32 level, 
    [In, Out] CARD_INFO_2 pData, UInt32 cbBuf, out UInt32 pcbNeeded); 

Merci à vous deux pour votre contribution à la résolution de ce problème :-)

+0

Heh, je ne savais pas sur UnmanagedType.Struct. Merci pour le conseil! :) –

Questions connexes