變體(Variant)
Variant類型理論上可以存放任何類型的數據,這也是中文很多 人稱之為“變體”的原因。對於C++這種強類型語言的程序員來說,存在變體 (Variant)這樣的類型是奇怪的。但是對於哪些淡化類型概念的語言(如Visual Basic等) 來說,Variant是它們默認的類型。在VB中,如果沒有用As語句聲明變量,那麼這個變量就是 Variant類型的。對於C++程序員來說,Variant不過是一個超復雜的結構體:
typedef /* [wire_marshal] */ struct tagVARIANT VARIANT;
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類型在解釋型語言和腳本語言中應用甚廣。在Visual Basic,JavaScript等身上,處處可見其身影。但是如果沒有語言本省的支持,對Variant操 作是復雜的。不幸的是,C/C++就是屬於這種情況。這應該說與C++對新技術的慎重,以及是 一種非純商業公司控制的語言有關。其他語言如Delphi,一定要與時俱進,是一定要加 Variant的內置支持的。
IDispatch與雙接口
在我看來, Dispatch調用 (IDispatch)的存在主要是腳本語言的需要。腳本語言多數屬於解釋型語言,其代碼並不生 成機器指令,而是邊解釋邊執行(或者翻譯成為中間代碼後解釋執行),這種語言通常有這 樣一個需求:就是要在不知道類(或者說組件)的詳細規格情況下調用類的方法。
Dispatch調用(IDispatch)就是滿足這種需求的一個技術規格。它用一個dispid或 者名字(字符串)表示要調用的方法(或者屬性),其原理和Windows窗口的消息機制挺類似 (你可以把窗口消息中的uMsg參數和這裡的dispid對應起來)。IDispatch的實現者對dispid 進行分派,完成具體的功能調用。有些腳本語言也許未必采用 IDispatch 作為它的調用標准 ,但是通常是一種和 IDispatch 類似的東西。
這種在不知道類(或者說組件)的詳 細規格情況下調用類的方法,我們稱之為“晚綁定”。這是相對於C++這類編譯型 語言中基於虛函數機制的調用機制而言的,後者我們成為“早綁定”。對於虛函 數機制,它要求組件的接口是已知的,如果你不知道組件的接口,也就不知道又哪些方法可 用,更談不上如何去調用。
IDispatch的定義如下:
interface IDispatch : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(
/* [out] */ UINT __RPC_FAR *pctinfo) = 0;
virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(
/* [in] */ UINT iTInfo,
/* [in] */ LCID lcid,
/* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo) = 0;
virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(
/* [in] */ REFIID riid,
/* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,
/* [in] */ UINT cNames,
/* [in] */ LCID lcid,
/* [size_is][out] */ DISPID __RPC_FAR *rgDispId) = 0;
virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke(
/* [in] */ DISPID dispIdMember,
/* [in] */ REFIID riid,
/* [in] */ LCID lcid,
/* [in] */ WORD wFlags,
/* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,
/* [out] */ VARIANT __RPC_FAR *pVarResult,
/* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,
/* [out] */ UINT __RPC_FAR *puArgErr) = 0;
};
最後一個問題是,什麼是“雙接口”? 一個誤區是, 也許有人會把“雙接口”和從IDispatch繼承的接口等同起來。不過,這種理解有 點片面了。
所謂“雙接口”,是指哪些既實現了IDispatch接口,又實現 了基於虛表調用的普通接口的特殊接口。雙接口的好處在於它既適應了C++這種支持虛表 (vtbl)、追求高效的語言,也支持了腳本語言。在idl文法中,雙接口以dual關鍵字表示:
[dual]
interface IFoo : IDispatch
{
HRESULT foo(int arg1, int arg2);
};
這裡 IFoo 是一個雙接口。一個雙接口一定是 IDispatch 接口,但是反之 則不一定。