有個問題,64位COM組件調用32位COM組件(進程外)的時候:
32位組件裏引入_IDTExtensibility2接口,64調用的時候失敗;
如果不用引入的方法,而把接口函數寫到IDL裏,則調用成功。
比較奇怪。
(關鍵是接口函數有SAFEARRAY**類型的參數,讓系統默認轉就失敗。
返回的錯誤時加載DLL模塊失敗,其實是代理存根調用有異常,可能轉
SAFEARRAY的時候有異常,但最最原始的根源還不能確定)
--------------------------------------------------------------------------------------------
注意 safearray 做參數,在idl裏寫比較特殊。以下這篇博客比較全。
http://blog.sina.com.cn/s/blog_72ce76690100qkde.html
1 使用SafeArray
SafeArray是VB中的數組存儲方式。通過SafeArray,可以在VC++和VB間相互調用。SafeArray也是Automation中的標準數組存儲方式。
1.1 SafeArray處理函數
COM提供了一套API用於處理SafeArray。爲了保證程序和SafeArray結構無關,程序中建立、讀取、更改和釋放SafeArray都應該通過這些API進行,而不應該直接讀寫SafeArray結構。
下面介紹常用的SafeArray處理函數。
1.1.1 建立SafeArray
SAFEARRAY* SafeArrayCreate(
VARTYPE vt,
unsigned intcDims,
SAFEARRRAYBOUND *rgsabound
);
SAFEARRAY SafeArrayCreateEx(
VARTYPE vt,
unsigned int cDims,
SAFEARRRAYBOUND * rgsabound
PVOID pvExtra
);
SAFEARRAY* SafeArrayCreateVector(
VARTYPE vt,
long lLbound,
unsigned int cElements
);
SAFEARRAY* SafeArrayCreateVectorEx(
VARTYPE vt,
long lLbound,
unsigned int cElements,
LPVOID pvExtra
);
SafeArrayCreate於建立多維普通數組。SafeArrayCreateEx用於建立多維自定義類型或接口指針數組。SafeArrayCreateVector用於建立一維普通數組。SafeArrayCreateVectorEx用於建立一維自定義類型或接口指針數組。
1.1.2 釋放數組
HRESULT SafeArrayDestroy(
SAFEARRAY * psa
);
SafeArrayDestroy用於釋放創建的SafeArray數組。
1.1.3 訪問數據
HRESULT SafeArrayAccessData(
SAFEARRAY *psa,
void HUGEP** ppvData
);
HRESULT SafeArrayUnaccessData(
SAFEARRAY * psa
);
SafeArrayAccessData函數返回數組的指針。而SafeArrayUnaccessData釋放通過SafeArrayAccessData所取得的指針。
1.2 SafeArray相關處理
1.2.1 創建SafeArray數組
創建SafeArray可以使用COM提供的四個創建函數之一。所有的創建函數都返回一個SafeArray指針。通過這個指針可以讀寫SafeArray中的數據。SafeArray使用完後必須釋放。
1. SafeArrayCreateVector
SAFEARRAY* SafeArrayCreateVector(
VARTYPE vt,
long lLbound,
unsigned int cElements
);
這個函數用來創建簡單類型的一維數組。這個函數有三個參數:vt是數組類型、lLbound是數組下界值(最小下標)和數組長度。vt的取值如下表:
vt值 類型
VT_UI1 無符號1字節整數(BYTE)數組
VT_UI2 無符號2字節整數(WORD)數組
VT_UI4 無符號4字節整數(DWORD)數組
VT_UINT 無符號整數(UINT)數組
VT_INT 有符號整數(INT)數組
VT_I1 有符號1字節整數數組
VT_I2 有符號2字節整數數組
VT_I4 有符號4字節整數數組
VT_R4 IEEE 4字節浮點數(float)數組
VT_R8 IEEE 8字節浮點數(double)數組
VT_CY 8字節定點數貨幣值數組
VT_BSTR VB字符串數組
VT_DECIMAL 12字節定點數(大數字)數組
VT_ERROR 標準錯誤編號數組
VT_BOOL 布爾值數組
VT_DATE 日期型數組
VT_VARIANT VB Variant類型數組
lLbound是數組的最小下標,可以是取負數。cElements是數組的長度。數組的最大下標的值是最小下標加上數組長度減一。
SafeArrayCreateVector函數返回SafeArray結構的指針。
2. SafeArrayCreateVectorEx
SAFEARRAY* SafeArrayCreateVectorEx(
VARTYPE vt,
long lLbound,
unsigned int cElements,
LPVOID pvExtra
);
這個函數用於創建自定義類型或COM對象的SafeArray數組。和SafeArrayCreateVector類似,SafeArrayCreateVector也有類型、下界和長度的三個參數。SafeArrayCreateVectorEx還增加了一個參數pvExtra。
pvExtra的含義和vt的取值有關。當vt的取值在上表中的時候,pvExtra的取值沒有作用。當vt取值VT_RECORD時,SafeArrayCreateVectorEx返回一個自定義類型(結構structure或聯合union)的數組。這時,pvExtra必須是一個指向IRecordInfo的指針。
當vt取值是VT_UNKNOWN或VT_DISPATCH時。pvExtra是一個指向IID(接口GUID)的指針。在目前的COM規範中,pvExtra只能是IID_IUnknown和IID_IDispatch。並且必須和vt的取值一致。
a. 創建自定義類型數組
當vt是VT_RECORD時。pvExtra必須是一個IRecordInfo指針。絕大多數情況下,我們從TLB中取得自定義類型的IRecordInfo指針。以下是取得IRecordInfo的代碼:
IRecordInfo * pRecordInfo;
hr = GetRecordInfoFromGuids(
LibID,
MajorVer,
MinorVer,
LOCALE_USER_DEFAULT,
TypeGUID,
&pRecordInfo);
上述代碼中,LibID是所TLB的GUID,MajorVer和MinorVer分別是TLB的主、次版本號,TypeGUID是自定義結構的GUID。
函數返回的是IRecordInfo接口的指針。
b. 創建COM對象數組
當需要創建COM數組時,可以使用IUnknown指針,也可以用IDispatch指針。如果需要使用其它指針類型,應該使用QueryInterface方法取得,而不能直接在數組中保存。因爲SafeArray數組的序列化程序只能處理IUnknown和IDispatch兩種指針類型,如果在數組中放其它接口類型的指針,可能在跨套間使用中會出現問題。
1.2.2 讀取和寫入SafeArray數組。
讀寫SafeArray數組時。應該使用COM提供的標準API。COM提供了大量函數用於SafeArray數組的操作,本文中僅使用其中的兩個函數,SafeArrayAccessData和SafeArrayUnaccessData,和一些輔助用的函數。實際上是用這兩個函數就可以進行所有的數組操作了。其它的函數用於對單個元素的操作,由於使用不多,而且效率也不高,所以本文中不進行說明。
1. SafeArrayAccessData
這個函數用於獲取SafeArray的數據指針,並鎖定SafeArray數組的數據。在取得了數據指針之後,就可以直接訪問SafeArray數組中的數據了。
如果數組類型是Type,那麼所取得的數據指針實際上就是Type類型的數組的地址。在多維數組的情況下,必須把多個維度的下標轉換成一維下標進行訪問。
2. SafeArrayUnaccessData
這個函數的作用是對SafeArray數據解鎖,解鎖後,就不應該繼續對數據指針進行讀寫訪問。如果要訪問,必須重新獲取並鎖定數據。
3. 確定數組結構
在訪問數組之前,必須知道數組中數據的類型,、維數以及每個維度的下界和長度。COM提供了取得這些數組參數的函數。
取得類型,返回“VT_”開頭的類型枚舉值:
HRESULT SafeArrayGetVartype (
SAFEARRAY* pSA,
VARTYPE *pVarType);
取得維數,返回數組的維數:
UINT SafeArrayGetDim (
SAFEARRAY* pSA);
取得每個維度的屬性,返回指定維數(nDim)的上界和下界(nDim從1開始):
HRESULT SafeArrayGetLBound (
SAFEARRAY* pSA,
UINTnDim,
long *pLBound);
HRESULT SafeArrayGetUBound (
SAFEARRAY* pSA,
UINTnDim,
long *pUBound);
取得自定義類型接口,對於自定義結構數組,返回自定義結構類型數據的指針:
HRESULT SafeArrayGetRecordInfo (
SAFEARRAY* pSA,
IRecordInfo ** ppRecordInfo);
4. 訪問普通一維數組
從SafeArrayAccessData返回的指針實際上就是C語言中的一維數組地址。在VC++中可以像訪問普通數組一樣讀寫這個數組。
需要注意的是,在C語言中,所有的數組下標都是從0開始的。而在SafeArray中,數組下標可以從任何數字開始。所以在訪問前必須進行轉換。轉換方法就是從SafeArray的下標中減去數組的下界,就可以得到C語言中數組的下標了。
如下:
Type * pData;
long LBound;
SafeArrayAccessData(pSA, (void HUGEP **)&pData);
SafeArrayGetLBound(pSA, 1, &LBound);
Type Item = pData[n – LBound];
5. 訪問多維數組
訪問多維數組和訪問一維數組類似,只是要把多維下標轉換成一維下標。把多維下標轉換成一維下標的方法和在數組指針中介紹的是相似的。
設:有n個維度,每個維度的長度(上界減去下界加一)分別是L1、L2、…、Ln。要轉換的下標是X1、X2、…、Xn。可以根據下述公式轉換成一維數組的下標。
X1+X2*L1+X3*(L1*L2)+X4*(L1*L2*L3)+…+Xn*(L1*L2*…*L(n-1))
6. 訪問自定義結構數組
訪問自定義結構數組的時候,可以使用#import自動生成或者IDL編譯產生的類型定義。如果沒有辦法取得自定義結構的聲明,可以使用IRecordInfo接口中的方法間接訪問自定義結構。
首先需要取得自定義結構的長度,這可以通過IRecordInfo::GetSize方法取得。
訪問自定義結構中的字段內容,通過IRecordInfo::GetField和IRecordInfo::PutField方法實現。
通過IRecordInfo中的其它方法還可以取得每個字段的屬性內容。大家可以參考相關文檔。
1.2.3 釋放SafeArray數組
釋放SafeArray數組應該通過COM的支持函數:
HRESULT SafeArrayDestroy(SAFEARRAY * pSA);
1.3 使用SafeArray的IDL定義
每個接口都要通過IDL生成代理和佔位程序代碼。爲了使代理和佔位程序能夠正確地對參數進行序列化,必須正確的書寫IDL定義。
MIDL工具直接支持SafeArray類型數據的傳遞。但是,在傳遞SafeArray數據的時候,必須通過SAFEARRAY的指針進行。困難在於,VC++6.0的添加方法和添加屬性的工具不能夠正確的處理SafeArray數組的情況。
在IDL中,數組必須指定類型,如下:
[id(10)] HRESULT Foo([in] SAFEARRAY(LONG) pParam);
在實現的函數聲明中,要使用相應的指針類型:
HRESULT Foo(SAFEARRAY * pParam);
輸出和輸入輸出類型的數組參數,在IDL中必須使用指針參數,而在函數聲明中則是雙重指針。
[id(11)] HRESULT Foo2([out] SAFEARRAY(LONG) * ppParam);
函數聲明如下:
HRESULT Foo2(SAFEARRAY ** ppParam);
1.4 VARIANT和SafeArray
在VB的接口中,經常通過VARIANT傳遞數組參數。這裏簡述一下使用VARIANT參數傳遞數組中需要注意的地方。
1.4.1 輸入數組
對於輸入數組,可以使用VARIANT指針,也可以使用VARIANT類型參數。在這兩種情況下,VARIANT中的類型是不同的。
當使用VARIANT指針時,輸入的VARIANT參數的類型(vt參數的值)是VT_ARRAY | VT_BYREF |VT_xxx。此時,使用VARIANT參數的pparray。
SafeArray在ADO編程中經常使用。它的主要目的是用於automation中的數組型參數的傳遞。因爲在網絡環境中,數組是不能直接傳遞的,而必須將其包裝成SafeArray。實質上SafeArray就是將通常的數組增加一個描述符,說明其維數、長度、邊界、元素類型等信息。SafeArray也並不單獨使用,而是將其再包裝到VARIANT類型的變量中,然後才作爲參數傳送出去。在VARIANT的vt成員的值如果包含VT_ARRAY|...,那麼它所封裝的就是一個SafeArray,它的parray成員即是指向SafeArray的指針。SafeArray中元素的類型可以是VARIANT能封裝的任何類型,包括VARIANT類型本身。
使用SafeArray的具體步驟:
方法一:
包裝一個SafeArray:
(1)定義變量,如:
VARIANT varChunk;
SAFEARRAY *psa;
SAFEARRAYBOUNDrgsabound[1];
(2) 創建SafeArray描述符:
//read array from a file.
uIsRead=f.Read(bVal,ChunkSize);
if(uIsRead==0)
break;
rgsabound[0].cElements = uIsRead;
rgsabound[0].lLbound = 0;
psa = SafeArrayCreate(VT_UI1,1,rgsabound);
(3)放置數據元素到SafeArray:
for(longindex=0;index<uIsRead;index++)
{
if(FAILED(SafeArrayPutElement(psa,&index,&bVal[index])))
::MessageBox(NULL,"出毛病了。","提示",MB_OK | MB_ICONWARNING);
}
一個一個地放,挺麻煩的。
(4)封裝到VARIANT內:
varChunk.vt = VT_ARRAY|VT_UI1;
varChunk.parray = psa;
這樣就可以將varChunk作爲參數傳送出去了。
讀取SafeArray中的數據的步驟:
(1)用SafeArrayGetElement一個一個地讀
BYTE buf[lIsRead];
for(long index=0;index<lIsRead;index++)
{
::SafeArrayGetElement(varChunk.parray,&index,buf+index);
}
就讀到緩衝區buf裏了。
方法二:
使用SafeArrayAccessData直接讀寫SafeArray的緩衝區:
(1)讀緩衝區:
BYTE *buf;
SafeArrayAccessData(varChunk.parray, (void**)&buf);
f.Write(buf,lIsRead);
SafeArrayUnaccessData(varChunk.parray);
(2)寫緩衝區:
BYTE *buf;
::SafeArrayAccessData(psa, (void**)&buf);
for(longindex=0;index<uIsRead;index++)
{
buf[index]=bVal[index];
}
::SafeArrayUnaccessData(psa);
varChunk.vt = VT_ARRAY|VT_UI1;
varChunk.parray = psa;
這種方法讀寫SafeArray都可以,它直接操縱SafeArray的數據緩衝區,比用SafeArrayGetElement和SafeArrayPutElement速度快。特別適合於讀取數據。但用完之後不要忘了調用::SafeArrayUnaccessData(psa),否則會出錯的。
以下就是SAFEARRAY的Win32定義:
typedef struct tagSAFEARRAY
{
unsigned short cDims;
unsigned short fFeatures;
unsigned long cbElements;
unsigned long cLocks;
void * pvData;
SAFEARRAYBOUND rgsabound[ 1 ];
} SAFEARRAY;
這個結構的成員(cDims,cLocks等)是通過API函數來設置和管理的。真正的數據存放在pvData成員中,而SAFEARRAYBOUND結構定義該數組結構的細節。以下就是該結構成員的簡要描述:
成員 描述
cDims 數組的維數
fFeatures 用來描述數組如何分配和如何被釋放的標誌
cbElements 數組元素的大小
cLocks 一個計數器,用來跟蹤該數組被鎖定的次數
pvData 指向數據緩衝的指針
rgsabound 描述數組每維的數組結構,該數組的大小是可變的
rgsabound是一個有趣的成員,它的結構不太直觀。它是數據範圍的數組。該數組的大小依safearray維數的不同而有所區別。rgsabound成員是一個SAFEARRAYBOUND結構的數組--每個元素代表SAFEARRAY的一個維。
typedef struct tagSAFEARRAYBOUND
{
unsigned long cElements;
unsigned long lLbound;
} SAFEARRAYBOUND;
維數被定義在cDims成員中。例如,一個\'C\'類數組的維數可以是[3][4][5]-一個三維的數組。如果我們使用一個SAFEARRAY來表示這個結構,我們定義一個有三個元素的rgsabound數組--一個代表一維。
cDims = 3;
...
SAFEARRAYBOUND rgsabound[3];
rgsabound[0]元素定義第一維。在這個例子中ILBOUND元素爲0,是數組的下界。cElements成員的值等於三。數組的第二維([4])可以被rgsabound結構的第二個元素定義。下界也可以是0,元素的個數是4,第三維也是這樣。
要注意,由於這是一個"C"數組,因此由0開始,對於其它語言,例如Visual Basic,或者使用一個不同的開始。該數組的詳細情況如下所示:
元素 cElements ILbound
rgsabound[0] 3 0
rgsabound[1] 4 0
rgsabound[2] 5 0
關於SAFEARRAYBOUND結構其實還有很多沒說的。我們將要使用的SAFEARRAY只是一個簡單的單維字節數組。我們通過API函數創建數組的時候,SAFEARRAYBOUND將會被自動設置。只有在你需要使用複雜的多維數組的時候,你才需要操作這個結構。
還有一個名字爲cLocks的成員變量。很明顯,它與時間沒有任何的關係--它是一個鎖的計數器。該參數是用來控制訪問數組數據的。在你訪問它之前,你必須鎖定數據。通過跟蹤該計數器,系統可以在不需要該數組時安全地刪除它。
創建SAFEARRAY
創建一個單維SAFEARRAY的簡單方法是通過使用SafeArrayCreateVectorAPI函數。該函數可分配一個特定大小的連續內存塊。
SAFEARRAY *psa;
// create a safe array to store the streamdata
// llen is the number of bytes in thearray.
psa = SafeArrayCreateVector( VT_UI1, 0, llen );
SafeArrayCreateVectorAPI創建一個SAFEARRAY,並且返回一個指向它的指針。首個參數用來定義數組的類型--它可以是任何有效的變量數據類型。爲了傳送一個串行化的對象,我們將使用最基本的類型--一個非負的字節數組。VT--UI1代表非負整形的變量類型,1個字節。
常數\'0\'定義數組的下界;在C++中,通常爲0。最後的參數llen定義數組元素的個數。在我們的例子中,這與我們將要傳送對象的字節數是一樣的。我們還沒有提數組大小(llen)是怎樣來的,這將在我們重新考查串行化時提及。
在你訪問SAFEARRAY數據之前,你必須調用SafeArrayAccessData。該函數鎖定數據並且返回一個指針。在這裏,鎖定數組意味着增加該數組的內部計數器(cLocks)。
// define a pointer to a byte array
unsigned char *pData = NULL;
SafeArrayAccessData( psa, (void**)&pData);
... use the safe array
SafeArrayUnaccessData(psa);
相應用來釋放數據的函數是SafeArrayUnaccessData(),該功能釋放該參數的計數。
定義COM接口
在COM中傳送一個SAFEARRAY是很簡單的,你需要設置自己的接口來傳送SAFEARRAY結構。SAFEARRAY是一個本地的IDL數據類型。以下就是一個接口要處理的IDL代碼:
[
object,
uuid(EEC6D3EF-32F7-11D3-9EA1-00105A132526),
dual,
helpstring("IBlobData Interface"),
pointer_default(unique)
]
interface IBlobData : IUnknown
{
HRESULT GetArray([out] SAFEARRAY(unsigned char) *pData);
HRESULT SetArray([in] SAFEARRAY(unsigned char) pData );
};
只要你定義它包含的數據類型,SAFEARRAY就是一個有效的數據類型。語句SAFEARRAY(unsignedchar)可用來傳送任何類型的二進制數據。"Unsignedchar"意味着該數據將會是二進制字節,它與VT_UI1變量類型相對應。
它的兩個方法是相對的--GetArray方法從服務器得到一個對象。SetArray方法則發送一個對象給服務器。我們將不會談及爲該接口創建一個COM對象的基本問題。這個工作可通過使用ATL嚮導來完成。
接下來,我們會將串行化和SAFEARRAY兩部分的知識結合起來,講述一個例子。
Fig1.0數據類型允許用在IDispatch接口上。以下這些數據類型是可被一個類庫調用的。
類型 名字 描述
byte VT_UI1 非負字節
Short VT_I2 有符號16位短整型
Long VT_I4 有符號32位長整型
float VT_R4 一個IEEE 4字節實型數字
double VT_R8 一個IEEE 8字節實型數字
VARIANT_BOOL VT_BOOL 16位布爾 0=false, 0xFFFF=true
SCODE VT_ERROR 16位錯誤碼
CY VT_CY 16位貨幣結構
DATE VT_DATE 使用雙精度數字表示的日期
BSTR VT_BSTR visual basic風格的字符結構
DECIMAL VT_DECIMAL 一個十進制的結構
IUnknown VT_UNKNOWN 一個COM接口的指針
IDispatch VT_DISPATCH COM Dispatch接口的指針
SAFEARRAY * VT_ARRAY 一個用作傳送數組數據的特別結構
VARIANT * VT_VARIANT 一個VARIANT結構的指針
void * 普通的指針
VT_BYREF 任何類型(除指針外)的指針
【轉載完成】