《VARIANT自动化技术.docx》由会员分享,可在线阅读,更多相关《VARIANT自动化技术.docx(6页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、Skyline开发中的VARIANT参数类型在二次开发中,无论是针对什么软件平台,甚或于在所有的软件开发活动中,人们都只在做一件事情:进行接口调用。用Win32 API写代码的人关注的是函数,写WinForm的人关注整套Framework的调用,针对Skyline做二次开发的我们则更关注COM接口的使用。 在这个过程中,调用规则是所有开发人员高度关注的。这里所说的调用规则主要包括:名称,参数,返回值。 Skyline中的调用规则有其一定的特殊性,因为Skyline提供了一整套COM自动化接口,调用的名称既可以是接口方法,也可以是DISPATCH ID,调用参数中又有几乎无处不在,却又令人无比陌
2、生的类型,大多数的返回值不是某个接口的对象指针,就是HRESULT类型。以上这些对于将整套系统架构在C/S之上的开发人员来说都会产生一定的困惑,无论你使用的是C+还是C#,或者别的什么开发语言,都无法回避这一点。也许使用C#进行二次开发会很方便,快速,但明白些内部机制,对程序出错后的debug是相当有益的。基于此,我计划介绍一下Skyline开发中的参数类型。随便翻翻Skyline的开发文档就会发现,除过那些普通不起眼的小角色以外(我指的是类似于double, long这样的常规类型),出镜率最高的当属VARIANT类型和BSTR类型了,这两种类型也是COM开发中的常见类型。在这篇文章中我会全
3、面介绍一下VARIANT类型,因此如果你不了解TE,可借此熟悉这一在COM相关开发中大量使用的数据类型。如果你是一名TE开发人员,那么你还会发现我的重点是解决一些开发中的问题,例如构造多维数组类型的VARIANT变量以及如何访问该变量的数据等等话题。在COM发展出来之后,虽然它标榜自己跨语言,分布式,但人们发现,这里所谓的跨语言仅仅是对那些含有指针机制的语言所说的,对于脚本类型的语言仍然无法使用,因此在COM的基础上又发展出了自动化(Automation),如何在各种类型的语言之间传递数据,或者说,各种开发语言如何以一套相同的调用规则去使用组件。这便是VARIANT类型应运而生的背景。因为它能
4、够存储任意类型的数据。在VS的开发工具内,在VARIANT类型上点击”go to definition”,可以看到它的原型。大致是这个样子:struct tagVARIANT VARTYPE vt; union short iVal; / VT_I2. long lVal; / VT_I4. float fltVal; / VT_R4. double dblVal; / VT_R8. DATE date; / VT_DATE. BSTR bstrVal; / VT_BSTR. short * piVal; / VT_BYREF|VT_I2. long * plVal; / VT_BYREF|V
5、T_I4. float * pfltVal; / VT_BYREF|VT_R4. double * pdblVal; / VT_BYREF|VT_R8. DATE * pdate; / VT_BYREF|VT_DATE. BSTR * pbstrVal; / VT_BYREF|VT_BSTR. ;可以看出,VARIANT是个结构体,该变量的类型保存在vt中,含有的不同类型的值保存在union中。我们注意到vt的类型是VARTYPE,这也是一个结构,借助开发工具,我们也能看到它的定义。例如long 是VT_I4,double 是VT_R8,long类型的指针是VT_BYREF与VT_I4的组合。
6、下面通过一个简短的程序来演示一下它的基本用法。#include#includeusing std:cout;using std:endl;int main() VARIANT v1; VariantInit(&v1); v1.vt = VT_R8; v1.dblVal = 0.0001; cout v1.dblVal endl; VariantClear(&v1); return 0;从程序中可以看到,VARIANT类型使用起来很麻烦,使用前要初始化,用完后还要清空,一不留神也许就会出现错误。如果有面向对象编程经验,也许你会将这个类型封装为一个类,在构造函数中调用VariantInit,在析构
7、函数中调用VaraiantClear进行清理。幸运的是,微软已经封装好了这样一个类:_variant_t,上述程序在使用了_variant_t类型后代码如下:#include#includeusing std:cout;using std:endl;int main() _variant_t v1 = 0.0001; cout v1.dblVal endl; return 0;有了_variant_t类型之后,我们就可以申请一个该类型的变量,甚至不用初始化,就可以作为参数使用,这篇文章中的代码就是一个很好的例子。有的时候,一些函数,比如CreatePolyline函数中需要一个VARIANT类
8、型,但得是一个数组。自动化技术中的数组使用的是SAFEARRAY,它同样是一个结构体,与普通数组最大的区别就是这种数组有”自我意识”,所谓的自我意识就是,它知道所保存数据的维度,知道数组大小,知道是否还在被使用(内部有引用计数),与VARIANT类型一样,也有一套函数作为SAFEARRAY的支撑操作存在着,下面我以一个小程序来演示如何构建一个数组类型的_variant_t变量。#include#include#includeusing std:cout;using std:endl;using std:vector;int main() vectorvec; for(int i = 1; i
9、11; i+) vec.push_back(i * 0.0001); SAFEARRAY* psa = NULL; SAFEARRAYBOUND rgsabound1; rgsabound0.lLbound = 0; rgsabound0.cElements = vec.size(); psa = SafeArrayCreate(VT_R8,1,rgsabound); for(long i = 0; i 10; i+) SafeArrayPutElement(psa,&i,reinterpret_cast(&veci); _variant_t va; va.vt = VT_ARRAY | VT
10、_R8; va.parray = psa; long size = va.parray-rgsabound0.cElements; for(long i = 0; i size; i+) double tmp = 0.0; SafeArrayGetElement(va.parray,&i,reinterpret_cast(&tmp); cout tmp ; cout endl; return 0;程序中首先创建一个SAFEARRAY的指针变量,然后声明一个SAFEARRAYBOUND类型的数组,这个数组用来指定SAFEARRAY每个维度的元素数目,如果有两个维度,那么就声明含有两个元素的SAF
11、EARRAYBOUND数组,每个维度的元素数目在cElements域中指定。这段代码中我只使用一维数组,所以SAFEARRAYBOUND数组中只有一个元素。然后使用函数SafeArrayCreate创建,如果成功,它会返回一个SAFEARRAY的地址,到此为止,我们的数组中都还没有数据,所以接下来用SafeArrayPutElement填充数据。最后我创建一个SAFEARRAY数组类型的_variant_t变量。 VT_ARRAY|VT_R8表示该变量中保存的是double型的数组,parray域保存SAFEARRAY的地址。代码的最后部分演示了如何取用SAFEARRAY中的数据。这样我们就有
12、了一个包含SAFEARRAY的VARIANT变量,可如何在TE中使用呢?空间某点的信息至少是三维,甚至多维数组才能表示的。也许通过前面的简短介绍你还是一头雾水,甚至去网上搜索看到的也几乎全是对一维数组构造VARIANT变量的介绍,下面我就用TerraExplorer Pro V6中的创建剖面功能来演示一下二维数组的VARIANT变量如何建立。创建剖面在TE V6中使用如下函数定义:HRESULT CreateTerrainProfile(VARIANT arrPoints)它的参数是一个数组型的VARIANT,数组的每一项是都是一个Point,我们创建一个4行2列的常规数组,接着声明一个含有两
13、个元素的SAFEARRAYBOUND数组rgsabound,在rgsabound0中指定其cElements为4,在rgsabound1中指定cElements为2,然后创建一个二维的SAFEARRAY,代码如下:void CTEST_TEPV6View:OnTerrainProfile() SAFEARRAY* psa; SAFEARRAYBOUND rgsabound2; rgsabound0.lLbound = 0; rgsabound0.cElements = 4; rgsabound1.lLbound = 0; rgsabound1.cElements = 2; psa = Safe
14、ArrayCreate(VT_R8,2,rgsabound); assert(psa); long index2 = 0,0; double point42=100.0,200.0,300.0,500.0,400.0,900.0,500.0,100.0; HRESULT hr; for(int i = 0; i 4; i+) index0 = i; for(int j = 0; j Analysis-CreateTerrainProfile(pt); assert(SUCCEEDED(hr);这里要注意的是向SAFEARRAY中填充数据的过程,SafeArrayPutElement有三个参数,
15、其中第一个参数是要填充的数组的首地址,在这里就是指针psa,第二个参数也是一个指针,但是它是一个索引向量的指针,每一个索引指示一个维度,那么这个向量的指针说白了就相当于一个内存偏移量,如果你要在二维数组的哪个位置存入数据,那么这个索引就是那个位置的值,比如在本例中,因为是二维数组,所以首先声明一个包含两个元素的索引数组index2,如果此时你要在第1行第3列存入数据,那么就将index0指定为行号0,然后将index1指定为列号2,并将index数组的地址传入函数才行。在代码中我使用循环来为SAFEARRAY存入数据,所以就得不停的修正索引的值。第三个参数是欲传入数组的值的地址。以上的初始化过
16、程完毕后,我们新建一个_variant_t类型的变量,并设置正确的类型,然后传入函数就可以了。大部分函数调用都是传入一个参数的值,但有时也会从函数传出一个值,比如在TE V6实现屏幕录像时用到的函数GetAvailableCodecs,它会返回一个SAFEARRAY的数组,这种情况下,仍然是新建一个_variant_t类型的变量,调用完成后,该变量会自动被设置为包含SAFEARRAY数组。我们根据文档中的说明,可以得知该数组的维数,如果是1维的,那么可以通过vt.parray-rgsabound0.cElements获得数组长度,如果是二维的,那么还要得到rgsabound1来得到cEleme
17、nts,以此确定二维数组的大小。下面的代码演示了读取编码信息的功能,更多细节参看:TE V6中屏幕录像功能的实现_variant_t vtCodeInfo;HRESULT hr = m_ptt-AviWriter-raw_GetAvailableCodecs(&vtCodeInfo);assert(SUCCEEDED(hr);ICodecInfo6* pCodec = NULL; /vtCodeInfo是从GetAvailableCodes()返回的VARIANT类型变量/下面这行代码用于获取该数组的大小unsigned int size = vtCodeInfo.parray-rgsabou
18、nd0.cElements;/下面这个向量用于存放编码器名称std:vector codeDesp;/用于存放编码器的IDispatch指针std:vector codeInfo; for(unsigned int i = 0; i size;i+) _variant_t tmp; long index = static_cast(i); hr = SafeArrayGetElement(vtCodeInfo.parray,&index,&tmp); assert(SUCCEEDED(hr); pCodec = reinterpret_cast(tmp.pdispVal); std:wstring nmtmp(pCodec-GetName(); codeDesp.push_back(nmtmp); codeInfo.push_back(tmp.pdispVal);总结一下,有了_variant_t类型,我们基本上可以把它当做普通参数变量来看待了,如果是传入值的参数,一定要事前设置类型和值,从函数传出值的参数,就没有这个必要了。如果要使用SAFEARRAY,特别是多维数组的情况,要小心设置和读取SAFEARRAYBOUND中的值。