P / Вызвать проблему вызова функции - PullRequest
0 голосов
/ 29 марта 2009

Я работаю в системе, которая требует взаимодействия с собственным C API, используя P / Invoke. Теперь я (еще раз) наткнулся на проблему, которую никак не могу решить. Оригинальная функция предназначена для возврата 2 видов структур, основываясь на параметре, который указывает, какую структуру использовать.

Файл заголовка C определяет структуры и функции следующим образом:

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

Я попытался внедрить оболочки P / Invoke следующим образом:

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

Кажется, что вызов GetCardId работает нормально. Я получаю правдоподобные данные в экземпляре CARDIDTYPE после его вызова. Однако, когда я вызываю «GetCardStatus», начинаются проблемы. Тип структуры, которая должна быть возвращена, определяется параметром "level", а значение 1 должно привести к структуре CARD_INFO_1, которая будет возвращаться в "pData".

Документация содержит следующий пример C:

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

Моя эквивалентная реализация на C # выглядит следующим образом:

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

Когда я выполняю этот код C #, метод возвращает false, а Marshal.GetLastWin32Error () возвращает -1073741737 (что для меня не имеет большого смысла). Я не вижу причин, по которым этот вызов должен завершиться неудачей, и определенно не с этим кодом ошибки. Поэтому я подозреваю, что что-то не так в моей оболочке P / Invoke.

Я знаю, что использование «byte []» в качестве типа pData, вероятно, некорректно, но, согласно некоторым поискам, «LPBYTE» переводится в «[Out] byte []». Я предполагаю, что правильный способ сделать это - использовать pData в качестве IntPtr и создать структуру, используя Marshal.PtrToStructure (...). Я пробовал это, но результат тот же. Вот код для этого сценария:

[DllImport(@"ICE_API.DLL", CharSet = CharSet.Auto, EntryPoint = "_GetCardStatus@28", 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: Одна вещь, которую я забыл упомянуть, это то, что по какой-то причине вызов GetCardStatus завершается неудачно с неизвестным исключением точки входа, если я не укажу EntryPoint = "_GetCardStatus @ 28". Этого не произошло ни с одной другой функцией, которую я обернул, поэтому я немного удивился.

Ответы [ 3 ]

3 голосов
/ 29 марта 2009

_GetCardStatus@28 дал мне идею. Если вы не работаете в 64-битной Windows, у вас неверное количество аргументов. Ваш P / Invoke для GetCardStatus будет _GetCardStatus@20, потому что он имеет 5 32-битных аргументов. Ваша декларация C GetCardStatus, кажется, принимает cardId по значению, а не по ссылке. Поскольку CARDIDTYPE имеет длину 12 байт, это даст правильную длину списка аргументов (28). Более того, это объясняет как получение вами кода ошибки -1073741737 (C0000057, STATUS_INVALID_PARAMETER) - поскольку вы не передаете действительные cardId - и нарушение доступа - GetCardStatus пытается напишите pcbNeeded, что является мусором, потому что маршалер даже не выдвинул его!

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

Обратите внимание на обратный порядок трех CARDIDTYPE членов: stdcall сдвигает параметры слева направо (то есть в направлении более низких адресов), и я предполагаю, что struct "толкается" как единое целое.

Кроме того, если позже вы закроете дескриптор принтера с помощью CloseHandle, я бы рекомендовал получить дескриптор в CARDIDTYPE в соответствующий SafeHandle, а не в пустой IntPtr и объявить GetCardStatus для получения безопасная ручка.

2 голосов
/ 30 марта 2009

Как предполагает Антон, проблема заключается в параметрах, передаваемых в функцию. Я не заметил этого вчера, но структура CARDIDTYPE передается по указателю в функции GetCardID и по значению в функции GetCardStatus. В своих вызовах я передавал CARDIDTYPE по указателю также на GetCardStatus, заставляя платформу P / Invoke найти правильную функцию, указав точное имя функции, найденное в Dependecy Walker.

Я решил это, определив CARDIDTYPE как структуру вместо класса, и передал ее по ссылке в функцию GetCardId. Кроме того, CARDIDTYPE маршалируется как структура при передаче функции GetCardStatus. Это в дополнение к технике Антонса, использующей два определения функций с разными типами pData (CARD_INFO_1 и CARD_INFO_2), теперь работает правильно. Вот окончательные определения:

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

Спасибо вам обоим за ваш вклад в решение этой проблемы: -)

1 голос
/ 29 марта 2009

Проблема в том, что вы используете [Out] там, где вы ничего не должны использовать

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

Атрибуты Out / In сообщают маршаллеру CLR, в каком направлении будет маршалирована переменная немедленное . В случае с байтом [] параметр действительно ничего не делает. Один из его подэлементов перемещается.

Выделение массивов - дело сложное, особенно когда оно используется непосредственно в сигнатуре или в структуре. Для вас может быть лучше использовать IntPtr, выделить там память и вручную распределить массив из 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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...