分享
 
 
 

在C++中创建COM DLL

王朝vc·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

今日来在学习COM编程,找到一些自认为有价值文件分享给大家:

在C++中创建COM DLL

在本篇文章中我们将讨论如何在MSVC++中开发一个非常简单的ActiveX dll,并在Visual Basic中对它进行调用,其中的例子将用到Active Template Library(ATL)和相关的向导工具。本篇文章不会涉及COM和ATL的工作原理(尽管这是必需的),也不要求读者具有任何ATL方面的经验。

IDL:主要的差别

在C++与VB中开发ActiveX的最大的不同之处(除了语言和其他一些小的方面)是IDL的引入。IDL是界面定义语言(Interface Definition Language)的简写,就象其名字那样,它定义了dll的界面。客户端可以用界面将自己与对象和dll的方法绑定,但它的作用并不仅仅限于此。

通过使用IDL,我们可以将对象的外观和代码完全独立开来,隐藏在界面之后的任何东西可以随时被改变,任何使用dll的客户端程序都无需有任何改变,因为界面并没有改变。IDL还定义了过程之间传递的数据类型和参数。

由于IDL是完全独立于具体语言的,不仅仅局限于COM或C++,CORBA同样也可以使用IDL来定义界面。

所有的VB AX(ActiveX)对象在编译时都使用了IDL类型库,根据对象、方法和函数在VB程序中是如何定义的,VB会在幕后完成所有的工作。在MSVC++中,创建AX dll时不会自动生成IDL,类型库并非是COM对象所必需的(DirectX COM API就是一个很好的例子。),这个工作需要C++编程人员来完成。

MSVC++ COM ATL工程

创建一个ATL Com AppWizard工程,并将它命名为MyAXDll。具体的方法我在这里就不啰嗦了。

AppWizard将在工程中添加一些文件,最重要的是MyAXDll.cpp,这个文件包括了每个COM对象必须提供的dll导出函数━━DllCanUnloadNow、DllGetClassObject、DllRegisterServer和DllUnregisterServer,这些函数是dll在注册和创建时就带有的。向导代码forwards调用基本ATL CComModule类的变量_Module,该类中包含COM对象创建、注册和注销它自己所必需的所有基本函数。

AppWizard 还会添加一个MyAXDll.idl文件,这个文件包含该工程中所有的对象、属性和方法的IDL。

这个工程本身并没有什么有用的东西,它只是包括一些COM对象和函数的外部结构和模块,如果要使该工程具有一定的功能,就需要在其中添加一个对象。添加对象的方法相信对于大家都是一件再简单不过的事儿了,我在这里就不再详细讨论了。

向导在该工程中添加了三个文件━━MyObject.h、MyObject.cpp和MyObject.rgs,rgs文件包含用一种注册语言编写的COM类的注册设置,其内容在编译过的dll文件中是作为一种注册源存在的,可以被Registration使用,以正确地注册dll。MyObject.h文件中包含MyObject类的定义,打开该文件并详细观察其代码就可以发现,该文件包含了一个类━━CMyObject,该类是由其他四个类继承生成的,其中的三个是模式板类,另一个是一个独立的类(ISupportErrorInfo)。此外,其中还包含一些宏定义:

DECLARE_REGISTRY_RESOURCEID(IDR_MYOBJECT)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CMyObject)

COM_INTERFACE_ENTRY(IMyObject)

COM_INTERFACE_ENTRY(IDispatch)

COM_INTERFACE_ENTRY(ISupportErrorInfo)

END_COM_MAP()

DECLARE_REGISTRY_RESOURCEID宏扩展为一个静态函数,该函数在系统注册表中注册该服务器(COM对象),IDR_MYOBJECT是要注册的类的源ID。

DECLARE_PROTECT_FINAL_CONSTRUCT()可以确保任何内部对象不会产生调用FinalConstruct()的对象,被组合的对象必须使用该宏。

BEGIN_COM_MAP()、COM_INTERFACE_ENTRY()和END_COM_MAP()的定义是有超前性的,对它们的讨论已经超出本篇文章的范围。我们只要理解类执行的每个界面就可以了,COM_MAP中的COM_INTERFACE_ENTRY(ITheInterface)是必需的。

MyObject.h中接下来的代码是一个函数的定义:

STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);

这个函数的代码是在MyObject.cpp中完成的。宏STDMETHOD(FunctionName)可以扩展成如下的形式:

virtual HRESULT STDMETHODCALLTYPE FunctionName

STDMETHODCALLTYPE是一个标准的__stdcall类型,因此,宏STDMETHOD可以确保该函数使用标准的调用规则,并返回一个HRESULT类型的值。

InterfaceSupportsErrorInfo函数是ISupportErrorInfo界面中除必需的函数之外的唯一函数。通过执行这个界面,服务器可以设置出错信息对象,客户端可以使用这一出错信息对象。尽管VB中的错误与COM错误都属同一类型,但这种处理错误信息的方式与VB有很大不同。由于恰当的错误信息处理方式对于软件是极为重要的,因此很有必要对这一问题进行深入的探讨。

COM和COM错误

MyAXdll工程中的COM对象执行ISupportErrorInfo界面,这一界面使服务器的客户端可以查询扩展的错误信息。这种方式会使已经习惯了标准VB错误处理方式的用户感到迷惑。二者之间的差别在于对象的方法的返回值上。在VB中,我们在函数的首部定义返回值的类型,如下所示:

Public Function DoIt() As Long...DoIt = SomeValueEnd Function

上面的代码将函数返回值的类型定义成Long类型数据,在VB和脚本语言中这没有什么问题。在C++中则完全不同,该函数的头部将变成如下所示的格式:

HRESULT __stdcall DoIt( long *return_value ){...*return_value = SomeValue;return S_OK;}

上面的函数头部将返回值定义成S_OK,它向客户端表明,函数执行成功,真正可用的返回值被传送到一个与ByRef类似的参数中。那么为什么会是这样呢?

其原因就在于COM的构建方式。COM子系统有时需要通知客户端在函数调用中发生了错误,例如,如果一个客户端正在访问一个远程服务器,如果该服务器没有在网络上,此时,COM子系统就有许多返回值可供选择,而对异常的自理又对语言有较大的依赖性。因此 ,几乎所有的COM方法都返回一个HRESULT值,这个值可以表明函数的执行结果。在SDK中的 文件定义了一些基本的返回类型,其中最常用的二种为:S_OK,它表明函数执行正常;E_FAIL表示函数执行异常。

表1列出了一些常用的返回类型:

E_NOTIMPL :没有执行

E_OUTOFMEMORY:内存溢出

E_INVALIDARG :一个或多个参数无效

E_NOINTERFACE:不支持界面

E_POINTER :无效的指针

E_HANDLE :无效的句柄

E_ABORT :操作中止

上面的表列出了可以在过程之间传递的消息,一旦过程在执行中有什么不正常,COM就可以把这些消息通知客户端,在VB中这一切是如何实现的呢?

VB也有同样的机制。如果一个VB函数得到成功的执行,VB就加返回一个S_OK值;如果函数执行有什么异常,就会出现错误,VB会返回一个类型为HRESULT的错误对象的错误代码。

当VB客户端程序访问COM服务器时,如果返回一个调用失败的HRESULT值,VB就会立即查询服务器看它是否支持ISupportErrorInfo界面。因为如果服务器支持这一界面,VB将向InterfaceSupportErrorInfo函数查询失败的界面是否支持扩展的错误信息。

由于二者相同,InterfaceSupportErrorInfo会返回一个true值,这表明该界面不支持扩展错误信息,VB会询问其他界面看它们是否支持这些信息━━这一切都是由ATL在幕后完成的,因此只要COM服务器设置了相应的信息,VB是可以得到扩展错误信息的。VB使用这些信息建立相似的错误对象,然后给出一个错误类型值━━类型为HRESULT的值。

因此,如果在VB中发现一个错误信息,真正的错误仍然只局限于COM对象的范围内,VB只是简单地返回Err.Number的值,它只用于客户端向COM对象查询扩展错误信息。

稍后,我们将会仔细讨论如何设置错误信息。

在对象中添加方法

现在我们来向对象中添加方法。本例中的对象有3种方法:

1、取二个long类型值,将二者相加,并返回结果。

2、取一个long型数据并报告出错信息

3、取一个long型数据,将它转换为BSTR数据,并返回这个数据

在一个COM对象中添加方法需要二个步骤:1、需要在IDL文件中定义方法;2、必须由MyObject类来实现。为了说明在COM对象中添加方法的具体步骤,我们将用手工方式添加第一个方法,第二、三个方法则使用AppWizard向导来完成。

打开MyAXDll.idl文件。文件中的IDL可以分为二部分,第一部分是MyObject对象的定义,下面是该对象所有的方法、属性。代码如下所示:

MyObject definitions

[

object,

uuid(28D7C31F-1FB2-4BF8-BC98-C8A256348354), dual,

helpstring("IMyObject Interface"),

pointer_default(unique)

] interface IMyObject : IDispatch

{

};

The MyAXDll definitions:

[ uuid(E37200F2-E3DD-4243-9248-AA6A7FE71369),

version(1.0),

helpstring("MyAXDll 1.0 Type Library")

]

library MYAXDLLLib

{

importlib("stdole32.tlb"); importlib("stdole2.tlb");

[

uuid(09374147-1B90-4D78-B5B1-6E5B97C60DF2), helpstring("MyObject Class")

]

coclass MyObject

{

[default] interface IMyObject;

};

};

MyAXDll把MyObject界面作为一个联合类。

要为MyObject定义一个方法,该方法必须添加在上面代码中IMyObject : IDispatch { }中的二个括弧中间。把下列内容添加到二个括弧之间:

[id(1), helpstring("AddLongs method - adds two longs and return the result")]

HRESULT AddLongs([in] long FirstParam, [in] long SecondParam, [out, retval] long* rt);

这是在对象中添加方法的最基本IDL。 [id(1), helpstring("AddLongs method - adds two longs and return the result")]设置要添加的方法的一些属性。id(#)设置供IDispatch界面函数使用的对象的DISPID,Helpstring是一个解释这个方法作用的字符串,我们可以在VB中的对象浏览器中查看这个字符串。

真正的函数: HRESULT AddLongs([in] long FirstParam, [in] long SecondParam, [out, retval] long* rt);除了[direction]标志外是非常简单的,这些标志用于表明如何以及谁对参数进行控制,他们同VB如何向函数传递参数有直接的关系。下面列出的一些常用的标记组合:

[in] 与VB中的ByVal相同,表明该参数是由调用者分配存储和控制的。

[out] 表明该参数由被调用函数分配存储空间。

[in, out] 与VB中的ByRef相同,表明该参数应该由调用者分配存储空间,被调用函 数可以回收该参数的存储空间,也可以重新为该参数分配空间。

[out, retval] 与VB中的As Type返回值相同,如果指针指向一个指针,则被调用函数只能为该参数分配存储空间。

要完成这个函数,需要打开MyObject.h文件。在类的public小节中添加下面基本的函数定义:

STDMETHOD(AddLongs)( long FirstParam, long SecondParam, long* rt);

这只是该对象的定义,还需要在MyObject.cpp文件中添加下面的内容:

STDMETHODIMP CMyObject::AddLongs(long FirstParam, long SecondParam, long *rt)

{

*rt = FirstParam + SecondParam;

return S_OK;

}

至此,我们就为该对象添加了一个方法。

第二个函数通过使用向导工具可以很方便地进行定义。在工程资源管理器中选择ClassView标签,扩展CMyObject并选择IMyObject界面,右击该界面并选择“添加方法”,把该方法命名为RaiseAnError,并把下面的内容添加到参数框中:

[in] long FirstParam, [out,retval] long * rt

至此,向导已经在MyAXDll.idl中添加了该方法的IDL,在MyObject.h、MyObject.cpp文件中添加了该函数的框架。打开MyObject.cpp文件,在其中的RaiseAnError函数中添加下面的内容:

STDMETHODIMP CMyObject::RaiseAnError(long FirstParam, long *rt)

{

//该函数必须设置错误对象

return AtlReportError( CLSID_MyObject, "Upps an error occurred", IID_IMyObject, E_FAIL );

}

该函数使用了AtlReportError帮助函数,该函数设置错误信息,并能够让VB得到并显示这些错误信息。

由于使用了BSTR,最后一个函数有点特殊,BSTR是一个指向OLECHAR的简单指针,分配和收回BSTR的存储空间必须通过SysAllocaXX字符串函数实现,否则就可能扰乱COM系统,从而可能使你遇到一些奇怪的问题。

The function takes one long value, convert it to a BSTR and returns it. Define it like this, using the AppWizard mentioned above:

该函数取得一个long型数据,转换为BSTR数据并将转换的结果返回给调用者。用AppWizard定义如下:

名字: ConvertToString

参数: [in] long FirstParam, [out, retval] BSTR* ConvertedValue

生成的代码非常简单,如下所示:

STDMETHODIMP CMyObject::ConvertToString(long FirstParam, BSTR* ConvertedValue )

{

TCHAR tzConverted[20];

//清零

memset( tzConverted, 0, 20 );

//用C运行库函数进行转换

_itoa( FirstParam, tzConverted, 10 ); //转换为BSTR

USES_CONVERSION;

if ( ::SysReAllocString( ConvertedValue, A2OLE( tzConverted ) ) )

return S_OK;

return E_OUTOFMEMORY;

}

这里比较重要的一点是,返回值的存储空间的分配是由SysReAllocString()完成的,由于BSTR是由客户端程序创建的,使用SysAllocString()函数可以保证它不被覆盖。

That is it. Compile the project and fix any errors that have sneaked into the typing of the code.

所有的3个方法都添加后,编译该工程并修正其中出现的错误。

客户端软件

客户端软件是一个VB程序,我们可以使用本篇文章附加的程序(需要首先编译dll)也可以自己创建一个VB程序。在工程符号库中为MyAXdll.dll类型库建立一个符号。

结论

本篇文章没有详细讨论COM和ATL的有关知识,但它从一个VB编程人员的角度出发,简要地介绍了如何使用ATL和COM对VB进行扩展的基本技巧。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有