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.
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. –
J'ai lu votre question plus attentivement et édité ma réponse. –
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 :-) –