65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (12投票s)

2001 年 9 月 4 日

5分钟阅读

viewsIcon

158055

downloadIcon

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_PROPERTYPUTDISPATCH_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 接口的东西使用的对象。要注意的是,你的接口只在运行时才知道,这正是我们想要达到的目的,对吧?我现在没有时间,但稍后我会更新这篇文章,提供一个展示这些类使用方法的实际自动化项目。

你可以自由使用这些代码,如果你在一些有趣的项目中使用了它、扩展了它,或者只是想打个招呼,请给我发邮件。

许可证

本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.