注冊(cè) 登錄
Office中國(guó)論壇/Access中國(guó)論壇 返回首頁(yè)

ganlinlao的個(gè)人空間 http://m.mzhfr.cn/?230471 [收藏] [復(fù)制] [分享] [RSS]

日志

Freebasic菜鳥初學(xué)Freebasic基礎(chǔ)教程九:了解com數(shù)據(jù)類型

已有 4225 次閱讀2017-5-11 10:34 |個(gè)人分類:FreeBasic| FreeBasic教程, Freebasic入門, Freebasic基礎(chǔ), FreeBasic教程, Freebasic入門

FB在windows上使用,有兩種東西是無(wú)法回避的,一種是api,一種是com,這是幾乎所有在windows上使用的語(yǔ)言都必須用到的。
這是引用自csdn上的博客內(nèi)容:
了解一下com數(shù)據(jù)類型,有助于在FB中使用com。

BSTR、
BSTR到底是什么。

BSTR是COM中的數(shù)據(jù)類型,在COM編程時(shí),接口中定義的字符串類型都是BSTR類型,

而使用BSTR類型是極其容易出錯(cuò)的,同時(shí),一不小心就有可能造成內(nèi)存泄露。所以有如下建議:

  1. 在對(duì)接口進(jìn)行實(shí)現(xiàn)時(shí),從接口參數(shù)中傳遞過來(lái)的BSTR類型的變量,一定要在第一時(shí)刻將BSTR類型轉(zhuǎn)變成_bstr_t類型的變量,就是因?yàn)?font color="#0000F0">BSTR有隱藏的危險(xiǎn),同時(shí)當(dāng)使用BSTR出現(xiàn)bug時(shí),而這種bug而不一定好找;
  2. 在對(duì)接口進(jìn)行實(shí)現(xiàn)時(shí),接口中的BSTR字符串參數(shù)是out時(shí),在接口實(shí)現(xiàn)的內(nèi)部不要定義BSTR類型變量,而是定義_bstr_t類型的變量,進(jìn)行操作,操作完成以后,然后在轉(zhuǎn)為BSTR類型的變量傳出去。

既然是這樣,就有人要問了,那么BSTR存在的必要是什么?

這個(gè)是由COM決定的,由于COM是跨系統(tǒng)及不同開發(fā)語(yǔ)言間實(shí)現(xiàn)互操作的技術(shù),常規(guī)以NULL結(jié)尾的簡(jiǎn)單字符串在COM組件間傳遞不太方便。

所以,BSTR就這么出現(xiàn)了。BSTR作為指針類型,標(biāo)準(zhǔn)的BSTR是一個(gè)有長(zhǎng)度前綴和NULL結(jié)束符的OLECHAR數(shù)組。BSTR的前4個(gè)字節(jié)是一個(gè)

表示字符串長(zhǎng)度的前綴。BSTR長(zhǎng)度域的值是字符串的字節(jié)數(shù),但不包括字符串結(jié)束符。BSTR實(shí)際上包含的是Unicode串,

所以字符數(shù)是字節(jié)數(shù)的一半。

所以,在能不使用BSTR的情況下,就盡量不要使用BSTR類型,而是使用對(duì)應(yīng)的_bstr_t類型。為了處理BSTR,Microsoft提供了以下API供使用:

BSTR    SysAllocString(const OLECHAR * psz);
INT     
SysReAllocString(BSTR* pbstr,const OLECHAR* psz);
BSTR   
SysAllocStringLen(const OLECHAR * strIn,UINTui);
INT     
SysReAllocStringLen(BSTR* pbstr,constOLECHAR* psz,unsignedintlen);
void   SysFreeString
(BSTR bstrString);
 
UINT  SysStringLen(BSTR);
UINT  SysStringByteLen(BSTR bstr);
BSTR
SysAllocStringByteLen(LPCSTRpsz,UINTlen);

現(xiàn)在對(duì)以上的
API逐一的進(jìn)行講解和應(yīng)用,大家可以結(jié)合著MSDN,看這篇博文,至少MSDN講的比我這里更詳細(xì)。
SysAllocString
分配內(nèi)存,并創(chuàng)建BSTR字符串;
SysReAllocString
重新分配內(nèi)存,并將第二個(gè)參數(shù)指定的OLECHAR同時(shí)放入新開辟的的內(nèi)存中;
SysAllocStringLen
分配內(nèi)存,將第一個(gè)參數(shù)指定的字符串的前ui(第二個(gè)參數(shù)指定的字符個(gè)數(shù))個(gè)數(shù)放入開辟的內(nèi)存中;
 
SysReAllocStringLen,經(jīng)過上面兩個(gè)API的講解,這個(gè)API的就不需要更多的講解了;
SysFreeString釋放由以上的API函數(shù)開辟的內(nèi)存;
SysStringLen
表示的是BSTR的字符個(gè)數(shù);
SysStringByteLen
獲得BSTR字符串表示的字節(jié)數(shù),也就是BSTR的前4個(gè)字節(jié)表示的內(nèi)容;
SysAllocStringByteLen
使用的是ANSI string進(jìn)行創(chuàng)建BSTR對(duì)象,盡量少用該API。VARIANT

VARIANT結(jié)構(gòu)體主要是使用在COM(組件對(duì)象模型)中用于傳遞參數(shù)使用,

它的存在主要是為了保持一個(gè)在COM參數(shù)傳遞方法的統(tǒng)一性,它幾乎包含了所有普通常用類型的數(shù)據(jù)類型的傳遞,

如整型,浮點(diǎn)型,布爾型等等,以及相應(yīng)類型的指針類型,如整型指針。

它的使用也比較方便。先來(lái)看看這個(gè)結(jié)構(gòu)體它的結(jié)構(gòu):

typedef struct tagVARIANT { 
union { 
struct __tagVARIANT { 
VARTYPE vt; 
WORD    wReserved1; 
WORD    wReserved2; 
WORD    wReserved3; 
union { 
LONGLONG            llVal; 
LONG                lVal; 
BYTE                bVal; 
SHORT               iVal; 
FLOAT               fltVal; 
DOUBLE              dblVal; 
VARIANT_BOOL        boolVal; 
_VARIANT_BOOL       bool; 
SCODE               scode; 
CY                  cyVal; 
DATE                date; 
BSTR                bstrVal; 
IUnknown            *punkVal; 
IDispatch           *pdispVal; 
SAFEARRAY           *parray; 
BYTE                *pbVal; 
SHORT               *piVal; 
LONG                *plVal; 
LONGLONG            *pllVal; 
FLOAT               *pfltVal; 
DOUBLE              *pdblVal; 
VARIANT_BOOL        *pboolVal; 
_VARIANT_BOOL       *pbool; 
SCODE               *pscode; 
CY                  *pcyVal; 
DATE                *pdate; 
BSTR                *pbstrVal; 
IUnknown            **ppunkVal; 
IDispatch           **ppdispVal; 
SAFEARRAY           **pparray; 
VARIANT             *pvarVal; 
PVOID               byref; 
CHAR                cVal; 
USHORT              uiVal; 
ULONG               ulVal; 
ULONGLONG           ullVal; 
INT                 intVal; 
UINT                uintVal; 
DECIMAL             *pdecVal; 
CHAR                *pcVal; 
USHORT              *puiVal; 
ULONG               *pulVal; 
ULONGLONG           *pullVal; 
INT                 *pintVal; 
UINT                *puintVal; 
struct __tagBRECORD { 
PVOID       pvRecord; 
IRecordInfo *pRecInfo; 
} __VARIANT_NAME_4; 
} __VARIANT_NAME_3; 
} __VARIANT_NAME_2; 
DECIMAL             decVal; 
} __VARIANT_NAME_1; 
} VARIANT, *LPVARIANT, VARIANTARG, *LPVARIANTARG; 

這個(gè)結(jié)構(gòu)體呢,有5個(gè)成員,分別是 VARTYPE  vt ,WORD wReserved1,WORD wReserved2,WORD wReserved3,和最后一個(gè)共用體。

其中vt用以指明最后一個(gè)共用體中哪一個(gè)成員有效,wReserved1,wReserved2,wReserved3,這三個(gè)我們使用的時(shí)候不用管,系統(tǒng)保留,

最后一個(gè)共用體根據(jù)vt的提示,對(duì)相應(yīng)的成員進(jìn)行值的存儲(chǔ)。我們從兩個(gè)不同的角度來(lái)理解,首先是使用VARIANT來(lái)存儲(chǔ)參數(shù),

首先是聲明一個(gè)這個(gè)結(jié)構(gòu)體的對(duì)象,然后對(duì)對(duì)象的vt進(jìn)行賦值,它可接受的值是一個(gè)枚舉值,也就說只能在枚舉這個(gè)范圍內(nèi)取值,

比如我要用VARIANT傳遞一個(gè)整數(shù),現(xiàn)在我對(duì)vt的賦值為VT_INT,這樣就說明了我要使用這個(gè)結(jié)構(gòu)體中共用體的整型變量,

接著對(duì)INT變量進(jìn)行賦值,賦我們要傳遞的值。這樣就完成VARIANT的傳遞。現(xiàn)在我們從另外一個(gè)角度來(lái)理解VARIANT,

剛才是我們對(duì)VARIANT對(duì)象進(jìn)行賦值傳遞,現(xiàn)在我們是這個(gè)VARIANT對(duì)象的接收者,我們從參數(shù)中獲得這個(gè)對(duì)象之后,

我們首先檢查這個(gè)結(jié)構(gòu)體的vt成員,看它哪個(gè)類型的變量有效,比如就這個(gè)例子而言,檢查到vt的值是VT_INT,

因此,我直接去獲取這個(gè)結(jié)構(gòu)體中VT_INT所對(duì)應(yīng)的變量,獲取它的值。這樣,我們從傳遞到使用兩個(gè)角度來(lái)理解了VARIANT結(jié)構(gòu)體,

概括起來(lái)說,就是vt指明了我要傳遞的變量的類型,結(jié)構(gòu)體中共用體的成員用來(lái)存儲(chǔ)vt指明的類型的值。
下面來(lái)看看具體VARIANT結(jié)構(gòu)體是如何使用賦值的,首先是第一種方法:


首先我們聲明一個(gè)VARIANT結(jié)構(gòu)體的對(duì)象vr1,然后使用VariantInit函數(shù)對(duì)其進(jìn)行初始化,它的作用就是對(duì)vt賦VT_EMPTY,

對(duì)別的變量值附空,否則就是一個(gè)隨機(jī)值,這個(gè)過程和我們聲明一個(gè)int變量一樣,如果聲明的時(shí)候不初始化,就是一個(gè)隨機(jī)值。

編程過程中,不管是指針還是什么變量,都應(yīng)該在聲明之后對(duì)其進(jìn)行初始化。接著就是我賦VT_INT給vt,

這里的V_VT就是代表要對(duì)vr1結(jié)構(gòu)體中的vt成員進(jìn)行幅值,接著對(duì)vr1中的INT成員賦值,這里的V_INT就是表示要對(duì)INT賦值,

因此這里有一個(gè)規(guī)律,就是V_,它的后面加成員類型就可以對(duì)相應(yīng)的成員賦值,而這里的成員類型有一個(gè)對(duì)照表,在文章的最后給出,

比如,我要對(duì)BSTR成員賦值,我就用V_BSTR。這時(shí)候,就可以使用vr1了,使用完成之后,我們應(yīng)該調(diào)用VariantClear函數(shù),

這個(gè)函數(shù)的作用就是將vt賦值為VT_EMPTY,以及釋放使用這個(gè)結(jié)構(gòu)體中的內(nèi)存中的內(nèi)容,如果是com對(duì)象,該函數(shù)是不會(huì)進(jìn)行對(duì)象的release操作的,

不管怎么樣,我們都應(yīng)該在使用完了VARIANT結(jié)構(gòu)體之后,調(diào)用這個(gè)函數(shù)。
下面是第二種方法,原理和過程和地中方法類似,有一點(diǎn)微小的區(qū)別,

就是VARIANT結(jié)構(gòu)體的賦值上面來(lái)說,有點(diǎn)不同,如下:


接下來(lái)是字符串的操作,BSTR,我們不能直接將字符串傳遞一個(gè)VARIANT結(jié)構(gòu)體對(duì)象,而是要用到函數(shù)SysAllocString,

它的返回值就是一個(gè)BSTR,由它分配一個(gè)字符串,供VARIANT,對(duì)于為什么要這么做,可以自己查看MSDN COM的Automation那部分:

完成之后,我們還應(yīng)該調(diào)用SysFreeString來(lái)釋放由SysAllocString分配的內(nèi)存。
另外,我們可以在類型與變量的對(duì)照表中發(fā)現(xiàn),同一類型,對(duì)應(yīng)了兩種不同的變量,如,INT對(duì)應(yīng)了變量有intVal和pintVal,其實(shí)這很簡(jiǎn)單,后者是指針性,如果要使用,說明我們賦值的對(duì)象是一個(gè)指針,如下:

最后給出類型與變量的對(duì)照表,如果是使用地中方法用V_加類型,就直接使用下表中VT_后的名稱就可以了:

Member nameDescription
VT_EMPTYIndicates that a value was not specified.
VT_NULLIndicates a null value, similar to a null value in SQL.
VT_I2Indicates a short integer.
VT_I4Indicates a long integer.
VT_R4Indicates a float value.
VT_R8Indicates a double value.
VT_CYIndicates a currency value.
VT_DATEIndicates a DATE value.
VT_BSTRIndicates a BSTR string.
VT_DISPATCHIndicates an IDispatch pointer.
VT_ERRORIndicates an SCODE.
VT_BOOLIndicates a Boolean value.
VT_VARIANTIndicates a VARIANT far pointer.
VT_UNKNOWNIndicates an IUnknown pointer.
VT_DECIMALIndicates a decimal value.
VT_I1Indicates a char value.
VT_UI1Indicates a byte .
VT_UI2Indicates an unsigned short .
VT_UI4Indicates an unsigned long .
VT_I8Indicates a 64-bit integer.
VT_UI8Indicates an 64-bit unsigned integer.
VT_INTIndicates an integer value.
VT_UINTIndicates an unsigned integer value.
VT_VOIDIndicates a C style void .
VT_HRESULTIndicates an HRESULT.
VT_PTRIndicates a pointer type.
VT_SAFEARRAYIndicates a SAFEARRAY. Not valid in a VARIANT.
VT_CARRAYIndicates a C style array.
VT_USERDEFINEDIndicates a user defined type.
VT_LPSTRIndicates a null-terminated string.
VT_LPWSTRIndicates a wide string terminated by null Nothing nullptr a null reference (Nothing in Visual Basic) .
VT_RECORDIndicates a user defined type.
VT_FILETIMEIndicates a FILETIME value.
VT_BLOBIndicates length prefixed bytes.
VT_STREAMIndicates that the name of a stream follows.
VT_STORAGEIndicates that the name of a storage follows.
VT_STREAMED_OBJECTIndicates that a stream contains an object.
VT_STORED_OBJECTIndicates that a storage contains an object.
VT_BLOB_OBJECTIndicates that a blob contains an object.
VT_CFIndicates the clipboard format.
VT_CLSIDIndicates a class ID.
VT_VECTORIndicates a simple, counted array.
VT_ARRAYIndicates a SAFEARRAY pointer.
VT_BYREFIndicates that a value is a reference.

VARIANT使用起來(lái)是很簡(jiǎn)單的,但是有些問題,我們還必須要去注意:

  • 建立VARIANT變量時(shí),必須使用VariantInit進(jìn)行初始化;
  • 對(duì)于VT_UI1, VT_I2, VT_I4, VT_R4, VT_R8, VT_BOOL, VT_ERROR, VT_CY, VT_DECIMAL, 和VT_DATE這些類型,
  • 數(shù)據(jù)的值是直接儲(chǔ)存在VARIANT結(jié)構(gòu)中的,當(dāng)VARIANT的類型發(fā)生變化時(shí),指向這些數(shù)據(jù)的指針會(huì)變得無(wú)效。
  • 例如: VARIANT varParam;::VariantInit(&varParam);short iVal =20; varParam.vt = VT_I2;
  • varParam.iVal = iVal;short*pVal =&varParam.iVal; varParam.vt = VT_I4; varParam.lVal =200;
  • 此時(shí),*pVal指向的值與你期望的可能是不一樣的。
  • 對(duì)于VT_BYREF | any type數(shù)據(jù)類型,而VARIANT只是擁有這些數(shù)據(jù)指向內(nèi)存的指針,而內(nèi)存的釋放需要由函數(shù)的調(diào)用者負(fù)責(zé)。
  • 例如: VARIANT varParam;::VariantInit(&varParam);int*p =newint;*p =10; varParam.vt = VT_BYREF | VT_I4;
  • varParam.plVal =(long*)p;delete p;// If you donnot delete the memory, this may cause memory leak
  • 對(duì)于VT_BSTR類型,在VARIANT中的字符串必須要用SysAllocString進(jìn)行分配內(nèi)存,當(dāng)釋放時(shí),或者VARIANT的類型發(fā)生改變時(shí),
  • 都需要調(diào)用SysFreeString進(jìn)行內(nèi)存釋放,否則就會(huì)發(fā)生內(nèi)存泄露;
  • 對(duì)于VT_ARRAY | any type類型,這個(gè)規(guī)則和VT_BSTR是類似的,在VARIANT中的數(shù)組必須使用SafeArrayCreate進(jìn)行開辟內(nèi)存空間,
  • 然后必須使用SafeArrayDestroy進(jìn)行內(nèi)存空間的釋放;
  • 對(duì)于VT_DISPATCH和VT_UNKNOWN,我們考慮的更多的就是引用計(jì)數(shù)器的增加與減少了,
  • 是的,在進(jìn)行賦值時(shí)需要進(jìn)行引用計(jì)數(shù)的增加,釋放時(shí),則需要對(duì)應(yīng)的減少。
SAFEARRAY的使用

SAFEARRAY的主要目的是用于automation中的數(shù)組型參數(shù)的傳遞,我們都知道,在網(wǎng)絡(luò)環(huán)境中,數(shù)組是不能直接傳遞的,

所以我們必須將數(shù)組封裝成SAFEARRAY類型,這樣才能進(jìn)行傳遞,在COM編程時(shí),SAFEARRAY類型是可以存放在VARIANT類型中,

指定vt為VT_ARRAY|*或者VT_BYREF|VT_ARRAY。對(duì)于SAFEARRAY,說白了,就是普通的數(shù)組,添加了一些額外的說明,

當(dāng)我第一次遇到這個(gè)類型時(shí),也是有點(diǎn)恐懼的,后來(lái),用慣了,也就無(wú)所謂了。SAFEARRAY單獨(dú)用的時(shí)候很少,就像我前面說的,

一般都是搭配著VARIANT一起使用,指定vt類型以后,parray成員就是指向SAFEARRAY的指針。SAFEARRAY中元素的類型可以

是VARIANT能封裝的任何類型,包括VARIANT類型本身。

訪問SAFEARRAY

訪問SAFEARRAY的方法大體上有兩種:

  1. 使用SafeArrayAccessData方法;
  2. 使用SafeArrayGetElement和SafeArrayPutElement方法。

關(guān)于這兩種方法,在上面的例子中都有涉及。

HRESULT 函數(shù)返回值

每個(gè)人在做程序設(shè)計(jì)的時(shí)候,都有他們各自的哲學(xué)思想。拿函數(shù)返回值來(lái)說,就有好多種形式。

函數(shù)返回值返回值信息
double sin(double)

浮點(diǎn)數(shù)值

計(jì)算正玄值
BOOL DeleteFile(LPCTSTR)

布爾值

文件刪除是否成功。如失敗,需要GetLastError()才能取得失敗原因
void * malloc(size_t)

內(nèi)存指針

內(nèi)存申請(qǐng),如果失敗,返回空指針 NULL
LONG RegDeleteKey(HKEY,LPCTSTR)

整數(shù)

刪除注冊(cè)表項(xiàng)。0表示成功,非0失敗,同時(shí)這個(gè)值就反映了失敗的原因
UINT DragQueryFile(HDROP,UINT,LPTSTR,UINT)

整數(shù)

取得拖放文件信息。以不同的參數(shù)調(diào)用,則返回不同的含義:
一會(huì)兒表示文件個(gè)數(shù),一會(huì)兒表示文件名長(zhǎng)度,一會(huì)兒表示字符長(zhǎng)度
...... ......

...

...... ......

如此紛繁復(fù)雜的返回值,如此含義多變的返回值,使得大家在學(xué)習(xí)和使用的過程中,增加了額外的困難。好了,COM 的設(shè)計(jì)規(guī)范終于對(duì)他們進(jìn)行了統(tǒng)一。組件API及接口指針中,除了IUnknown::AddRef()和IUnknown::Release()兩個(gè)函數(shù)外,其它所有的函數(shù),都以 HRESULT 作為返回值。大家想象一個(gè)組件的接口函數(shù)比如叫Add(),完成2個(gè)整數(shù)的加法運(yùn)算,在C語(yǔ)言中,我們可以如下定義:

  1. long Add( long n1, long n2 )  
  2. {  
  3. return n1 + n2;  
  4. }  

還記得剛才我們說的原則嗎?COM 組件是運(yùn)行在分布式環(huán)境中的。也就是說,這個(gè)函數(shù)可能運(yùn)行在“地球另一邊”的計(jì)算機(jī)上,既然運(yùn)行在那么遙遠(yuǎn)的地方,就有可能出現(xiàn)服務(wù)器關(guān)機(jī)、網(wǎng)絡(luò)掉線、運(yùn)行超時(shí)、對(duì)方不在服務(wù)區(qū)......等異常。于是,這個(gè)加法函數(shù),除了需要返回運(yùn)算結(jié)果以外,還應(yīng)該返回一個(gè)值------函數(shù)是否被正常執(zhí)行了。

  1. HRESULT Add( long n1, long n2, long *pSum )  
  2. {  
  3. 3*pSum = n1 + n2;  
  4.  return S_OK;  
  5. }  

如果函數(shù)正常執(zhí)行,則返回 S_OK,同時(shí)真正的函數(shù)運(yùn)行結(jié)果則通過參數(shù)指針返回。如果遇到了異常情況,則COM系統(tǒng)經(jīng)過判斷,會(huì)返回相應(yīng)的錯(cuò)誤值。常見的返回值有:

HRESULT含義
S_OK0x00000000成功
S_FALSE0x00000001函數(shù)成功執(zhí)行完成,但返回時(shí)出現(xiàn)錯(cuò)誤
E_INVALIDARG0x80070057參數(shù)有錯(cuò)誤
E_OUTOFMEMORY0x8007000E內(nèi)存申請(qǐng)錯(cuò)誤
E_UNEXPECTED0x8000FFFF未知的異常
E_NOTIMPL0x80004001未實(shí)現(xiàn)功能
E_FAIL0x80004005沒有詳細(xì)說明的錯(cuò)誤。一般需要取得 Rich Error 錯(cuò)誤信息(注1)
E_POINTER0x80004003無(wú)效的指針
E_HANDLE0x80070006無(wú)效的句柄
E_ABORT0x80004004終止操作
E_ACCESSDENIED0x80070005訪問被拒絕
E_NOINTERFACE0x80004002不支持接口

圖一、HRESULT 的結(jié)構(gòu)

HRESULT 其實(shí)是一個(gè)雙字節(jié)的值,其最高位(bit)如果是0表示成功,1表示錯(cuò)誤。具體參見 MSDN 之"Structure of COM Error Codes"說明。我們?cè)诔绦蛑腥绻枰袛喾祷刂,則可以使用比較運(yùn)算符號(hào);switch開關(guān)語(yǔ)句;也可以使用VC提供的宏:

  1. HRESULT hr = 調(diào)用組件函數(shù);  
  2. if( SUCCEEDED( hr ) ){...} // 如果成功  
  3. ......  
  4. if( FAILED( hr ) ){...} // 如果失敗  
  5. ...... 


評(píng)論 (0 個(gè)評(píng)論)

facelist doodle 涂鴉板

您需要登錄后才可以評(píng)論 登錄 | 注冊(cè)

QQ|站長(zhǎng)郵箱|小黑屋|手機(jī)版|Office中國(guó)/Access中國(guó) ( 粵ICP備10043721號(hào)-1 )  

GMT+8, 2025-7-13 03:13 , Processed in 0.070356 second(s), 17 queries .

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

返回頂部