Santé

为明天干杯!
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

给.Net程序员的PInvoke Tips [2]: Are Strings Immutable?

Posted on 2006-06-22 10:56  smalldust  阅读(821)  评论(0编辑  收藏  举报
早在Java到来之际,程序员们都已逐渐接受并乐于接受String的这一特性:immutable。
从C/C++转到.Net/C#的程序员们,在最初可能非常不适应把char[]和string分开对待,但是一旦习惯了就会觉得非常方便,尤其是直接以+进行连接,以及支持switch...case等。
这个时候问题来了,string真的是immutable的吗?

cbrumme的blog上给出了一个例子:
using System;
using System.Runtime.InteropServices;
 
public class Class1
{
    
static void Main(string[] args)
    {
        String computerName 
= "strings are always immutable";
        String otherString 
= "strings are always immutable";
 
        
int len = 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,由于不在本文范围之内,就不进行说明了。