澄清从本机结构复制字符串

时间:2012-01-11 00:07:17

标签: c# pinvoke marshalling

我正在使用PInvoke的东西来使用C ++中的SetupAPI函数。我正在使用它来获取符合HID规范的USB设备路径。我已经把一切都搞定了但是我不明白的东西让我感到困惑。使用SetupAPI中的这个结构:

typedef struct _SP_DEVICE_INTERFACE_DETAIL_DATA {
    DWORD cbSize;
    TCHAR DevicePath[ANYSIZE_ARRAY];
} SP_DEVICE_INTERFACE_DETAIL_DATA, *PSP_DEVICE_INTERFACE_DETAIL_DATA;

我没有得到与我正在使用的示例代码相同的结果。首先,我使用IntPtr并使用Marshal.AllocHGlobal()分配内存来来回传递。我调用了SetupDiGetDeviceInterfaceDetail()两次,首先得到我需要的缓冲区的大小,然后实际得到我感兴趣的数据。我想找到这个存储在这个设备的路径结构

我要离开的代码就是这样:

IntPtr pDevPath = new IntPtr(pDevInfoDetail.ToInt32() + 4);
string path = Marshal.PtrToStringAuto(pDevPath);

哪种方法效果很好。我做到了,我得到的字符串是胡言乱语。我不得不把它改成

IntPtr pDevPath = new IntPtr(pDevInfoDetail.ToInt32() + 4);
string path = Marshal.PtrToStringAnsi(pDevPath);

让它发挥作用。为什么是这样?我错过了项目/解决方案的一些设置,通知这个野兽如何处理字符串和字符?到目前为止,PtrToStringAuto()的MSDN文章并没有告诉我太多。事实上,看起来这个方法应该做出适当的决定,根据我的需要调用Unicode或Ansi版本,一切都会好的。

请解释。

1 个答案:

答案 0 :(得分:1)

首先,+10000使用真正的P / Invoke互操作类型而不是手动编组数据。但既然你问过,这就是你的琴弦发生了什么。

运行时根据您应用于互操作声明的属性,使用互操作的上下文,调用的方法等,决定如何根据每个案例处理字符串和字符。每种类型的P /调用声明(extern方法,委托或结构)允许您指定该定义范围的默认字符大小。有三种选择:

  • 使用CharSet.Ansi将托管的Unicode字符串转换为8位字符
  • 使用CharSet.Unicode,将字符串数据作为16位字符传递
  • 使用CharSet.Auto,它根据主机操作系统在运行时决定使用哪一个。

一般来说,我讨厌CharSet.Auto,因为它几乎没有意义。由于Framework甚至不支持Windows 95,因此唯一一次“Auto”并不意味着“Unicode”在Windows 98上运行时。但是这里存在一个更大的问题,即运行时决定如何编组字符串在“错误的时间”。

您正在调用的非托管代码在编译时做出决定,因为编译器必须确定TCHAR是否表示char或wchar - 该决定是基于是否存在_UNICODE预处理器宏。这意味着,对于大多数图书馆来说,它总是会使用其中一个,而让CLR“选择一个”是没有意义的。

对于Windows系统组件,事情要好一些,因为支持Unicode的构建实际上包含大多数系统功能的两个版本。例如,Setup API有两种方法:SetupDiGetDeviceInterfaceDetailASetupDiGetDeviceInterfaceDetailW。 * A版本使用8位“ANSI”字符串,* W版本使用16位宽“Unicode”字符串。它同样具有任何具有字符串的结构的ANSI和Wide版本。

这是CharSet.Auto闪耀的情况,假设您正确使用它。将DllImport应用于函数时,可以指定字符集。如果为字符集指定Ansi,如果运行时未找到与函数名称完全匹配的值,则会附加A并再次尝试。 (奇怪的是,如果你指定Unicode,它会先调用* W函数 ,如果失败则只尝试完全匹配。)

以下是问题:如果您未在DllImport上指定字符集,则默认为CharSet.Ansi 。这意味着您将获得该函数的ANSI版本,除非您专门覆盖该字符集。这很可能是这里发生的事情:你默认调用ANSI SetupDiGetDeviceInterfaceDetail版本,从而获得ANSI字符串,但PtrToStringAuto想要使用Unicode,因为你可能至少运行Windows XP

最佳选项,假设我们可以忽略Windows 98,将指定CharSet.Unicode到处,因为SetupAPI支持它,但至少,你需要指定相同的 CharSet价值无处不在。