C# PInvoke WindowsAPI从C创建文件#

C# PInvoke WindowsAPI从C创建文件#,c#,winapi,pinvoke,marshalling,C#,Winapi,Pinvoke,Marshalling,从c#程序PInvoking WindowsAPI CreateFile时,最佳做法是什么:调用通用CreateFile、ANSI CreateFileA或Unicode CreateFileW版本 每个API对相关字符集都有不同的签名: // CreateFile generic [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern SafeFileHandle

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

每个API对相关字符集都有不同的签名:

// 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.ANSI。这看起来很奇怪,因为C#中的字符串是Unicode。如果文档是正确的,这意味着CreateFile最终将在运行时调用CreateFileA(同时进行适当的ANSI来回转换)

另一个Microsoft doc2说,“当字符集是Unicode或参数显式标记为[Marshallas(UnmanagedType.LPWSTR)],字符串通过值传递(而不是ref或out),字符串将被固定并直接由本机代码使用(而不是复制)。”这对于避免复制可能较大的字符串和提供最大性能来说似乎很好

假设我想调用CreateFile flavor,它最适合于C#字符串,具有最佳性能,最少的转换/转换,在Windows x64操作系统上工作,其次具有最大的可移植性

方法1:调用泛型CreateFile,但将签名更改为CharSet.Unicode。
这可能是一个问题,因为CreateFile将lpFileName封送为UnmanagedType.LPTStr,而CreateFileW将其封送为UnmanagedType.LPWStr。似乎封送处理必须执行转换?获取正确的LP类型(不止一次)。另一个低效之处是CreateFile必须在内部调用CreateFileW。此外,我想确保“钉住”是为了实现最高性能,我不确定这会发生在这里

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

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

看起来方法1是最好的,但是Marshallas LPTStr在我看来并不合适(考虑到CreateFileW版本封送为LPWStr)

如果你能在这方面给予任何帮助,我将不胜感激。我已经翻阅了几十个相互矛盾的网页,但找不到确切的答案

参考资料:

一,

二,


3

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

记住这一点,您需要确保始终调用Windows API的Unicode版本。这在所有受支持的Windows版本上提供了最大的性能,并且在从Unicode转换到ANSI时防止信息丢失。无论您使用
CharSet.Auto
marshallas(UnmanagedType.LPTStr)
还是
CharSet.Unicode
marshallas(UnmanagedType.LPWStr)
都是相同的2,这是个人偏好的问题。显式,即显式命名Unicode版本(
CreateFileW
),并指定Unicode编码以及宽字符串类型(问题中的第三个选项)


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


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

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

记住这一点,您需要确保始终调用Windows API的Unicode版本。这在所有受支持的Windows版本上提供了最大的性能,并且在从Unicode转换到ANSI时防止信息丢失。无论您使用
CharSet.Auto
marshallas(UnmanagedType.LPTStr)
还是
CharSet.Unicode
marshallas(UnmanagedType.LPWStr)
都是相同的2,这是个人偏好的问题。显式,即显式命名Unicode版本(
CreateFileW
),并指定Unicode编码以及宽字符串类型(问题中的第三个选项)


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


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

调用
CreateFileW
。C#字符串始终是Unicode,没有理由将其转换为ASCII和Unicode。关于“generic”
CreateFile
——我不是100%确定,但对于大多数API函数来说,generic是一个C宏。真正导出的函数是
A