动态创建 IDispatch 接口, 使用简单类






4.87/5 (12投票s)
2001 年 9 月 4 日
5分钟阅读

158055

1385
创建可以通过 IDispatch 接口传递的动态对象
引言
这个概念可能有点难理解,所以我将先讲述我最初为这些类设定的意图,这应该能帮助你理解如何使用它们。
我正在编写一个软件,需要将某些对象暴露给 VBscript 主机。当你用 ATL 编写对象或创建 ActiveX 对象时,这很容易——你只需将 IDispatch
传递给脚本主机,它就会自动解析方法和属性的 ID/名称,这样你的对象就可以自然地被脚本化。
所以,在脚本主机工作正常后,我考虑如果我能编写一些插件,并能以某种方式将它们也暴露给脚本主机,那就太好了。我不想限制插件只能拥有特定数量的方法或属性。基本上,我想做的是实时创建一个 ActiveX 控件。
我不知道这是否可行,所以搜索开始了,编码开始了,头痛也开始了。所有这些工作的最终结果是一套可以用来“即时”创建 IDispatch
接口的类。 我的一个朋友评论说“有什么意义呢?”,好吧,如果你看到任何这方面的用途,请在帖子中分享。
在整个过程中,我学到了比我真正想要的更多的 COM 知识,但最终我的想法确实实现了。而且我学到的信息后来也派上了用场。
有趣的是,你不需要关心 GUID 或其他创建和使用这些对象的细节。它们不需要注册,只需要存在。
在我最初的代码中,我需要查询 DLL 以获取它想要公开的属性和方法,给它一个名字,然后将其作为一个对象添加到脚本主机。这使得编写可脚本化的插件变得容易,并允许插件具有将自己独特的接口暴露给 VBscript 的所有灵活性,而无需担心 COM。
涉及的类有:
dDataCDynamicMetho
CDynamicInterface
CDynamicObject
CDynamicMethodData
对象用于设置我们期望的属性/方法。它有一个方法叫做
CreateNewMethod(CString sMethodName,unsigned short usMethodType).
sMethodName
- 方法或属性的名称usMethodType
- 这是什么类型的调用(方法)
有效的调用类型有:
DISPATCH_METHOD
(调用函数)DISPATCH_PROPERTYGET
(不言而喻)DISPATCH_PROPERTYPUT
(不言而喻)
CDynamicInterface
封装了 CDynamicMethodData
,并提供了实际生成实时 COM 接口的机制。
void CDynamicInterface::AddMethod(CString sMethod,CString sParms)
为了让添加方法和属性更加容易,我决定传递一个方法的参数字符串比重复调用 AddMethod
函数更有意义。因此,要设置一个名为“SetPoint
”且带有两个参数的方法,我们会这样做:
myDynamicInterface.AddMethod("SetPoint","VT_INT,VT_INT");
要添加属性,我们只需调用:
myDynamicInterface.AddProperty("intprop",VT_INT);
请注意,我们对所有内容都使用 VARIANT
。另外要知道,当你添加一个属性时,你实际上是在添加两个方法(一个用于 put
,一个用于 get
)。如果你直接调用 CDynamicMethodData
来添加属性,那么你可以指定你只需要 DISPATCH_PROPERTYPUT
或 DISPATCH_PROPERTYGET
。在我动态接口的情况下,我简化了处理,在创建属性时同时添加两者。
每当向接口添加方法或属性时,你需要意识到它会被分配一个 ID(从 0 开始)。这使得你最终的实现类了解添加属性和方法的顺序非常重要,以便你可以适当地处理它们(稍后会更清楚)。
现在我们来介绍 CDynamicObject
,它是你将要继承以实现你的动态对象的类。它直接基于 IDispatch
,并将处理任何传入的属性查询或接口调用。在我的实现中,我决定将 CDynamicMethodData
设为指针而不是实际对象。当时这个决定是有道理的,如果你创建多个相同类型的对象,为什么还要创建已存在接口的副本呢?使用一个 static
成员变量也可以达到这个目的,如果你真的想这样做,可以选择实现它。
那么,到目前为止,我们可能已经添加了一些属性和方法,那么我们如何处理传入的请求调用呢?
在我们的实现类(基于 CDynamicObject
)中,我们需要实现函数:
HRESULT vDispInvoke(
void FAR* _this,
ITypeInfo FAR* ptinfo,
DISPID dispidMember,
unsigned short wFlags,
DISPPARAMS FAR* pparams,
VARIANT FAR* pvarResult,
EXCEPINFO* pexcepinfo,
unsigned int FAR* puArgErr );
别担心,它看起来比实际的要简单得多。关键在于 dispidMember
变量。还记得我告诉你要记录添加属性和方法的顺序吗?好吧,如果你没有这样做,那么你就没办法弄清楚你该做什么了。在我的实现中,我使用了 #define
来设置动态对象的属性和方法。首先,我们确定调用者想要的是哪种方法:
if( (wFlags & DISPATCH_PROPERTYGET) || (wFlags & DISPATCH_PROPERTYPUT) )
{
// Its a property Alright...
// But Which one?
} else if(wFlags & DISPATCH_METHOD)
> {
// Its a method Call..
// which one?
}
“哪一个?”的答案包含在 dispidMember
变量中。如果在初始化接口时,你先添加了“SetPoint
”,并且 dispidMember = 0
,那么“SetPoint
”就是那个方法。记住,属性有两个 ID(一个用于 get
,一个用于 set
)。
所以,下面是一个完全实现的 vDispInvoke
可能是什么样子的示例:
HRESULT CMyDynamicObject::vDispInvoke(
void FAR* _this,
ITypeInfo FAR* ptinfo,
DISPID dispidMember,
unsigned short wFlags,
DISPPARAMS FAR* pparams,
VARIANT FAR* pvarResult,
EXCEPINFO* pexcepinfo,
unsigned int FAR* puArgErr )
{
/*
This is where all the work really takes place...
Since we know most of the methods, functions etc....
*/
// call a function
if(dispidMember == MY_INSTANCE_FUNCTION)
return i_MyFunction(wFlags,pparams,pvarResult);
if(dispidMember == INSTANCE_GET_X ||
dispidMember == INSTANCE_SET_X)// X
return i_HandleGetSetX(wFlags,pparams,pvarResult);
if(dispidMember == INSTANCE_GET_Y ||
dispidMember == INSTANCE_SET_Y)// Y
return i_HandleGetSetY(wFlags,pparams,pvarResult);
return S_OK;
}
我选择实现一个用于获取/设置属性的函数。它看起来像这样:
HRESULT CMyDynamicObject::i_HandleGetSetX
(unsigned short wFlags,DISPPARAMS FAR* pparams,VARIANT FAR* pvarResult)
{
if(wFlags & DISPATCH_PROPERTYGET)
{
pvarResult->vt = VT_INT;
pvarResult->intVal = m_iX;
}
else
m_iX=pparams->rgvarg->intVal;
return S_OK;
}
接收方法调用几乎是相同的:
HRESULT CMyDynamicObject::i_MyFunction(unsigned short wFlags, DISPPARAMS FAR* pparams,
VARIANT FAR* pvarResult)
{
VARIANT* pVars = pparams->rgvarg;// our array of parameters (which we should know)
// do something interesting
return S_OK;
}
这一切的最终结果是,你现在拥有了一个可以被脚本主机或任何接受 IDispatch
接口的东西使用的对象。要注意的是,你的接口只在运行时才知道,这正是我们想要达到的目的,对吧?我现在没有时间,但稍后我会更新这篇文章,提供一个展示这些类使用方法的实际自动化项目。
你可以自由使用这些代码,如果你在一些有趣的项目中使用了它、扩展了它,或者只是想打个招呼,请给我发邮件。
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。