在COM中使用陣列引數-陣列指標 (轉)

worldblog發表於2008-01-31
在COM中使用陣列引數-陣列指標 (轉)[@more@]

在COM中使用陣列引數-陣列指標

關鍵字:DCOM、陣列、自定義型別、Marshal、SafeArray、ICollection

1  使用陣列指標

陣列指標使用標準的C/C++陣列表示方式。陣列中的每個元素按照順序在中依次排放。陣列的下標從0開始計算。陣列的第一個元素(下標為0的元素)的地址就是陣列的指標,陣列中每個元素所佔的記憶體空間大小必須是固定的,只和陣列型別有關。計算陣列中某個元素的指標時,使用元素所佔的位元組數乘上元素下標就可以得出這個元素和陣列指標之間的偏移量/editor/Editor.htm#_ftn1" name=_ftnref1>[1]。根據這個偏移量就可以計算出這個元素的地址指標了。

使用陣列指標最重要的是確定陣列長度,使序列化可以正確地複製記憶體。為了生成和存根程式,我們要使用介面定義語言(IDL)描述介面和COM型別。MIDL讀取IDL中的描述,生成TLB和代理存根的程式碼。

1.1  IDL宣告

1.1.1  引數傳遞方向

為了儘量提高序列化的,在IDL中可以確定引數的傳遞方向。引數的傳遞方向有三種:輸入型(in)、輸出型(out)、和輸入輸出型(in, out)。輸入型引數從者傳遞到被呼叫者,被呼叫者對輸入型引數的更改不傳回撥用者。輸出型引數正相反,從被呼叫者分回撥用者,而被呼叫者不關心引數的初始值。輸入輸出型引數在呼叫的時候傳到被呼叫者,同時,被呼叫者可以對引數進行修改,這個修改在呼叫返回的時候會複製回撥用者。

非指標型別一定是輸入型引數。輸出型引數和輸入輸出型引數一定是指標型別。

1.1.2  陣列長度和複製長度

在IDL宣告中,應該正確的設定陣列長度和複製長度。陣列長度用size_is或min_is、max_is屬性定義,複製長度則使用length_is或first_is、last_is屬性定義。在目前的IDL實現中,min_is並沒有實現,所以,min_is只能是0。

size_is屬性用來設定陣列在記憶體中的長度。陣列在記憶體中的長度也可以透過max_is和min_is共同定義。他們的關係是:size = max – min + 1。由於min只能是0,所以,size = max + 1。

length_is屬性用來設定在序列化時需要複製的元素數量。需要複製的元素數量也可以透過first_is和last_is共同定義。他們的關係是:length = last – first + 1。可以同時定義first_is和length_is來定義複製的範圍,也可以同時定義first_is和last_is來定義複製的範圍。如果只定義了first_is,則last_is和有效的max_is值相同。如果沒有定義first_is,使用預設值0。但是,length_is和last_is不可以同時定義。複製元素的範圍不能夠超出陣列本身的範圍。如果沒有指定複製範圍,預設的複製範圍是整個陣列。

複製範圍一般不在單純的輸入引數或輸出引數上使用,而只在輸入輸出型引數中使用。在IDL中指定複製範圍會影響到哪部份的記憶體資料會從客戶端複製到COM端;當COM端的方法返回時,也是根據複製範圍,把資料複製回客戶程式。陣列從客戶端複製到COM端和從COM端複製回客戶端的範圍可以是不同的。

陣列用作輸出引數時,有兩種方案:呼叫方建立陣列和被呼叫方建立陣列。當輸出陣列的長度是可預知的時候,應該使用呼叫方建立陣列的方式。陣列用作輸出時,需要把屬性中的in改成out。參見例2和例3。

以下是使用陣列指標的例子。

 :namespace prefix = o ns = "urn:schemas--com::office" />

例1:計算陣列中數字的和,使用輔助變數確定需要傳遞的資料長度。

 

HRESULT Sum (
  [in, size_is(Count)] long * pNums,

  long Count,

  [out, retval] long * pResult);

 

也可以使用陣列風格的定義

 

HRESULT Sum (

  [in, size_is(Count)] long Numbers[],

  long Count,

  [out, retval] long * pResult);

 

例2:計算陣列中前Count個數的平方,儲存到陣列的後Count個元素中,注意陣列長度和複製長度是不同的。

 

HRESULT Square (

  [in, out, size_is(Count*2), length_is(Count)] long * pArray,

  long Count);

 

或使用陣列風格的定義:

 

HRESULT Square (

  [in, out, size_is(Count*2), length_is(Count)] long Array[],

  long Count);

 

例3:取得前n個質數。由呼叫方申請記憶體。這裡僅列出指標風格的定義。

 

HRESULT Prime (

long n,

[out, size_is(n)] long * pResult]);

 

1.1.3  雙重指標

在IDL中,可以使用雙重或多重指標型別的引數。這裡只介紹用於陣列輸出用的雙重指標,關於多重指標的使用,請參考相關文件。

雙重指標的size_is屬性中包含用逗號分隔的兩個長度值,分別是頂級指標陣列的長度和次級指標的長度。在長度值空缺的情況下,使用預設值1。例如:size_is(m,n)的含義是頂級指標長度是m,次級指標長度是n。size_is(,n)相當於size_is(1,n)。

在使用陣列指標傳遞陣列時,常用的是頂級指標長度是1的雙重指標。也就是指向陣列指標的指標。

1.1.4  被呼叫方建立陣列

如果輸出的陣列長度無法預知,就需要被呼叫的動態申請記憶體,建立陣列。這個時候,由於呼叫方不知道陣列的長度,不可能事先申請記憶體。所以,需要被呼叫方申請記憶體,並且把所申請的記憶體的地址傳給呼叫方。也就是說,呼叫方要傳遞存放陣列指標地址的地址給被呼叫方,也就是透過陣列指標的指標傳遞引數。

 

例1:取得所有員工編號,員工編號用長整形表示,員工的數量和每個員工編號都是輸出引數。

 

HRESULT GetStaffId (

[out] long * pNumber,

[out, size_is(, *pNumber)] long ** pResult]);

 

例2:返回傳入陣列中質數元素的指標。輸出是一個指標陣列,所以所傳遞的引數是一個三重指標,但本質上卻是一個二重指標,所以size_is引數仍然使用二重指標的型式。

 

HRESULT Prime2 (

long n,

[in, size_is(n)] long * pNums,

[out, size_is(, * pCount)] long *** pPrimeNums,

[out] long * pCount);

 

1.1.5  多維陣列

在IDL中,沒有定義多維陣列,如果要使用多維陣列,必須轉換成一維陣列來處理。一維陣列的長度是多維陣列中每個維度長度的乘積。比如說3*5的陣列可以用一個長度為15的陣列表示。

 

例1:計算行列式的值。

 

HRESULT Detenant (

  long Order,

  [in, size_is(Order * Order)] double * pNumbers,

  [out] double * pResult);

 

呼叫時使用型別轉換把多維陣列轉換成一維陣列。

 

double Num[10][10];

double Result;

// set the item in array Num

HRESULT hr = obj->Determinant (10, (double*) Num, &Result);

 

例2:計算逆矩陣,輸出陣列由呼叫方建立。

 

HRESULT AntiMatrix (

long Order,

[in, size_is(Order * Order)] double * pMatrix,

[out, size_is(Order * Order)] double * pAntiMatrix);

 

例3:取得轉換表(一個二維陣列)。

 

HRESULT GetConvertTable (

  [out] long * pLineNum;

  [out] long * pColNum;

  [out, size_is(, *pLineNum * *pColNum)] bool ** pTable);

 

在例3的輸出中,取得陣列元素時比較複雜,可以使用如下方法取得第i行第j列的元素:

 

if (i < *pLineNum && j < *pColNum)

{

bPass = (*pTable)[i*(*pColNum) + j];

}

 

1.1.6  字串

字串是一種特殊的陣列形式,這種陣列不定義長度,而是以特殊標誌——數值0結尾。由於在C語言中字串都是以0結尾的方式,所以這種型別常被用於傳遞字串引數。在IDL中規定字串的元素型別只能是單位元組或wchar_t型別,而且不能是多維的。

定義字串的屬性是string。字串陣列只能是一維的。如果字串是輸出引數,應該使用雙重指標,或者使用size_is屬性指定buffer的長度。

 

例1:字串作為輸入引數

 

HRESULT PutString (

  [in, string] char * pStr);

 

例2:字串作為輸出引數,被呼叫方申請記憶體。

 

HRESULT GetString (

  [out, string] char ** pStr);

 

例3:輸出字串,呼叫方申請記憶體。

 

HRESULT GetString2 (

  [in] long nMaxSize,

  [out, size_is(nMaxSize), string] char * pStr);

 

1.1.7  固定長度陣列

在上面提到的陣列指標方法中,size_is中的引數也可以是常量,但是效率比較低。如果陣列長度是一個常量,可以使用固定長度陣列的定義。固定長度的陣列可以是多維的,可以是輸入、輸出或輸入輸出型別。

 

例1:

 

HRESULT Add2 (

long Nums[10],

[out, retval] long * pResult);

 

例2:

 

HRESULT GetNumbers (

[out] long Nums[10]);

 

1.2  陣列指標的記憶體管理

根據COM規範,輸入型引數由呼叫方申請和釋放記憶體。輸出型引數由被呼叫方申請記憶體,由呼叫方釋放記憶體。輸入和輸出型引數,由呼叫方申請記憶體,被呼叫方可以釋放並重新申請記憶體,最終由呼叫方釋放記憶體。

由於涉及到代理和存根,跨套間的呼叫時代理和存根也參與記憶體管理,所以,COM和客戶端必須使用相同的記憶體管理方式。在COM中,提供了一套記憶體管理函式,凡是涉及到COM介面引數的記憶體塊都必須透過這幾些函式進行管理,這裡列出這些函式原型,具體說明請參考相關文件。

 

LPVOID CoTaskMemAlloc (ULONG cb);

void CoTaskMemFree (LPVOID pv);

LPVOID CoTaskMemRealloc (LPVOID pv, ULONG cb);

 

 



準確地說,陣列元素所佔記憶體的位元組數和編譯時的對齊引數有關。

複製範圍還大量使用在自定義型別中的陣列成員,這已經超出了本文的範圍,請大家參考相關資料。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-998945/,如需轉載,請註明出處,否則將追究法律責任。

相關文章