PInvoke来自C#的WindowsAPI CreateFile

问题描述 投票:0回答:2

从c#程序PInvoking WindowsAPI CreateFile时,最佳做法是什么:调用通用CreateFile,ANSI CreateFileA或Unicode CreateFileW版本?

每个API都有相应CharSet的不同签名:

// CreateFile generic
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern SafeFileHandle CreateFile (
    [MarshalAs(UnmanagedType.LPTStr)] string lpFileName,
    ...

 // CreateFileA ANSI 
 [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
 public static extern SafeFileHandle CreateFileA (
    [MarshalAs(UnmanagedType.LPStr)] string lpFileName,
    ...

// CreateFileW Unicode
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeFileHandle CreateFileW (
    [MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
    ...

根据Microsoft documentation1,对于C#,默认的CharSet是Charset.ANSI。这似乎很奇怪,因为C#中的字符串是Unicode。如果文档是正确的,则意味着CreateFile最终将在运行时调用CreateFileA(在此过程中适当地转换为ANSI)。

另一个Microsoft doc2说,“当CharSet是Unicode或参数明确标记为[MarshalAs(UnmanagedType.LPWSTR)]并且字符串是通过值传递(不是ref或out)时,字符串将被固定并由本机直接使用代码(而不是复制)。“这似乎非常适合避免复制潜在的大字符串并提供最大性能。

假设我想调用最适合C#字符串的CreateFile风格,具有最佳性能,最小的转换/翻译,适用于Windows x64操作系统,其次具有最大的可移植性。

方法1:调用通用CreateFile,但将签名更改为CharSet.Unicode。 这可能是一个问题,因为CreateFile将lpFileName编组为UnmanagedType.LPTStr,而CreateFileW将其编组为UnmanagedType.LPWStr。似乎编组必须进行转换?获得正确的LP类型(不止一次)。另一个低效率是CreateFile必须在内部调用CreateFileW。此外,我想确保“钉扎”发生最大性能,我不确定会发生在这里。

方法2:使用签名CharSet.Auto调用通用CreateFile这似乎为目标操作系统提供了最大的可移植性,但最终会在内部调用CreateFileA,这不适合C#字符串(Unicode)。

方法3:直接调用CreateFileW。这似乎也不是最优的,因为如果我正在编译不同的目标操作系统,如Win x86(仅使用ANSI字符串),那么程序根本无法运行。

看起来方法1似乎是最好的但是MarshalAs LPTStr看起来并不合适(考虑到CreateFileW版本编组为LPWStr)。

我很感激你能给予的任何帮助。我一直在挖掘数十个相互冲突的网页,无法找到明确的答案。

参考文献:

1 DllImportAttribute.CharSet Field

2 Native interoperability best practices

3 Copying and Pinning

c# winapi pinvoke marshalling
2个回答
2
投票

Windows在内部使用UTF-16 LE字符编码1。当您调用Windows API的ANSI版本时,系统会将输入转换为UTF-16(使用调用线程的当前代码页),调用Unicode版本,并将输出转换回ANSI编码。这既费钱又有损:不是每个Unicode字符串都可以用ANSI编码表示。转换还对输入和输出缓冲区施加任意大小限制(CreateFileA将文件名长度限制为260 ANSI代码单位)。

考虑到这一点,您需要确保始终调用Windows API的Unicode版本。这样可以在所有受支持的Windows版本上提供最高性能,并在从Unicode转换为ANSI时防止信息丢失。你是否使用CharSet.AutoMarshalAs(UnmanagedType.LPTStr)CharSet.UnicodeMarshalAs(UnmanagedType.LPWStr)相同2,这是个人喜好的问题。 Microsoft recommends是显式的,即明确命名Unicode版本(CreateFileW)并指定Unicode编码以及宽字符串类型(问题中的第3个选项)。


1 Windows 95/98 / ME除外,统称为Win9x。他们都没有得到官方支持。

2 CharSet.Auto“在运行时根据目标平台选择ANSI和Unicode格式”,因此它在理论上与CharSet.Unicdoe不同。但是,所有支持的平台在实践中都使用Unicode编码。


0
投票

打电话给CreateFileW。 C#字符串始终是Unicode,没有理由转换为ASCII并返回Unicode。关于“通用”CreateFile - 我不是100%肯定,但对于大多数API函数,泛型是一个C宏。真正的导出函数是AW版本。只有在运行Windows 95/98 / Me时才可以考虑CreateFileA(ASCII版本)。对于2000 / XP / 7/10 Unicode(UTF-16)字符串是默认值。

© www.soinside.com 2019 - 2024. All rights reserved.