關於SAFEARRAY的,轉載了篇文章,比較全

有個問題,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        任何類型(除指針外)的指針

【轉載完成】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章