从C#调用WindowsAPI CreateFile

时间:2019-03-15 03:41:08

标签: c# winapi pinvoke marshalling

从C#程序中调用WindowsAPI CreateFile时,最佳做法是:调用通用的CreateFile,ANSI CreateFileA或Unicode CreateFileW版本?

每个API对于相关的CharSet具有不同的签名:

$ mkdir -p /tmp/foo/bar/test
$ touch /tmp/foo/bar/test/file{1..7}
ls /tmp/foo/bar/test/
file1  file2  file3  file4  file5  file6  file7
$ outpath=/tmp/foo/bar/test
$ rm -rvf "$outpath"/!(file6)
removed ‘/tmp/foo/bar/test/file1’
removed ‘/tmp/foo/bar/test/file2’
removed ‘/tmp/foo/bar/test/file3’
removed ‘/tmp/foo/bar/test/file4’
removed ‘/tmp/foo/bar/test/file5’
removed ‘/tmp/foo/bar/test/file7’

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

另一位Microsoft doc 2 说,“当CharSet为Unicode或参数被明确标记为[MarshalAs(UnmanagedType.LPWSTR)]且字符串按值传递(而不是ref或out)时, ,则该字符串将被固定并由本机代码直接使用(而不是复制)。”对于避免复制可能较大的字符串并提供最佳性能来说,这似乎很有用。

假设我想调用与C#字符串最佳配合,具有最佳性能,最少的转换/翻译,在Windows x64 OS上工作且具有最大可移植性的CreateFile风格。

方法1:调用通用CreateFile,但将签名更改为CharSet.Unicode。
这可能是一个问题,因为CreateFile将lpFileName封送为UnmanagedType.LPTStr,而CreateFileW将其封送为UnmanagedType.LPWStr。似乎封送处理必须执行转换?获得正确的LP类型(不止一次)。另一个效率低下的地方是CreateFile必须在内部调用CreateFileW。另外,我想确保为实现最大性能而进行“固定”操作,并且不确定是否会在此处发生。

方法2:使用签名CharSet.Auto调用通用CreateFile 这似乎为目标OS提供了最大的可移植性,但是最终将在内部调用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

2 个答案:

答案 0 :(得分: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编码以及宽字符串类型(问题中的第三个选项)。


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

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

答案 1 :(得分:0)

致电CreateFileW。 C#字符串始终为Unicode,因此没有理由转换为ASCII并转换回Unicode。关于“通用” CreateFile-我不确定100%,但是对于大多数API函数而言,通用是C宏。实际导出的功能是AW版本。仅当您运行Windows 95/98 / Me时,才可以考虑CreateFileA(ASCII版本)。对于2000 / XP / 7/10,Unicode(UTF-16)字符串是默认字符串。