代码之家  ›  专栏  ›  技术社区  ›  Johnny Egeland

P/Invoke函数调用问题

  •  0
  • Johnny Egeland  · 技术社区  · 16 年前

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

    打电话给“GetCardd”似乎效果不错。调用CardType实例后,我在该实例中获得了合理的数据。然而,当我调用“GetCardStatus”时,问题就开始了。应返回的结构类型由“level”参数定义,值为1将导致在“pData”中返回一个CARD_INFO_1结构。

    文档包含以下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);
    

    编辑: 我忘记提到的一件事是,由于某种原因,如果我没有指定EntryPoint=,GetCardStatus调用将失败,并出现未知的入口点异常_GetCardStatus@28". 我包装的任何其他函数都没有出现这种情况,所以我有点疑惑。

    3 回复  |  直到 16 年前
        1
  •  3
  •   Anton Tykhyy    16 年前

    _GetCardStatus@28 GetCardStatus 会是 _GetCardStatus@20 ,因为它有5个32位参数。你的C声明 GetCardStatus 似乎接受了这个事实 cardId CARDIDTYPE 是12字节长,这将给出参数列表的正确长度(28)。此外,这将解释您收到的错误代码为-1073741737(C0000057, STATUS_INVALID_PARAMETER 卡迪德 GetCardStatus 试图写信给 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_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) ;
    

    注意三者的相反顺序 梳棉机 stdcall struct

    CloseHandle ,我建议你把把手收起来 进入一个适当的阶段 SafeHandle ,而不是光秃秃的 IntPtr ,并宣布 接受安全手柄。

        2
  •  2
  •   Johnny Egeland    16 年前

    正如Anton所说,问题在于传递给函数的参数。我昨天没有注意到这一点,但是CardType结构在GetCardID函数中通过指针传递,在GetCardStatus函数中通过值传递。在调用中,我还通过指向GetCardStatus的指针传递了CardType,通过指定Dependecy Walker中的确切函数名,强制P/Invoke框架定位正确的函数。

    我通过将CardType定义为结构而不是类来解决这个问题,并通过引用将其传递给GetCardId函数。此外,CardType在传递给GetCardStatus函数时被封送为结构。除了Anton使用两种不同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);
    

    感谢你们为解决这个问题所做的贡献:-)

        3
  •  1
  •   JaredPar    16 年前

    问题是你在使用[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封送拆收器 立即的 变量将被封送。在字节[]的情况下,参数实际上什么也不做。它的一个子元素正在移动。

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