从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
答案 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.Auto
和MarshalAs(UnmanagedType.LPTStr)
还是CharSet.Unicode
和MarshalAs(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宏。实际导出的功能是A
和W
版本。仅当您运行Windows 95/98 / Me时,才可以考虑CreateFileA
(ASCII版本)。对于2000 / XP / 7/10,Unicode(UTF-16)字符串是默认字符串。