前言:
当从一个用C++语言写的COM组件中向一个BASIC客户端程序传递一个基本类型,比如BSTR、LONG、VARIANT_BOOL等,是很方便地,直接将参数设定为此种类型就行了。但很多情况下,需要向客户端传递一个用户自定义的类型(从C语言的角度来讲,就是一个用struct定义的结构),或者从客户端得到一个这样的类型,这个时候,如果只是简单地把参数设定为这种类型,是无法完成传递的。这是因为类似于BASIC这样的语言虽然支持COM,支持自动化,但它无法识别任意一个C语言定义的结构。为了能够在C与BASIC之间准确无误地传递用户自定义类型,需要将这种类型转化为一种自动化类型。
现在先定义几个在本文中使用的术语(注意,我在这里对这几个术语说明,只是基于我对这几个术语在使用上的理解,与标准定义有一定的偏差):
1、 用户自定义类型(即UDT,User Define Type):
从C语言的角度而言,就是一个用struct关键字定义的一个结构;从BASIC的角度而言,就是一个用Type…End Type定义的一个用户定义类型;从内存的角度而言,就是几个基本类型(或者其它已定义的用户定义类型)的有序组合。
2、 自动化类型:
在用户自定义类型的基础之上,对它进行了一定的加工,包括:
1) 为这个用户自定义类型指定一个GUID;
2) 将用户自定义类型编译到一个类型库中;
3) 在系统注册表中登记这个类型库,同时注册这样的用户自定义类型;
注意:并不是所有的用户自定义类型都可以转化为自动化类型,其前提条件是这个用户自定义类型的所有成员必须是自动化支持的类型,比如char *就不是自动化支持的类型,因此如果自定义类型中包含了这种类型,则无法转化为自动化类型。
3、 封装自动化类型:
自动化类型仍然不能够直接通过接口成员函数的参数向客户端传递,或者由客户端直接向组件传递,而必须把它封装到一个VARIANT变量中。COM的服务器端与客户端之间传递自动化类型都是通过VARIANT变量这个中介进行的。
好,现在我再来说明一下自动化类型的传递过程,它分为导出和接收两个部分(注意,这里的导出与接收都是针对服务器而言的)
1、 导出一个自动化类型(这是一个打包的过程):
1) 定义一个VARIANT变量,指定这个VARIANT变量的vt成员的值是VT_RECORD,标明这个VARIANT中包含了一个自动化类型的实例;
2) 让这个VARIANT变量的pRecInfo成员指向一个IRecordInfo接口指针,这个接口指针与被包含的自动类型实例是直接关联的(它所引用的GUID值就是被包含的自动化类型的GUID值,可以使用这个GUID值调用API函数GetRecordInfoFromXXX()来得到IRecordInfo接口),可以通过它来读取类型库,从而得到被包含的自动化类型的所有有用信息。
3) 让这个VARIANT变量的pvRecord成员指向自动化类型实例的地址,其实就是指向了自定义类型的实例的地址。
4) 将这个VARIANT变量作为参数向客户端传递。
2、 接收一个自动化类型(这是一个解包的过程):
A、 验证这个VARIANT变量的vt成员的值是不是VT_RECORD;
B、 通过这个VARIANT的pRecInfo成员得到一个IRecordInfo接口指针,通过这个接口指针可以判断等待接受VARIANT的值的一个自动化类型实例是否和这个VAIRANT内包含的自动化类型一致;
C、 通过VAIRANT的成员pvRecord得到UDT实例的地址,把这个UDT实例中包含的值赋于准备接受VARIANT值的同类型的UDT,从而实现了VARIANT的解包。
可见,在传递上,自动化类型是通过VARIANT这种结构作为中介来进行导出与接收的,因为VARIANT变量是所有支持自动化类型的语言都能识别的,所以通过这个中介,可以实现跨平台。
根据上面的叙述,在COM服务器与客户端之间传递自定义类型实际上就是一个打包与解包的过程,这中间包括查找类型库,获取自动化类型的IRecordInfo接口等繁琐但过程基本一致的工作,因此我创建了一个类来实现打包与解包,以简化这个过程。
这个类是一个模板类:UDT<T>
模板参数T就是UDT类型的名称。
这个类要实现以下几个功能:
1、 可以很方便地将任意一个struct类型T包装为一个自动化类型。
2、 将任意一个UDT<T>的实例打包到一个VARIANT中;
3、 从任意一个包含有自动化类型的合法的VARIANT中解包出UDT<T>实例;
4、 实现UDT<T>类型的实例与常规的struct类型T之间的转换与赋值;
5、 实现两个同类型的UDT<T>实例之间的相互赋值;
6、 检测两个UDT<T>实例是否则同种类型;
7、 可以象使用简单的struct类型T一样地使用UDT<T>,可以在任何可以使用T的地方用UDT<T>代替。
下面是对这个类的详细说明:
模板类UDT<T>:
综述:
从本质上讲,本类是对用户自定义类型T的一个封装,将每一个自定义类型T绑定几个已经定义好的函数,这些函数可以完成比如获取IRecordInfo接口指针、将T转换为VARIANT类型、从VARIANT类型中获取T等工作。
UDT<T>自身包含T的所有成员变量(这是因为UDT<T>类本身就是T的一个派身类),加上本类对“&”操作符进行了重载,因而可以在任何使用T类型的变量(包括指针、实例、引用等)的地方使用UDT<T>类型的变量。这一点,与任何使用VARIANT变量的地方可以使用CComVariant变量一样。
值得注意的是,本类为了尽可能的实现安全地传递数据,实现了几个重载“=”操作符,使得对UDT<T>类实例进行赋值时,都是以拷贝的方式进行,即如果赋值方指向了某一资源,则被赋值方指向的是这个资源的副本,而不会与赋值方指向同一地址。这样可以保证赋值方与被赋值方各自拥有对各自资源的控制权。例如:
假设已经向系统注册了下面的一个自动化类型:
struct Student{
BSTR Name;
INT Age;
}
func()
{
UDT<Student> udtSt1,udtSt2;
Student st;
VARIANT varSt;
st.Name=SysAllocString(“FlyingLeaf”);
st.Age=30;
udtSt1=st; //st.Name与udtSt1.Name指向不同的BSTR
udtSt2=udtSt1; //udtSt1.Name与udtSt2.Name指向不同的BSTR
udtSt2=NULL; //自动释放udtSt2.Name,且udtSt2.Age=0,即初始化所有成员
udtSt1.CopyTo(varSt); //让varSt包含一个udtSt1的副本
udtSt2=varSt; //udtSt2.Name与varSt中的Name不是同一BSTR
}
另外,本类与普通的struct类型T不同的一点是,当普通类型T的实例超出作用域之后,不会自动释放其成员占用的资源(比如上面的成员BSTR Name),而默认情况下,UDT<T>的实例在超出作用域后,会自动释放其成员占用的资源。如果要改变这一点,使UDT<T>与T一样,不自动释放资源,可以在源代码中定义宏“_UDT_DISABLE_AUTO_CLEAR”
1、 成员变量:
只有一个成员变量:
private IRecordInfo * m_pRecordInfo;
存放与实例相关联的自动化类型的IRecordInfo接口。正常情况下,只要使用除默认构造函数之外的其它几个构造函数来构造一个实例,一旦实例构造成功,这个值就不为空(NULL),如果它为空,说明构造失败。构造之后的任何赋值操作不会改变它的值。实例被卸载后,析构函数会自动释放这个接口。
2、 构造函数:
有五个构造函数,除默认构造函数之外的前两个根据已注册的类型信息来构造一个空的UDT,此时UDT的所有成员都是空值或零,后两个分别根据一个已存在的UDT<T>实例或一个同类型的VARIANT实例构造一个UDT副本:
1) UDT()
默认构造函数,使用默认构造函数创建一个实例之后,一定要调用四个Init()函数之一来初始实例,然后才能使用。在调用Init()之前,被构造的实例就相当于一个普通的T结构,即它的成员变量m_pRecordInfo没有被赋值,其值为NULL,因此不能够使用UDT<T>的成员函数及重载操作符,只有经过初始化之后,才能使用这个成员函数及重载操作符(如果成功)。
默认构造函数主要用于全局变量及类成员变量,这样它们可以在运行时动态地被指定为某一类型。
一般不推荐用默认构造函数来构造实例,因为类型是未知的,其成员变量处于不稳定状态(在调用初始化函数Init之前)。
以下的四个带参数的构造函数是通过各种类型的参数,来获得正确的IRecordInfo接口,并向成员变量m_pRecordInfo赋值。
2) UDT(OLECHAR *wsTlbName,REFGUID guidUDT);
参数:
wsTlbName:包含自动化类型注册信息的类型的文件路径;
guidUDT:自动化类型的GUID值;
3) UDT(REFGUID guidTlb,
REFGUID guidUDT,
ULONG ulTlbMajorVer=1,
ULONG ulTlbMinorVer=0,
LCID lcid=LOCALE_USER_DEFAULT);
参数:
guidTlb:类型库的GUID值;
guidUDT:自动化类型的GUID值;
ulTlbMajorVer:类型库的主版本号;
ulTlbMinorVer:类型库的次要版本号;
lcid:类型库的区域说明标识;
备注:
如果构造失败,会激活调试警告(ATLASSERT),同时m_pRecordInfo==NULL。所以如果要检查构造是否成功,除通过调试来检查外,还可以在构造语句之后,使用成员函数GetIRecordInfo来得到m_pRecordInfo,然后根据它是否为努NULL来判断构造是否成功。
如果构造成功,实例的T部分所有成员的值被初始化为NULL或零。
4) UDT(UDT<T> &sourceUDT);
参数:
sourceUDT:一个已经存在的UDT<T>实例;
备注:
根据已经存在的UDT<T>实例来构造一个副本,它与上面两个构造函数的区别在于:
1、 不需要传入类型库及自动化类型的GUID值等信息,直接从参数sourceUDT中获取IRecordInfo接口,所以比较方便,同时少了读库文件的IO操作,因此速度较快;
2、 构造完成后,实例的T部分的所有成员的值等于sourceUDT的对应的成员的值,注意,被构造的实例的T部分只是sourceUDT的T部分的一个副本,即如果成员中有指针,而指针指向的是不同的资源(如BSTR、接口等),则这些资源会自动被复制为一个副本,被构造的实例的成员指向的是这些副本,这保证了每一个资源只对应一个支配者,从而确保本类在资源的使用和释放方面的安全性。
3、 用户应该保证参数sourceUDT的类型与模板参数T的类型完全一致。
如果构造失败,m_pRecordInfo==NULL。
5) UDT(VARIANT &sourceVar);
参数:
sourceVar:已经存在的包含自动化类型实例的VARIANT变量,该VARIANT所包含的自动化类型应该与类型T相匹配。
与第3个构造函数一样,它根据sourceVar中包含的IRecordInfo接口设置被构造的实例的IRecordInfo接口,被构造的实例是参数sourceVar中包含的自定义类型的实例的一个副本。调用者应该保证参数sourceVar中包含的IRecordInfo接口与模板参数T相匹配,否则结果未知.
如果构造失败,m_pRecordInfo==NULL.
上面四个构造函数的共同点是:
A、 如果构造失败,成员变量m_pRecordInfo==NULL,可以通过公共函数GetIRecordInfo()来获得m_pRecordInfo的值,从而判断构造成功与否(在调试状态会发出错误警告)。
B、 如果构造失败,则实例的T部分的数据是未知的,也就是说是未经初始化的,从而是不安全的,这个时候去使用它们,有可能导致程序崩溃。如果要建立高度安全的代码,在构造之后用上面的办法来检查构造是否成功则是必要的。当然,如果你能确定传入的参数绝对正确,并且认为不会发生内存不足的情况,也可以不作检查。
C、 如果构造成功,实例的T部分的数据肯定是经过了初始化,或者等于有效的值(由后两个构造函数中的参数的值保证),并且m_pRecordInfo是指向正确的IRecordInfo接口。
D、 使用上面的任何一个构造函数之后,在实例的生存期内,m_pRecordInfo不会再改变,直到实例被卸载后,它会被自动释放。因此对于任何一个已经构造的UDT<T>实例(除由默认构造函数构造的之外),其类型在构造时就已经确定了,之后无法改变。
3、 析构函数:
UDT<T>::~UDT();
析构函数做两件事:
1、 如果m_pRecordInfo不为空,则释放IRecordInfo接口;
2、 如果没有定义宏“_UDT_DISABLE_AUTO_CLEAR”,则自动释放本实例的T部分的所有成员所指向的资源。如果定义了这个宏,则不自动释放其成员指向的资源,调用者可以象使用普通的T类型一样,自行释放其成员指向的资源。
4、 成员函数:
公有成员函数:
1、 四个初始化函数:
BOOL Init(OLECHAR *const wsTlbName,REFGUID guidUDT);
BOOL Init(REFGUID guidTlb,
REFGUID guidUDT,
ULONG const ulMajorVer=1,
ULONG const ulMinorVer=0,
LCID const lcid=LOCALE_USER_DEFAULT);
BOOL Init(UDT<T> &sourceUDT);
BOOL Init(VARIANT &sourceVar);
这四个初始化函数是与上面除默认构造函数之外的四个带参数构造函数相对应的。用于实始化UDT<T>的实例,初始化之后,如果成功,就可以使用本类的所有其它函数来实例向VARIANT或T类型实例任意转化、传递数据等。如果没有初始化(特指使用默认构造函数来创建实例的情况),则不能使用本类的其它函数,否则发出调试警告。
注意,只当使用默认构造函数来构造实例之后,才使用这四个初始化函数,如果是使用其它四个构造函数来构造实例,就不应再进行初始化了。
使用Init初始化的好处是比较灵活,可以在一个函数中构造一个实例,而在另一个函数中对它进行初始化;缺点是如果在初始化之前去使用这样的实例,可能导致未知的错误。
2、 RecordInfo * GetIRecordInfo():
获得m_pRecordInfo指针,如果需要通过IRecordInfo来直接处理一些工作,可以使用这个函数。
备注:
这个函数返回IRecordInfo*时,并未在这个接口上执行AddRef(),实际上,它只是简单地把m_pRecordInfo返回给调用者。因此,如果调用者要把返回的接口指针对外赋值,必须先对它调用AddRef()。
3、 T* operator &()
返回实例的T部分的指针。而不是返回实例的指针。
这是为了实现在任何可以使用类型T的地方,均可用本类来代替(你可能会问:如果不是取值,而需要直接传递实例的T部分该怎么办呢?很简单,因为UDT<T>是T的派生类,所以可以用在需要T参数的地方,并自动转换为作为父类的T类型。本重载操作符的实质,是屏蔽掉了私有成员m_pRecordInfo,从而传递了一个纯粹的T结构指针)。
4、 UDT<T>& UDT<T>::operator =(T &OriginType)
重载“=”操作符,使本类的实例的T部分成为参数OriginType的一个副本(并不是指向同一资源)。
用户千万要避免以下的使用方法:
struct BookInfo{
BSTR BookName;
UINT BookID;
BSTR BorrowerName;
};
void ErrFunc()
{
UDT<BookInfo> Book(LIBID_LibManagerLib,UDTID_BookInfo); //自动化类型
BookInfo OneBook; //自定义类型
OneBook.BookID=100;
OneBook.BookName=SysAllocString(L"老人与海");
//OneBook.BorrowerName未赋初值,它正指向一个无效地址!!!
//OneBook.BorrowerName=NULL; 如果加上此句就是正确的
Book=OneBook; //出错
……
}
这段代码的错误在于没有给成员BorrowerName赋初值,而将它赋值自动化类型Book,所以出错(自动化类型中的值不是为空值,就是有效的数据,与C不一样,C中的指针可能指向无效地址)。这个错误是一个很容易犯的错误,如果不知道这个原因,还很难发现,因此一定要注意。
备注:
赋值之前,存放在实例中T部分的成员所指向资源会被自动释放掉。
5、 UDT<T>& UDT<T>::operator =(UDT<T> &otherUDT)
重载“=”操作符,使实例的T部分成为参数otherUDT的T部分的一个副本(并不是指向同一资源)。
备注:
赋值之前,存放在实例中T部分的成员所指向资源会被自动释放掉。
如果参数otherUDT的类型与本实例的类型不一致(GUID值一致),则会产生调试警告,但由于本重载函数没有返回值,因此如果不在调试状态,是无法检查出赋值成功与否的,因此用户应注意不要使参数otherUDT的类型与本实例的类型不一致。
6、 UDT<T>& UDT<T>::operator =(VARIANT& otherVar)
重载“=”操作符,使实例的T部分成为参数otherVar包含的自定义类型T的一个副本(并不是指向同一资源)。
备注:
赋值之前,存放在实例中T部分的成员所指向资源会被自动释放掉。
如果参数otherVar包含的自动化类型与本实例的类型不一致(GUID值一致),则会产生调试警告,但由于本重载函数没有返回值,因此如果不在调试状态,是无法检查出赋值成功与否的,因此用户应注意不要使参数otherUDT的类型与本实例的类型不一致。
7、 UDT<T>& UDT<T>::operator =(INT VALUE_NULL)
如果令实例=NULL,则相当于调用了Clear()函数,会释放实例拥有的所有资源。
注:
所有重载的“=”操作符都会自动释放原来由被赋值方所指向的资源。因此它们是安全的,其返回值都是实例本身。
8、 BOOL UDT<T>::CopyTo(VARIANT &var)
BOOL UDT<T>::CopyTo(VARIANT *pVar)
将本实例所包含的数据拷贝到VARIANT变量中。
成功返回TRUE,失败返回FALSE。
备注:
如果参数var中事先已经包含了数据(即其vt成员不等于VT_EMPTY),则本函数会自动调用VariantClear()来清除它及释放其中包含的任何资源。因此如果在调用方代码中,传递给参数var的变量中包含了有用数据,应该先把数据存放在其它位置,则否这个数据会丢失。
函数执行成功后,var中将包含本实例中T部分的一个副本。并且其pRecInfo成员在指向的IRecordInfo接口之时,也已经自动将计数加了1。所以参数var中的数据及其生存周期与本实例没有任何关系。
9、 BOOL UDT<T>::CopyTo(T &OriginType)
BOOL UDT<T>::CopyTo(T *pOriginType)
将本实例的数据拷贝到一个T类型的实例中。
运行成功返回TRUE,失败返回FALSE。
备注:
与上面的所有拷贝与赋值函数一样,执行成功后,参数得到的是一个副本,与本类的实例没有任何关联,可以独立使用及释放。值得注意的是,本函数与赋值函数是有区别的,如:
struct Student{
BSTR Name;
INT Age;
}
Student OriginT;
UDT<Student> udtT;
…
OriginT=udtT; ………………………… (1)
udtT.CopyTo(OriginT);……………… (2)
代码(1)与代码(2)的执行结果是完全不同的:
对于代码(1),执行完后,OriginT.Name与udtT.Name指向的是同一个地址;
对于代码(2),执行完后,OriginT.Name与udtT.Name指向的是不同的地址,但字符串的内容相同;
由于无法对Student的“=”操作符进行重载(不可能对所有struct都重载“=”号),所以要将本实例的数据以复制的方式赋予一个T的实例,必须使用本函数。
另外,也是很重要的一点,在调用本函数之前,必须事先自行释放通过参数传入的T实例中的成员所指向的资源。本函数不会释放参数的成员指向的任何资源,只是在代码的开始,通过IRecordInfo的RecordInit函数来初始化参数OriginType,RecordInit函数不会自动释放T中的任何资源,只是让T的所有成员指向空值。因此,如果事先不自行释放它们,这些资源会就无法销毁,从而导致内存泄漏。这种情况很难通过调试发现,因为运行起来,就好象什么错误都没有发生一样,所以要特别引起注意——从编程习惯上来讲,还没有释放一个变量所指向的资源,就让这个变量去接受其它的资源,本身就是一个错误的做法。
10、 BOOL CopyFrom(const VARIANT &var)
BOOL CopyFrom(const VARIANT *pVar)
这两个函数与上面重载的“=”操作符的作用十分类似,事实上,上面重载的“=”操作就是调用此函数来实现拷贝赋值的,它们的唯一区别在于,重载的“=”没有返回值,因而无法判断赋值是否成功,而只能在不成功的时候发出调试警告。而这两个函数则可以通过返回值告诉调用者赋值是否成功。
成功返回TRUE,不成功返回FALSE;
导致赋值不成功的原因只有三个:
1、 参数不合法:包括参数的类型与实例的类型不一致、参数本身就不完整(如VARIANT的pRecInfo指针无效等);
2、 实例没有被正确的构建,所以对它的所有赋值都肯定是错的;
3、 内存空间不足。
前两个原因主要是由于编程人员的疏忽造成,除此之外则是由于客户端传入的参数不合法。编程人员的疏忽是可以通过程序的调试来解决的,所有的重载“=”操作符中的调试警告就是用来解决这个问题的。但是从客户端会传入什么样的参数,即使在组件发布之后,也无法完全确定下来,所以本类除了重载“=”操作符之外,还提供这样的一个CopyFrom函数,其目的就是提供编程人员一个可编程的手段,当服务器与客户端之间的参数传递成功时,后面的代码如何写,如果失败了,程序又走哪一条路。如果仅仅提供重载操作符这一种方法,是无法解决这个问题的。
至于内存空间不足该怎么办,这个是本类未作处理的,除调试外,无法从代码中进行判断。
没有提供类似CopyTo的针对其它类型参数的重载,因为从外界获取实例都是通过VARIANT,而UDT<T>及自定义的T都是模块内使用的,可能通过调试的方式发现错误。
11、 BOOL IsMatchingType(IRecordInfo *pRecInfo)
BOOL UDT<T>::IsMatchingType(UDT<T> &otherUDT)
BOOL UDT<T>::IsMatchingType(UDT<T> *pOtherUDT)
BOOL UDT<T>::IsMatchingType(VARIANT &otherVar)
BOOL UDT<T>::IsMatchingType(VARIANT *pOtherVar)
第一个函数判断一个IRecordInfo接口是否与本实例所拥有的IRecordInfo接口一致。后面四个判断参数中包含的自定义类型是否与本实例的类型一致。
一致返回TRUE,不一致返回FALSE;
如果参数不合法或本实例未构建成功,返回FALSE,同时产生调试警告;
如果指针参数为NULL,返回FALSE。
12、 void UDT<T>::Clear()
释放本实例的T部分的所有成员指向的所有资源。
本类的析构函数就是通过调用它来释放实例所占用的资源。
注意本函数并不会让内部成员m_pRecordInfo=NULL,实例的成员本身所占用的空间亦未被收回,只是这些成员占用的资源被收回了而已(如:接口被释放,BSTR字符串被释放等),但其中的非指向资源类型的成员(如LONG,INT类型)的值不变,因此在Clear之后,本类的实例还可以继续使用。
13、 void UDT<T>::InitValue()
与Clear()函数一样,会释放本实例中的T部分的所有成员指向的资源,但Clear()不会改变T的成员中非资源类型成员的值(比如INT,LONG之类的基本类型),而本函数则把它们全部清为零值。
命中实间UDTFunc:
这个命中空间中的函数有两个作用:
A、_GetIRecordInfoFromTlb(…)与_GetIRecordInfoFromGuid(…)两个函数是用来被UDT<T>类的成员调用,以获得IRecordInfo接口的;
B、VAR2UDT(…)与UDT2VAR(…)函数是用来更灵活地实现简单类型T与VARIANT之间的相互转换。因为这两个函数不需要构造一个UDT<T>实例就可以实现两种类型之间的转换,当然,缺点是没有该类的赋值功能。
1、IRecordInfo * _GetIRecordInfoFromTlb(OLECHAR *wsTlbName,REFGUID guidUDT)
得到一个自定义类型的IRecordInfo接口指针
参数:
wsTlbName,存有自定义类型的定义的类型库的名称(包含路径);
guidUDT,自定义类型的GUID值的引用(一般而言,须在客户程序中定义一个GUID变量存放此GUID值,不能通过#import从类型库中导入,可能是ATL尚不指供此功能)。
返回值:
返回类型库中指定的GUID值的自定义类型的IRecordInfo接口指针。如果指定的自动化类型不存在,则返回NULL;
2、IRecordInfo * _GetIRecordInfoFromGuid(REFGUID guidTlb,
REFGUID guidUDT,
ULONG ulTlbMajorVer=1,
ULONG ulTlbMinorVer=0,
LCID lcid=LOCALE_USER_DEFAULT)
得到一个自定义类型的IRecordInfo接口指针
参数:
guidTlb,类型库的GUID值的引用
guidUDT,自定义类型的GUID值的引用
ulTlbMajorVer,类型库主版本号
ulTlbMinorVer,类型库次版本号
lcid,LCID值。
返回值:
返回类型库中指定的GUID值的自定义类型的IRecordInfo接口指针。如果指定的自动化类型不存在,则返回NULL;
注意:上面的两个函数成功返回一个IRecordInfo接口之后,该接口上的计数值为2,而不是1,其中一个是应该占用的一个计数,另一个是为什么会存在尚不清楚,但我估计是API函数内部对IRecordInfo也加了一个计数。但是反过来,如果马上再对这个接口调用Release,则计数会变成0,而不是1。这个原因可能是IRecordInfo的Release函数中自行把上面多出的一个计数也给减掉了
3、 BOOL VAR2UDT(T &targetUDT,
VARIANT const &sourceVar,
IRecordInfo *const pRecInfo=NULL)
从一个VARIANT变量中获取自动化类型的信息,并将它赋予一个简单的T类型的实例。
如果没有指定IRecordInfo接口指针,则自动从参数sourceVar中获取IRecordInfo接口,并自动比较sourceVar中包含的自定义类型是否与T一样,如果不一样则返回FALSE,如果一样,则对参数targetUDT正确赋值,这个赋值同样是拷贝赋值,targetUDT得到的是sourceVar指向的资源的副本。如果指定了参数pRecInfo,则先会比较它所代表的类型与sourceVar中的自定义类型的是否一致(类型的GUID值一致),如果不一致就直接返回FALSE,如果一致,再执行拷贝赋值。
调用本函数之前,注意先把参数targetUDT所引用的资源释放掉,其道理同UDT<T>类的CopyTo函数。
IRecordInfo接口的计数不变。
成功返回TRUE,失败返回FALSE。
4、 BOOL UDT2VAR(VARIANT &targetVar,
T const& sourceUDT,
IRecordInfo *const pRecInfo)
这个函数与上面的函数的功能正好相反,用于将一个简单的T类型转化了一个VARIANT类型,同样是创建一个副本。但这个函数要指定IRecordInfo接口。
如果参数targetVar已经指向了有效的资源,则会自动释放它。
IRecordInfo接口的计数加一,由参数targetVar保管。以后调用VariantClear会自动释放。
成功返回TRUE,失败返回FALSE。
这个类的源代码见:
http://asp2.6to23.com/flyleaf/Article/UDT_Code.htm
使用说明见:
http://asp2.6to23.com/flyleaf/Article/UDT_HowToUse.htm
示例说明见:
http://asp2.6to23.com/flyleaf/Article/UDT_Example.htm
示例及源码下载:
http://asp2.6to23.com/flyleaf/Code/LibManager.rar
恳请各位朋友测试与批评!!!