【问题标题】:DLLImport -> how to handle a HANDLE in C#DLLImport -> 如何在 C# 中处理句柄
【发布时间】:2011-05-08 15:49:12
【问题描述】:

在我的 C# 代码中,我想导入一个 C++ DLL。我使用 dllimport,它可以很好地处理一些功能。但在一个函数中,我得到了一个 HANDLE,稍后我需要调用另一个函数。

   [DllImport("SiUSBXp.dll")]
   public static extern int SI_Open(UInt32 deviceNum,ref IntPtr devHandle );   // this function gets the HANDLE
   [DllImport("SiUSBXp.dll")]
   public static extern int SI_Write([In]IntPtr devHandle, [In, Out] byte[] inputByte, UInt32 size,ref UInt32 bytesWritten); // this function needs the HANDLE

在我的代码中,这些函数是这样调用的:

   IntPtr devHandle = new IntPtr();
   UInt32 bytesWritten = new UInt32();
   byte[] byteArr = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
   SI_Open(0, ref devHandle);
   SI_Write(devHandle, byteArr, 10, ref bytesWritten);

如果我这样做,我会收到“System.AccessViolationException”。我在这里和互联网上搜索过,但没有找到具体的答案。如何正确使用 IntPtr,使其正常工作? 最好的问候

托比

【问题讨论】:

  • SI_Open 和 SI_Write 的 C++ 函数原型是什么样的?
  • 您将字节数组初始化为 10 个元素,但这对于 SI_Write 函数是否足够?
  • SI_STATUS WINAPI SI_Open(DWORD dwDevice, HANDLE* cyHandle);
  • SI_STATUS WINAPI SI_Write(HANDLE cyHandle, LPVOID lpBuffer, DWORD dwBytesToWrite, LPDWORD lpdwBytesWritten);
  • 到字节数组 => 它实际上与 C++ 代码中的“相同”数组一起工作。我也尝试了一些东西 - >我像这样初始化了一个 IntPtr “IntPtr Test = new IntPtr()” - 当我用这个 IntPtr 调用 Write 函数时,它不会抛出异常......所以我实际上认为错误与 IntPtr 有关

标签: c# c++ dllimport handle intptr


【解决方案1】:

您的 SI_Write 函数看起来很像 Windows Kernel32 的 WriteFile

所以,我会这样做:

[DllImport("SiUSBXp.dll", SetLastError = true)]
static extern int SI_Open(uint dwDevice, ref IntPtr cyHandle);  

[DllImport("SiUSBXp.dll", SetLastError = true)]
static extern int SI_Write(IntPtr cyHandle, byte[] lpBuffer,
   uint dwBytesToWrite, out uint lpdwBytesWritten);

编辑:我在网上找到了这个文档USBXPRESS® PROGRAMMER’S GUIDE,它指出 SI_Write 原型实际上比我想象的更接近 WriteFile。该文档指出:

SI_STATUS SI_Write (HANDLE Handle, LPVOID Buffer, DWORD NumBytesToWrite,
DWORD *NumBytesWritten, OVERLAPPED* o = NULL)

这意味着 .NET 原型应该是这样的:

[DllImport("SiUSBXp.dll")]
static extern int SI_Write(IntPtr Handle, byte[] Buffer,
   uint NumBytesToWrite, out uint NumBytesWritten, IntPtr o);

o 是可选的,因此您可以传递 IntPtr.Zero。

【讨论】:

  • @Toby - 奇怪。也许 C++ 实现做了一些不寻常的事情。当你说它不起作用时,它仍然是 AccessViolationException 错误吗?
  • 没错……我不知道问题出在哪里。如果我 a) 将 SI_Write 中的缓冲区声明为 out (但就像在另一个答案中已经提到的那样,这实际上是错误的),我可以解决这个问题 - 如果我这样做,我会得到奇怪的值到达设备。 b)如果我声明要写入的字节数为零,但是嘿,这也不是解决方案 ^^ - 但我认为问题出在字节数组中,因为我可以使用 IntPtr 调用 SI_Close 函数(未提及在这里)没有问题!
  • 好吧,我得到一个好消息和一个坏消息 - 好消息是,该错误不会发生 - 现在 ^^ - 坏消息是现在 SI_Write 函数返回 12 ( 0x0C ),即在标题中定义为:SI_SYSTEM_ERROR_CODE 0x0c - 现在要做一些试验和错误,然后再报告。
  • @Toby - 从文档中,SI_SYSTEM_ERROR_CODE 意味着您现在可以使用 Marshal.GetLastWin32Error 并获得“真正的”底层 Windows 错误。请注意,我已在答案中将 SetLastError 添加到 C# 原型中,因此 Marshal.GetLastWin32Error 将起作用。
  • 天哪 - 它有效!我实际上做了一点改动——我从最后一个 IntPtr 中删除了“out”关键字。 --> static extern int SI_Write(IntPtr Handle, byte[] Buffer, uint NumBytesToWrite, out uint NumBytesWritten,IntPtr o);现在数据正确到达设备并且功能正确关闭!所以我实际上会说就是这样......?还是删除 out 关键字是错误的??!无论如何,我已经想告诉你 -> 非常感谢你花时间帮助我!!!
【解决方案2】:

你犯了一个典型的 C 程序员错误,你没有检查函数的返回值。它告诉您该功能是否失败。一种可能的情况是 SI_Open() 返回了一个失败代码。您忽略它并使用未初始化的句柄值。 Kaboom 并不罕见。

下一个可能的错误是您没有在 [DllImport] 语句中使用 CallingConvention 属性。很有可能需要它,除非使用 __stdcall 声明本机函数,否则 Cdecl 是默认值。也是调用 kaboom 的绝佳方式。如果您仍然遇到问题,那么您将不得不调试本机代码。

顺便说一句,您可以通过使用 out 而不是 ref 来摆脱笨拙的语法。在这两个函数中。

   [DllImport("SiUSBXp.dll", CallingConvention = CallingConvention.Cdecl)]
   public static extern int SI_Open(UInt32 deviceNum, out IntPtr devHandle );

【讨论】:

  • 我实际上确实检查了返回值(并且 SI_Open 成功),但我只是将其删除,以便您更清楚地了解源代码的关键点。
  • 不要犹豫,在 MSDN 库中查找此内容。帖子已更新。
  • 但是不应该是“CallingConvention.Stdcall”而不是Cdecl吗?因为在 MSDN 上它是“被调用者清理堆栈。这是使用平台调用调用非托管函数的默认约定。” ... ?
  • 是的,这是默认设置。仅当负责任的 C/C++ 程序员在声明中使用 __stdcall 关键字时,它才有效。这并不常见。我从这里看不到那个代码,你离得更近了。
【解决方案3】:

试试这个:

   [DllImport("SiUSBXp.dll")]
   public static extern int SI_Open(UInt32 deviceNum, ref IntPtr devHandle);   // this function gets the HANDLE
   [DllImport("SiUSBXp.dll")]
   public static extern int SI_Write(IntPtr devHandle, ref byte[] inputByte, UInt32 size, ref UInt32 bytesWritten); // this function needs the HANDLE

编辑:

@Hans Passant 是对的。这是将 byte[] 传递给 LPVOID 参数的正确方法。 ref 用于将对象强制转换为 LPVOID,但数组不需要。当你尝试这个时会发生什么?

   [DllImport("SiUSBXp.dll")]
   public static extern int SI_Write(IntPtr devHandle, byte[] inputByte, UInt32 size, ref UInt32 bytesWritten); // this function needs the HANDLE

您尝试过@Simon Mourier 给出的答案吗?他是第一个提供此声明的人,他的回答值得被接受。

【讨论】:

  • 但是我尝试写入的字节没有正确写入 uC :(
  • 调用后bytesWritten的值是多少?
  • 这个答案不可能是正确的,数组已经作为指针(LPVOID)传递。声明它 ref 使其成为指向指针 (LPVOID*) 的指针。
  • 到您的编辑 --> 在这种情况下,我得到“通常”的 AccessViolation 异常......所以至少我们指出错误存在于字节数组中,而不是 IntPtr!跨度>
【解决方案4】:

不好: static extern void DoStuff(**byte[] inputByte**);

好: static extern void DoStuff(**[In, MarshalAs(UnmanagedType.LPArray)] byte[] inputByte**);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-31
    • 2016-02-01
    相关资源
    最近更新 更多