Pinvoke提示 Net程序员[2] :字符串是不可变的吗?
墨初 知识笔记 118阅读
早在Java到来之前,程序员就已经逐渐接受并愿意接受字符串的这一特性:不可变。从C/C转到。Net/C#一开始把char[]和string分开处理可能会很不舒服,但是一旦习惯了就会觉得很方便,特别是直接连接,支持switch.凯斯。这时候,问题来了。字符串真的不可变吗?cbrumme的博客上举了一个例子:usingSystem使用系统。Runtime . InteropServicespublic class class 1 { staticvoidMain(string[]args){ string computername=' stringsaraelwaysimmutable 'StringotherString=' stringsaarealwaysimmutable '因特伦
pan>= computerName.Length;GetComputerName(computerName, ref len);
Console.WriteLine(otherString);
}
[DllImport("kernel32", CharSet=CharSet.Unicode)]
static extern bool GetComputerName(
[MarshalAs (UnmanagedType.LPWStr)] string name,
ref int len);
}
该程序的执行结果也许正在你的预料之中,输出的是类似
MYCOMPUTERNAMElways immutable
之类的字符串,也就是说原字符串的前面一部分被计算机名覆盖掉了。
对上面的程序,我们可以做出如下分析:
1,computerName和otherString 的文本相同,因此由于编译器的Interning的结果,二者其实指向同一个字符串,用Object.ReferenceEquals()可以验证其相等。
2,红色部分标出的Marshal指令,使得该string被marshal为一个unmanaged pointer(LPWSTR)传递给了GetComputerName函数;
3,GetComputerName函数直接改写了computerName指向的缓冲区,string的immutable特性即被破坏。
由此我们可以看到,在与Unamanaged代码进行交互操作时必须额外小心,因为从某种意义上来说Unmanaged代码权限更大,破坏力也就更大,也就更容易引起意想不到的问题。
因此,上面那段使用GetComputerName的代码中,对该函数的包装要如何改进呢?
首先,在使用一个API之前应该注意其各个参数的in, out性质,例如关于GetComputerName,MSDN上有如下一段:
BOOL GetComputerName( LPTSTR lpBuffer, LPDWORD lpnSize );
Parameters
lpBuffer [out] Pointer to a buffer that receives a null-terminated string containing the computer name or the cluster virtual server name. The buffer size should be large enough to contain MAX_COMPUTERNAME_LENGTH + 1 characters.很显然,lpBuffer应该是用来输出的缓冲区,因此不应该用string,而是用byte[],StringBuilder之类的类型与之对应;
即便一定要用String,也绝对不能Marshal为LPWSTR/LPTSTR,而是Marshal为VBByRefStr,以确保Managed代码侧string的immutable性质。
※此外,使用unsafe代码也可以打破String的immutable,由于不在本文范围之内,就不进行说明了。
![Pinvoke提示 Net程序员[2] :字符串是不可变的吗?](https://www.feiniaomy.com/xzm_artimg/2309161355o21657469434_0.png)
标签: