最近灵感之源大哥常常在MSN中向我倾诉移植VB6代码的痛苦过程。我一看他的代码——好家伙,这都是原先在VB6种最高深的用法。什么不安全的指针啊,复制内存啊,一应俱全。调用平台的时候指针是少不了的,而VB.NET又不支持,所以麻烦常来,确实让人感到痛苦。我建议他用C#或者MC++,可是他愿意更纯的VB,那只好我来做了。为了能够让指针操作得以在VB中进行,必须对他们进行封装,泛型在这里是个很好的选择。而C#根本不允许对泛型的类型参数使用指针,那我们只有C++/CLI了。

C++/CLI支持两种托管的指针:内部指针和顶指针。内部指针(stdcli::language::interior_ptr)是一个指向托管堆中对象的动态指针。当托管堆中对象发生移动时,内部指针可以同步得到更新。内部指针具有很到的灵活性,他可以和C++本地指针进行类型转换,还可以进行指针运算。定指针(stdcli::language::pin_ptr)也是在一种指向托管堆对象的指针,被定指针所指的对象将被“定”在内存中,CLR不能移动它在内存中的位置,因此定指针可以用来向非托管的代码传递托管对象的指针,它能确保非托管代码操作的时候对象不会移动。最令人欣喜的就是,无论是定指针还是内部指针,他们都能对泛型的类型参数进行操作。所以我们可以封装一段代码来操作指针:

ref class PointerHelper sealed
{
    private:

    PointerHelper(void)
    {
    }
    public:

    generic<typename T> where T : System::ValueType
    static T GetValue(IntPtr address)
    {
        return *(interior_ptr<T>)(void *)address;
    }

    generic<typename T> where T : System::ValueType
    static void UnsafeSetValue(IntPtr address, T value)
    {
        interior_ptr<T> ptr = (interior_ptr<T>)(void*)address;
        *ptr = value;
    }

    generic<typename T>where T : System::ValueType
    static IntPtr UnsafeGetAddress(T %value)
    {
        pin_ptr<T> pp = &value;
        return IntPtr((void*)pp);
    }

};

我也不太确定这段代码会是什么效果,来试验一下。这是一段VB的代码:

Dim x As Integer
Dim p As IntPtr = PointerHelper.UnsafeGetAddress(x)

PointerHelper.UnsafeSetValue(p, 123)

MsgBox(x.ToString)

运行下看x的值,真的变了。相信有了这个,灵感之源所遇到的痛苦应该可以减弱一点了吧。至少从指针里获取指的操作不需要复制内存来做了。以前无法获取变量的地址现在也可以做到了。灵感之源只需要耐心等到VS2005发布,就可以轻松使用这段代码,逃离苦海了。

不过……这样使用终归是不安全的。VB对指针的类型没有任何检查和约束,我也没办法把这个东西做成强类型的。如果有人愿意,他完全可以这样写

Dim x As Long
Dim p As IntPtr = PointerHelper.UnsafeGetAddress(x)

PointerHelper.UnsafeSetValue(p, 123.0F)

MsgBox(PointerHelper.GetValue(Of Guid)(p).ToString)

这完全是可以运行的,把123.0F的浮点数塞到整型变量的地址里,然后按照Guid取出……不难想象若这种东西滥用起来,就会再次恢复到VB6那种“大师级”无法阅读、维护的代码了。