IDispatch接口主要应用于传统的自动化编程,如著名的Microsoft Visual Basic。客户端程序只需得到COM组件的IDispatch接口就可调用组件所有的属性和方法。但IDispatch的局限在与它假定COM组件是静态的。也就是说,在运行期间,这些COM组件的属性和方法是不能改变的。因此,如果要实现javascript和vbscript脚本语言的动态特性,就需要一个更灵活的接口。
于是为脚本语言量身定制的IDispatchEx接口诞生了。IDispatchEx派生自IDispatch,除了支持IDispatch所约定的方法之外,还提供了一组扩展方法,用于支持脚本语言的动态特性,这些动态特性包括:
- 为object添加成员 ("expando") — 使用带fdexNameEnsure标记的方法 GetDispID.
- 删除object的成员 — 使用方法DeleteMemberByName 或者DeleteMemberByDispID.
- 支持大小写敏感的方法 — 使用fdexNameCaseSensitive标记或fdexNameCaseInsensitive标记..
- 使用隐含名搜索成员 — 使用fdexNameImplicit标记
- 枚举object所有成员的DISPID — 使用GetNextDispID.
- 通过DispID取得成员的名称 — 使用GetMemberName.
- 获取object成员的属性 — 使用GetMemberProperties.
- 使用执行带this指针的方法调用 — 使用方法InvokeEx.
-
- 允许支持命名空间概念的浏览器获取object的命名空间 — 使用方法GetNameSpaceParent.
其实在这里,IDispatchEx接口存在更重要的意义在于,使得我们有途径在VC++中访问并模拟动态脚本语言的特性。下面我们以javascript为例。
举例:
方法test()中的javascript代码执行以下动作:
调用Object的constructor(构造器)创建一个新的object,用变量Obj保存。
在object中创建一个新的成员Elem,并将Elem的值设为方法cat的指针。
调用Elem方法,传入this指针。因为Elem是Obj的成员,所以this指针就指向Obj。于是方法cat中的代码也就是为Obj设置了一个新的成员Bar,并赋值为10。
完整的HTML代码如下:
<HTML>
<BODY>
<SCRIPT LANGUAGE="javascript"> function cat()
{
//创建一个新的成员,并赋值10
this.Bar = 10;
} function test()
{
// 创建一个新的object
Obj = new Object(); // 创建新的成员,并赋值为cat
Obj.Elem = cat; //调用Elem("this" == Obj)
Obj.Elem(); //现在Obj.Bar已经存在了
}
-
//
- BOOL test(IDispatchEx *pdexScript)
- {
- HRESULT hr;
- VARIANT var;
- DISPID dispid, putid;
- BOOL retval = FALSE;
- BSTR bstrName = NULL;
- IDispatch *pdispObj = NULL, *pdispCat = NULL;
- IDispatchEx *pdexObj = NULL;
- DISPPARAMS dispparams, dispparamsNoArgs = {NULL, NULL, 0, 0};
- // Get dispatch pointer for "cat"
- bstrName = SysAllocString(OLESTR("cat"));
- if (bstrName == NULL) goto LDone;
- hr = pdexScript->GetDispID(bstrName, 0, &dispid);
- if (FAILED(hr)) goto LDone;
- SysFreeString(bstrName);
- bstrName = NULL;
- hr = pdexScript->InvokeEx(dispid, LOCALE_USER_DEFAULT,
- DISPATCH_PROPERTYGET, &dispparamsNoArgs,
- &var, NULL, NULL);
- if (FAILED(hr)) goto LDone;
- pdispCat = var.pdispVal;
- // Create object by calling "Object" constructor
- bstrName = SysAllocString(OLESTR("Object"));
- if (NULL == bstrName) goto LDone;
- hr = pdexScript->GetDispID(bstrName, 0, &dispid);
- if (FAILED(hr)) goto LDone;
- SysFreeString(bstrName);
- bstrName = NULL;
- hr = pdexScript->InvokeEx(dispid, LOCALE_USER_DEFAULT,
- DISPATCH_CONSTRUCT, &dispparamsNoArgs,
- &var, NULL, NULL);
- if (FAILED(hr)) goto LDone;
- pdispObj = var.pdispVal;
- hr = pdispObj->QueryInterface(IID_IDispatchEx, (void **)&pdexObj);
- if (FAILED(hr)) goto LDone;
- // Create new element in object
- bstrName = SysAllocString(OLESTR("Elem"));
- if (NULL == bstrName) goto LDone;
- hr = pdexObj->GetDispID(bstrName, fdexNameEnsure, &dispid);
- if (FAILED(hr)) goto LDone;
- SysFreeString(bstrName);
- bstrName = NULL;
- // Assign "cat" dispatch pointer to element
- putid = DISPID_PROPERTYPUT;
- var.vt = VT_DISPATCH;
- var.pdispVal = pdispCat;
- dispparams.rgvarg = &var;
- dispparams.rgdispidNamedArgs = &putid;
- dispparams.cArgs = 1;
- dispparams.cNamedArgs = 1;
- hr = pdexObj->InvokeEx(dispid, LOCALE_USER_DEFAULT,
- DISPATCH_PROPERTYPUTREF, &dispparams,
- NULL, NULL, NULL);
- if (FAILED(hr)) goto LDone;
- // Invoke method with "this" pointer
- putid = DISPID_THIS;
- var.vt = VT_DISPATCH;
- var.pdispVal = pdispObj;
- dispparams.rgvarg = &var;
- dispparams.rgdispidNamedArgs = &putid;
- dispparams.cArgs = 1;
- dispparams.cNamedArgs = 1;
- hr = pdexObj->InvokeEx(dispid, LOCALE_USER_DEFAULT,
- DISPATCH_METHOD, &dispparams,
- NULL, NULL, NULL);
- if (FAILED(hr)) goto LDone;
- // Confirm that new element "Bar" is in object
- hr = pdexObj->GetNextDispID(fdexEnumAll, DISPID_STARTENUM, &dispid);
- while (hr == NOERROR)
- {
- hr = pdexObj->GetMemberName(dispid, &bstrName);
- if (FAILED(hr)) goto LDone;
- retval = !wcscmp(bstrName, OLESTR("Bar"));
- SysFreeString(bstrName);
- bstrName = NULL;
- if (retval) goto LDone;
- hr = pdexObj->GetNextDispID(fdexEnumAll, dispid, &dispid);
- }
- LDone:
- SysFreeString(bstrName);
- if (pdispCat != NULL)
- pdispCat->Release();
- if (pdispObj != NULL)
- pdispObj->Release();
- if (pdexObj != NULL)
- pdexObj->Release();
- return retval;
- }
test();
</SCRIPT>
</BODY>
</HTML>
我们通过一个控件用VC++封装方法test中的代码。当一个控件放在一个网页中,就可以取得浏览器中的脚本引擎的IDispatch接口和IDispatchEx接口( 详情见上一篇)。
<HTML>
<BODY>
<SCRIPT LANGUAGE="javascript">
function cat()
{
// 创建一个新的成员,并赋值10 this.Bar = 10;
}
</SCRIPT>
<OBJECT ID="test" <CLASSID="CLSID:9417DB5D-FA2A-11D0-8CB3-00C04FC2B085">>
</OBJECT>
</BODY>
</HTML>
在控件test中我们将进行如下操作:
1、使用GetDispID()取得方法cat的指针。
2、使用GetDispID()取得方法Object的指针。
3、使用InvokeEx()调用方法Object创建一个新的object。
4、使用GetDispID()为object创建一个新的成员Elem。
5、使用InvokeEx()将方法cat的指针赋给成员Elem。
6、使用InvokeEx()调用Elem(也就是方法cat),将object对象作为this指针传入。于是方法cat为this指针(也就是object)创建一个新成员Bar。
7、使用GetNextDispID()枚举object,验证刚才新建的成员Bar是否存在。
控件test中的代码如下: