自动化数据类型

为了支持多种语言(以及每种语言扩展,比如UNICODE),微软已经定义了一种通用的数据类型给COM对象使用。微软叫他们“自动化数据类型”。(以后,我们将看到自动化这个名字是怎么来得。)其中的一个自动化数据类型是BSTR。BSTR是什么?它是一个特殊格式的字符串指针。每个字符占两个字节(也就是说是一个Short类型),并且他以一个表明接着有多少short类型数据的unsiged long作为前缀, 它一个short类型的0结束。它几乎可以适应每种语言中的字符串数据类型。但是它也意味着,一个C/C++应用程序或者我们的C对象本身有时也需要转换一个C字符串为一个以NULL结束的带长度前缀的UNICODE字符串。

为了说明,让我们看一下下面的ANSI C 字符串:

char MyString[] = "Some text";
首先,让我们把字符串分成单个字符的形式:

char MyString[] = {'S','o','m','e',' ','t','e','x','t', 0};
由于一个C字符串是以NULL结束,注意我们在数组的最后包括了一个0字节。

现在,我们需要把这个字符串转换成UNICODE。这也意味这个每个char编程short。我们需要把数组重新定义为short类型:

short MyString[] = {'S','o','m','e',' ','t','e','x','t', 0};
现在,我们需要以这个数组的字节数作为这个数组的开始。注意我们没有把结束符的0作为short计算在内。所以,我们得到的长度是9乘以每个short占两个字节数(注意Intel处理器是小尾顺序):

short MyString[] = {18, 0, 'S','o','m','e',' ','t','e','x','t', 0};
一个BSTR被定义成特殊格式的UNICODE字符串指针。事实上,它是一个指向这个UNICODE字符串第三个short的指针。所以,在这里我们声明一个BSTR型的变量,把它指向我们的字符串:

BSTR strptr;

strptr = &MyString[2];
现在我们把strptr传递给以BSTR作为参数的COM对象。并且如果我们对象的函数的一个参数是BSTR类型,strptr就是可以被传递的。

但在此之前你可能认为你必须像以上一样手动重新格式化所有的字符串,其实是不需要得。幸运的是,可以通过操作系统提供的MutiByteToWideChar函数转换一个ANSI C字符串到UNICODE字符串,还可以用SysAllocString和SysAllocStringLen分配一个Buffer来拷贝一个UNICODE字符串,用一个unsiged long在前面表明这个Buffer的大小。所以通过我们的原始char型字符串获得一个指向它的BSTR指针(忽略了错误检查),我们可以这么做:

DWORD len;
BSTR strptr;
char MyString[] = "Some text";

// 得到我们要转换的MyString为UNICODE所需要的UNICODE缓冲区的长度
len = MultiByteToWideChar(CP_ACP, 0, MyString, -1, 0, 0);

// 分配所需长度的UNICODE缓冲区。SysAllocStringLen也会分配一个以0结束的short类型
// 空间和unsiged long类型的计数。SysAllocStringLen会以len*sizeof(wchar_t)的值
// 来填充这个Unsiged long的值并且返回指向分配的缓冲区的第三个short数据的指针。
// 留给我们要做的只是简单的用一个我们的C字符串的UNICODE版本来填充这个缓冲区。
strptr = SysAllocStringLen(0, len);

// 转换MyString为UNICODE到用SysAllocStringLen分配的缓冲区中
MultiByteToWideChar(CP_ACP, 0, MyString, -1, strptr, len);

// strptr现在是一个指向自动化数据类型的字符串的指针(BSTR)
注意:释放者接着必须通过传递strptr给SysFreeString来释放通过SysAllocStringLen分配的缓冲区。一般的这个释放者是调用SysAllocStringLen来分配缓冲区的分配者。

注意由于一个BSTR指针在这个unsiged long长度的后面,并且总是以NULL为结束符的字符串,你可以把BSTR看作是一个宽字符(wchar_t)数据类型。例如,你可以通过把它传给lstrlenW来获得它作为宽字符的长度。但是也应该注意到,在这个字符串中嵌入0s(‘/0’字符)是完全可能的。如果你写一个入口参数是一个人性化易读的文本字符串,那么这个BSTR指向的内容不包含嵌入的0在里面是一个正确的假设。另一方面,如果这个BSTR指向一个二进制数据,那么这就不是一个好的假设了。既然这样,你能用SysStringLen检测这个宽字符字符的长度(或者你想得到有多少字节数可以用SysStringByteLen)。

也有其他“自动化数据类型”,比如一个LONG(也就是一个32位的值)。所以我们对象的函数不会局限于只传递自动化的字符串的参数。

实事上,一些语言支持传递参数数据类型可选择的函数概念。比如说我们有一个Print()函数,这个函数有一个可能是一个字符串指针(BSTR)也可能是一个LONG,或者可能是一个其他自动化数据类型,并且不论传入什么它都会打印。例如,如果传入一个BSTR,它会打印输出字符串的字符。如果传入一个LONG,它首先会做类似这样的调用:

sprintf(myBuffer, "%ld", thePassedLong)
…然后打印输入这个myBuffer的字符串结果。

这个Print函数需要通过某种方法知道传入的是一个字符串还是长整型数据。如果我们要把这个函数放入我们的对象中,我们可以通过运用另一个被称为VARIANT的自动化数据来实现。一个VARIANT是一个封装了另一个自动化数据类型的简单结构。(这是所有支持COM必须在内部理解和会创建的一种机构类型,简单来达到传递参数给一个对象的函数或者接收返回数据的目的)

一个VARIANT有两个成员。第一个成员叫vt,是一个标识你的封装的VARIANT是那种类型的数据的值。另一个成员是一个联合,存储这个数据。

所以,如果我们要封装一个BSTR到一个VARIANT结构中,我们需要置这个VARIANT的VT成员的值为VT_BSTR,存储我们的BSTR(也就是说,指向特殊格式UNICODE字符串的指针)到这个VARIANT的bstrVal成员中。如果我们要封装一个LONG,那么我们要把这个VARIANT的VT成员的值置成VT_I4,存储我们的long值到VARIANT的lVal成员中(由于它是一个联合,它会指向同一个数据像bstrVAL)。

总之,为了支持脚本语言,我们的对象的函数必须要写成只能传递一种叫自动化数据类型的参数。同样的,我们返回的数据也必须是以自动化数据类型为形式的数据。

如果你看我们的SetString函数定义,你会注意点一个问题。我们定义它传递的是一个这样的字符串指针:

static HRESULT STDMETHODCALLTYPE SetString(IExample *this, char *str)
这是一个标C的字符串,它不是一个自动化数据类型。我们需要把它替换为BSTR变量。

我们的GetString函数对于脚本语言也有一些麻烦。不但它被定义接受一个标C的字符串指针,而且调用者也没有提供一个UNICODE缓冲区,并且我们的函数修改了它的内容。这不是自动兼容的。当需要返回一个BSTR给脚本语言时,我们必须给这个字符串分配内存且把它传给脚本引擎。脚本引擎会在结束时调用SysFreeString来释放内存。

为了这些需求,最好的办法是改变我们IExample对象,使其buffer成员是一个BSTR。当脚本引擎传递一个BSTR给我们的SetString时,我们会复制一个这个字符串拷贝,把这个新BSTR存储到buffer成员中。我们然后修改我们的GetString函数使它接受一个由脚本引擎提供的指向一个BSTR(也就是一个句柄)的指针。我们会再创建一个我们的字符串拷贝,把它返回给脚本引擎(相信这个脚本引擎会释放它)。

所以我们不改变我们的原始代码,我拷贝这个IExample目录内容到一个IExample2的新目录中。我改变IExample.c为IExample2.c,IExample.h为IExample2.h,等等。

在IExample2.c中,让我们重命名我们的MyRealExample结构为MyRellExample2(为了区分于我们原来的代码),并且改变这个buffer成员的数据类型。(我们也把这个成员的名字改成“string”)。这是新的定义:

typedef struct {
IExample2Vtbl *lpVtbl;
DWORD count;
BSTR string;
} MyRealIExample2;
这是我们更新后的SetString和GetString函数:

static HRESULT STDMETHODCALLTYPE SetString(IExample2

*this, BSTR str)
{
// 检查调用者传递的字符串
if (!str) return(E_POINTER);

// 首先,释放我们分配的旧的BSTR
if (((MyRealIExample2 *)this)->string)
SysFreeString(((MyRealIExample2 *)this)->string);

// 构造一个调用者字符串的拷贝并把存贮这个新的BSTR
if (!(((MyRealIExample2 *)this)->string =
SysAllocStringLen(str, SysStringLen(str))))
return(E_OUTOFMEMORY);

return(NOERROR);
}

static HRESULT STDMETHODCALLTYPE GetString(IExample2 *this, BSTR *string)
{
// 检查调用者传递的句柄
if (!string) return(E_POINTER);

// 创建一个我们的字符串拷贝并把这个BSTR放在它的句柄中返回,调用者负责释放它
if (!(*string = SysAllocString(((MyRealIExample2 *)this)->string)))
return(E_OUTOFMEMORY);

return(NOERROR);
}
同样的,我们的Release函数必须在我们释放我们的MyRealIExample2前正确得释放分配的string:

if (((MyRealIExample2 *)this)->string)
SysFreeString(((MyRealIExample2 *)this)->string);
GlobalFree(this);
最后,当我们的IclassFactory创建了一个MyRealIExample2时,它应该清除这个string成员:

((MyRealIExample2 *)thisobj)->string = 0;
当然,我们需要更新IExample2.h中的这些函数定义:

// 额外函数
STDMETHOD (SetString) (THIS_ BSTR) PURE;
STDMETHOD (GetString) (THIS_ BSTR *) PURE;
我们现在已经更新了我们的对象函数使用自动化数据类型。

你可能感兴趣的:(String,脚本,存储,buffer,语言,引擎)