从 C# 调用 WindowsAPI CreateFile
PInvoke WindowsAPI CreateFile from C#
当从 c# 程序中调用 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 文档1,对于 C#,默认的 CharSet 是 Charset.ANSI。这看起来很奇怪,因为 C# 中的字符串是 Unicode。如果文档是正确的,则意味着 CreateFile 最终将在 运行 时间调用 CreateFileA(在此过程中来回适当地转换为 ANSI)。
另一个 Microsoft 文档2 说,"When the CharSet is Unicode or the argument is explicitly marked as [MarshalAs(UnmanagedType.LPWSTR)] and the string is passed by value (not ref or out), the string will be pinned and used directly by native code (rather than copied)." 这对于避免复制潜在的大字符串并提供最大性能来说似乎很棒。
假设我想调用 CreateFile 风格,它最适合 C# 字符串,具有最佳性能,最少的转换/翻译,适用于 Windows x64 OS,其次具有最大的可移植性。
方法 1:调用通用 CreateFile 但将签名更改为 CharSet.Unicode。
这可能是个问题,因为 CreateFile 将 lpFileName 编组为 UnmanagedType.LPTStr,而 CreateFileW 将其编组为 UnmanagedType.LPWStr。似乎编组必须执行转换?获得正确的 LP 类型(不止一次)。另一个低效率是 CreateFile 必须在内部调用 CreateFileW。另外,我想确保 "pinning" 正在发生以获得最佳性能,但我不确定这里是否会发生这种情况。
方法 2:调用带有签名的通用 CreateFile CharSet.Auto
这似乎为目标 OS 提供了最大的可移植性,但最终会在内部调用 CreateFileA,这不适合 C# 字符串 (Unicode)。
方法三:直接调用CreateFileW。
这似乎也不是最佳选择,因为如果我正在为不同的目标 OS 编译,比如 Win x86(仅使用 ANSI 字符串),那么程序将根本无法 运行。
似乎方法 1 最好,但 MarshalAs LPTStr 对我来说不合适(考虑到 CreateFileW 版本编组为 LPWStr)。
如果您能提供任何帮助,我将不胜感激。我一直在挖掘数十个相互矛盾的网页,但找不到明确的答案。
参考文献:
1 DllImportAttribute.CharSet Field
Windows 在内部使用 UTF-16 LE 字符编码1。当您调用 Windows API 的 ANSI 版本时,系统会将输入转换为 UTF-16(使用调用线程的当前代码页),调用到 Unicode 版本,并将输出转换回来到 ANSI 编码。这既是不必要的成本,也是有损的:并非每个 Unicode 字符串都可以使用 ANSI 编码来表示。该转换还对输入和输出缓冲区施加了任意大小限制(CreateFileA 将文件名长度限制为 260 个 ANSI 代码单元)。
考虑到这一点,您需要确保始终调用 Windows API 的 Unicode 版本。这在所有受支持的 Windows 版本上提供了最高性能,并在从 Unicode 转换为 ANSI 时防止信息丢失。使用 CharSet.Auto
和 MarshalAs(UnmanagedType.LPTStr)
或 CharSet.Unicode
和 MarshalAs(UnmanagedType.LPWStr)
等于 2,这是个人喜好问题。 Microsoft recommends 是明确的,即明确命名 Unicode 版本 (CreateFileW
) 并指定 Unicode 编码以及宽字符串类型(您问题中的第三个选项)。
1除Windows95/98/ME外,统称Win9x。 None 其中官方支持。
2 CharSet.Auto
"chooses between ANSI and Unicode formats at run time, based on the target platform", 所以它和 CharSet.Unicdoe
不一样理论。但是,所有受支持的平台实际上都使用 Unicode 编码。
致电CreateFileW
。 C# 字符串始终是 Unicode,没有理由转换为 ASCII 并返回到 Unicode。关于 "generic" CreateFile
- 我不是 100% 确定,但对于大多数 API 函数,泛型是一个 C 宏。真正导出的函数是 A
和 W
版本。只有当你 运行 Windows 95/98/Me 时,你才可能会想到 CreateFileA
(ASCII 版本)。对于 2000/XP/7/10 Unicode (UTF-16) 字符串是默认值。
当从 c# 程序中调用 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 文档1,对于 C#,默认的 CharSet 是 Charset.ANSI。这看起来很奇怪,因为 C# 中的字符串是 Unicode。如果文档是正确的,则意味着 CreateFile 最终将在 运行 时间调用 CreateFileA(在此过程中来回适当地转换为 ANSI)。
另一个 Microsoft 文档2 说,"When the CharSet is Unicode or the argument is explicitly marked as [MarshalAs(UnmanagedType.LPWSTR)] and the string is passed by value (not ref or out), the string will be pinned and used directly by native code (rather than copied)." 这对于避免复制潜在的大字符串并提供最大性能来说似乎很棒。
假设我想调用 CreateFile 风格,它最适合 C# 字符串,具有最佳性能,最少的转换/翻译,适用于 Windows x64 OS,其次具有最大的可移植性。
方法 1:调用通用 CreateFile 但将签名更改为 CharSet.Unicode。
这可能是个问题,因为 CreateFile 将 lpFileName 编组为 UnmanagedType.LPTStr,而 CreateFileW 将其编组为 UnmanagedType.LPWStr。似乎编组必须执行转换?获得正确的 LP 类型(不止一次)。另一个低效率是 CreateFile 必须在内部调用 CreateFileW。另外,我想确保 "pinning" 正在发生以获得最佳性能,但我不确定这里是否会发生这种情况。
方法 2:调用带有签名的通用 CreateFile CharSet.Auto 这似乎为目标 OS 提供了最大的可移植性,但最终会在内部调用 CreateFileA,这不适合 C# 字符串 (Unicode)。
方法三:直接调用CreateFileW。 这似乎也不是最佳选择,因为如果我正在为不同的目标 OS 编译,比如 Win x86(仅使用 ANSI 字符串),那么程序将根本无法 运行。
似乎方法 1 最好,但 MarshalAs LPTStr 对我来说不合适(考虑到 CreateFileW 版本编组为 LPWStr)。
如果您能提供任何帮助,我将不胜感激。我一直在挖掘数十个相互矛盾的网页,但找不到明确的答案。
参考文献:
1 DllImportAttribute.CharSet Field
Windows 在内部使用 UTF-16 LE 字符编码1。当您调用 Windows API 的 ANSI 版本时,系统会将输入转换为 UTF-16(使用调用线程的当前代码页),调用到 Unicode 版本,并将输出转换回来到 ANSI 编码。这既是不必要的成本,也是有损的:并非每个 Unicode 字符串都可以使用 ANSI 编码来表示。该转换还对输入和输出缓冲区施加了任意大小限制(CreateFileA 将文件名长度限制为 260 个 ANSI 代码单元)。
考虑到这一点,您需要确保始终调用 Windows API 的 Unicode 版本。这在所有受支持的 Windows 版本上提供了最高性能,并在从 Unicode 转换为 ANSI 时防止信息丢失。使用 CharSet.Auto
和 MarshalAs(UnmanagedType.LPTStr)
或 CharSet.Unicode
和 MarshalAs(UnmanagedType.LPWStr)
等于 2,这是个人喜好问题。 Microsoft recommends 是明确的,即明确命名 Unicode 版本 (CreateFileW
) 并指定 Unicode 编码以及宽字符串类型(您问题中的第三个选项)。
1除Windows95/98/ME外,统称Win9x。 None 其中官方支持。
2 CharSet.Auto
"chooses between ANSI and Unicode formats at run time, based on the target platform", 所以它和 CharSet.Unicdoe
不一样理论。但是,所有受支持的平台实际上都使用 Unicode 编码。
致电CreateFileW
。 C# 字符串始终是 Unicode,没有理由转换为 ASCII 并返回到 Unicode。关于 "generic" CreateFile
- 我不是 100% 确定,但对于大多数 API 函数,泛型是一个 C 宏。真正导出的函数是 A
和 W
版本。只有当你 运行 Windows 95/98/Me 时,你才可能会想到 CreateFileA
(ASCII 版本)。对于 2000/XP/7/10 Unicode (UTF-16) 字符串是默认值。