(1) 打开VC6集成开发环境,按新建按钮,选择PROJECT标签。
(2) 选择ATL COM AppWizard。
(3) 在右侧Project Name下面的空白处输入"Polygon"。
按下OK按钮,出现如下对话框:
按Finish按钮,接受默认设置,出现如下对话框:
按下OK按钮,ATL COM AppWizard将生成一系列的文件,现在描述如下:
Polygon.cpp:
包含了DllMain,DllCanUnloadNow, DllGetClassObject,DllRegisterServer,DllUnregisterServer的实现,
同时它也包含object map:
BEGIN_OBJECT_MAP(ObjectMap)
//这里将列出你的工程中将会用到的ATL对象,这里最初为空,
//因为我们目前还没有创建新的ATL对象
END_OBJECT_MAP()
Polygon.def DLL便准模块定义文件
Polygon.dsw 项目工作区文档
Polygon.dsp 项目设置文档
Polygon.idl 接口定义语言文件, 它详细的描述了您的工程中所有的接口
Polygon.rc 资源文件, 它包含了版本信息和工程名称字符串
Resource.h 资源文件的头文件
Polygonps.mk 这个就是make file,它能被用来创建代理存根DLL
Polygonps.def 代理存根DLL的模块定义文件
StdAfx.cpp 此文件包含ATL的执行档
StdAfx.h 此文件包含ATL的头文件
为了使它(Polygon DLL)变得有用,我们需要用ATL Object Wizard给它添加一个控件(control)。
(二)添加一个控件
(1) 打开INSERT菜单,选择New ATL Object项,出现如下对话框:
(2) 我们在左边选择"Controls",右边选择Full Control,按下NEXT按钮,出现如下所示对话框:
(3) 我们在Names标签页,"Short Name"后面的空白中输入"PolyCtl",这时你将注意到其他的空白将会自动完成。
Class域显示控件将会使用的类名称。
CoClass是控件的组件类ID
Interface是接口名称,我们将会在此接口中实现一些方法和属性
Type是控件描述
ProgID是易记的类ID名称,用它可以得到控件的CLSID
(4) 为了激活错误提示信息和connection points支持,我们选择Attributes标签页,选择Support ISupportErrorInfo和Support Connection Points,结果如下图所示:
(5) 由于我们将会在多变形内部染色,所以我们需要增加一个Fill Color属性支持。我们选择stock property标签页,在左边的列表框中双击Fill Color,结果如下图所示:
(6) 按下“确定”按钮,结束创建控件。
VC6将会生成如下新的文件:
PolyCtl.h/cpp:包含了C++类CPolyCtl的实现
PolyCtl.rgs:一个包含了注册控件所需要的注册信息的文本文件
PolyCtl.htm:一个HTML文件,其中有关于这个控件的引用的代码。例如在我这个例子中有:
同时Wizard也改变了以下几处:
a)在StdAfx.h和StdAfx.cpp文件中增加了一条include语句,它把控件必需的ATL文件包含进来了
b)注册脚本文件PolyCtl.rgs被增加到工程资源中。
c)Polygon.idl被修改以便包括新的控件细节信息。
文件PolyCtl.h是最有趣的,因为它包含实现你的控件主要的代码。
现在,你已经准备好了建立你的控件:
1.在Build菜单点击Build Polygon.dll。
2.一旦你的控件已经完成Build,你就可以点击在Tools菜单上的ActiveX Control Test Container,控件测试容器工具将启动。
3.在ActiveX Control Test Container中,选择Edit菜单的Insert New Control,Insert Control会话框出现。如下所示:
4.从Insert Control会话框的列表框中选择 PolyCtl class,按下OK,你将看到ActiveX Control Test Container客户区出现一个长方形,在其中央显示了本文" ATL 3.0: PolyCtl",如下所示:
5.关闭ActiveX Control Test Container。
然后,你将会在控件中加入定制属性。
(三)为控件添加一个属性
(1) IPolyCtl是包含你定制的方法和属性的接口。 要把属性加入这一个接口的最容易的方法是在ClassView中右击它,而且选择Add Property。如下所示:
(2) Add Property to Interface会话框出现,允许你加入你的属性细节:
1.在属性类型的下拉列表框中选择short。
2.输入"Sides"作为我们的属性名称。当你编辑属性名字域的时候,Implementation下面的编辑框将会出现一些信息,这些信息将被增加到你的IDL文件。如下所示:
3.按下OK按钮。
MIDL(编译idl文件的程序)定义了一个Get和一个Put方法,他们将分别取得和设定属性。 当MIDL编译文件的时候,它对属性名字加前缀put_ 和get_, 在接口中自动地定义那二个方法。
连同把必需的信息加入.idl文件, Add Property to Interface对话框也在类定义文件PolyCtl.h中加入Get 和Put函数原型,并在类实现文件PolyCtl.cpp中加入相应的空的实现函数。
(3) 为了能设定并且取回属性值,我们需要一个地方来储存它。从FileView, 打开 PolyCtl.h,在类定义结尾即在m_clrFillColor定义之后加入如下一行代码:
short m_nSides
(4) 现在你能实现Get和Put方法。get_Sides和put_Sides函数定义已经被增加到 PolyCtl.h 。你把代码加入 PolyCtl.cpp如下列各项:
STDMETHODIMP CPolyCtl::get_Sides(short *pVal)
{
*pVal = m_nSides
return S_OK
}
STDMETHODIMP CPolyCtl::put_Sides(short newVal)
{
if (newVal >2 &&newVal <101)
{
m_nSides = newVal
return S_OK
}
else
return Error(_T("Shape must have between 3 and 100 sides"))
}
get_Sides
函数只是通过pVal指针返回属性Sides的当前值。在put_Sides方法中,你确定使用者正在对Sides属性设定可接受的值。你需要超过2条
边, 而且由于你以后将会为每个边储存点的阵列,100是一个合理的最大值界限。如果有非法的值传递进来,你可以通过使用ATL
IErrorInfo接口的Error函数来设定详细的错误信息。
如果你的客户(container)需要比HRESULT更多的关于错误的资讯,这是有用的。
(5) 你为属性做的最后一件事是设定m_nSides初值。藉由把一行代码加入 PolyCtl.h 的构造函数中使一个三角形成为默认形状:
CPolyCtl()
{
m_nSides = 3
}
你现在拥有了一个叫做Sides的属性。 除非你对它做一些事情,否则它并没有什么用处,下一步我们将改变画图代码并使用该属性。
(四)变更画图代码
(1) 在画图编码中你将会使用sin和cos动作计算多边形顶点, 因此在 PolyCtl.h 的顶端包含 math.h:
#include <math.h>
#include "resource.h" // main symbols
在Release
builds时需要注意:当ATL COM AppWizard产生内定工程的时候,它定义了
_ATL_MIN_CRT宏。这个宏的作用是,在你不需要C Run-Time Library支持的时候, C Run-Time
Library不被带到你的代码之内。多角形控件需要C Run-Time Library start-up code设定浮点函数初值。 因此,
如果你建立一个释放版本,你需要除去_ATL_MIN_CRT宏。 为了要除去该宏,点击Project 菜单上的Settings。
在Settings For:下拉框中选择Multiple Configurations。在跳出来的Select project
configuration(s) to modify对话框中,为所有的四个释放版本按复选框, 如图所示:
图12
然后点击OK。在C/C++标签页,选择General, 除去Preprocessor definitions定义结尾的 ",_ATL_MIN_CRT"
图13
(2) 一旦多边形顶点计算出来了,你就可以通过增加一个POINT类型的数组来保存所有的点,在PolyCtl.h中:
OLE_COLOR m_clrFillColor
short m_nSides
POINT m_arrPoint[100]
(3)
现在改变 PolyCtl.h
的OnDraw函数。注意你需要除去对Rectangle和DrawText函数的调用。你需要明确地得到而且选择黑色的笔和白色的刷子。
这么做是,以防你的控件正在运行在无窗口环境中。 如果你没有你自己的窗口, 你不能假定具备绘制所需要的设备环境。
完成的OnDraw函数如下所示:
HRESULT CPolyCtl::OnDraw(ATL_DRAWINFO&di)
{
RECT&rc = *(RECT*)di.prcBounds
HDC hdc = di.hdcDraw
COLORREF colFore
HBRUSH hOldBrush, hBrush
HPEN hOldPen, hPen
// Translate m_colFore into a COLORREF type
OleTranslateColor(m_clrFillColor, NULL, &colFore)
// Create and select the colors to draw the circle
hPen = (HPEN)GetStockObject(BLACK_PEN)
hOldPen = (HPEN)SelectObject(hdc, hPen)
hBrush = (HBRUSH)GetStockObject(WHITE_BRUSH)
hOldBrush = (HBRUSH)SelectObject(hdc, hBrush)
Ellipse(hdc, rc.left, rc.top, rc.right, rc.bottom)
// Create and select the brush that will be used to fill the polygon
hBrush = CreateSolidBrush(colFore)
SelectObject(hdc, hBrush)
CalcPoints(rc)
Polygon(hdc, &m_arrPoint[0], m_nSides)
// Select back the old pen and brush and delete the brush we created
SelectObject(hdc, hOldPen)
SelectObject(hdc, hOldBrush)
DeleteObject(hBrush)
return S_OK
}
你现在需要一个函数,叫做了CalcPoints, 它将会计算多边形顶点的坐标。这些计算将会以被获准进入函数的RECT变量为基础。首先你应该把CalcPoints的定义加入到PolyCtl.h中的IPolyCtl类的公众区段:
void CalcPoints(const RECT&rc)
公共区段看起来应该如下:
// IPolyCtl
public:
STDMETHOD(get_Sides)(/*[out, retval]*/ short *newVal)
STDMETHOD(put_Sides)(/*[in]*/ short newVal)
void CalcPoints(const RECT&rc)
接着在PolyCtl.cpp尾部添加函数CalcPoints的具体实现:
void CPolyCtl::CalcPoints(const RECT&rc)
{
const double pi = 3.14159265358979
POINT ptCenter
double dblRadiusx = (rc.right - rc.left) / 2
double dblRadiusy = (rc.bottom - rc.top) / 2
double dblAngle = 3 * pi / 2// Start at the top
double dblDiff = 2 * pi / m_nSides// Angle each side will make
ptCenter.x = (rc.left + rc.right) / 2
ptCenter.y = (rc.top + rc.bottom) / 2
// Calculate the points for each side
for (int i = 0i <m_nSidesi++)
{
m_arrPoint[i].x = (long)(dblRadiusx * cos(dblAngle) + ptCenter.x + 0.5)
m_arrPoint[i].y = (long)(dblRadiusy * sin(dblAngle) + ptCenter.y + 0.5)
dblAngle += dblDiff
}
}
现在初始化变量m_clrFillColor,选择绿色作为默认颜色并加如下语句到CPolyCtl类构造函数中:
m_clrFillColor = RGB(0, 0xFF, 0)
类CPolyCtl构造函数现在看起来如下:
CPolyCtl()
{
m_nSides = 3
m_clrFillColor = RGB(0, 0xFF, 0)
}
现在重新编译控件,如果发现如下重载函数模糊调用错误(一剑:"这可能是VC6的一个BUG,亦或是由于我的VC6没有打SP6包的缘故吧:)"):
f:\myprogram2\polygon1\polyctl.h(106) : error C2668: 'InlineIsEqualGUID' : ambiguous call to overloaded function
我们可以在polyctl.h中出错位置修改如下:
if (::InlineIsEqualGUID(*arr[i], riid))
return S_OK
这一部分将重点介绍ATL的基本使用过程。由于ATL已经被集成在Microsoft Visulal Studio的Visual C++开发环境中,因此要使用ATL必须先安装Visual C++。在下面的讨论中有关COM的基本知识请参阅有关的文档,这里不再详细说明。
使用ATL开发一个COM应用基本可以分为以下几个步骤:
创建一个新的ATL工程,并对工程的选项进行适当的配置。
向新创建的工程添加新的ATL类,并对该类进行一些初始配置工作。
根据COM应用的基本要求向新的ATL类加入新的接口定义,并实现相应的接口成员函数。
编译连接工程,注册COM应用。
下面将根据这些步骤依次介绍ATL的基本使用过程(给出的是Visual Studio 6.0的使用):
1. 创建工程
首先启动Visual C++集成开发环境,选择“File”菜单下的“New...”命令,在“New”对话框中选择“Project”页。
选择“ATL COM AppWizard”项,这是创建ATL工程的AppWizard向导入口。然后在“Project name”编辑框中输入工程的名字,单击“OK”按钮,进入AppWizard对话框。
在AppWizard对话框中主要的设置选项有:
COM服务程序的类型:
-动态连接库(Dynamic Linking Library) 最终产生一个动态连接库(DLL)形式的COM服务程序;
-应用程序(Executable application)最终产生一个可执行程序类型(EXE)的COM服务程序;
- NT服务(NT Service):产生一个以NT服务方式运行的COM服务程序。
允许嵌入Proxy/Stub代码。由Microsoft提供的MIDL编译IDL文件以后,将产生用于对象调度(Marshaling)的Proxy/Stub的代码。传统地,这部分代码与COM服务程序的代码是分离的,但是由于新的COM标准支持多线程环境下的COM对象服务,因此在动态连接库的COM服务程序中也要有Proxy/Stub的支持。为了支持在网络上的传输,ATL允许用户选择将Proxy/Stub的代码包括在生成的DLL代码中。这个选项在EXE和NT服务类型的COM应用条件下不可选。
允许支持MFC。由于ATL对除COM以外的基本的Windows编程方面的支持极为有限,同时许多程序员对MFC又非常熟悉,因此在ATL的工程设置中允许在ATL工程内部支持使用MFC,即可以使用MFC定义的类。这在一方面来看是非常方便的,特别是对于习惯于使用MFC的开发人员来说,能够使用MFC提供的各种功能强大的类的支持,而不必直接使用Windows SDK。从另一个方面来看,在ATL工程中使用MFC同时就丧失了ATL代码轻量级的特点。
支持MTS。MTS是Microsoft Transaction Server的缩写,它是Microsoft在COM技术方面的一个新的分支,这里不作详细说明。
完成上面的设置以后,可以选择FINISH完成工程的设置,ATL将创建相应的工程。
2. 加入ATL类
完成工程的创建和设置以后,下一步就是向工程中加入一个新的ATL类。Visual Studio集成环境提供了向导工具“ATL Object Wizard”用于加入一个新的ATL类。 *** 作过程并不复杂,只是一组对话框 *** 作而已。
首先通过集成环境的“Insert”菜单下的“New ATL Object…”命令进入“ATL Object Wizard”对话框。
这个对话框即为创建ATL对象的向导起始界面。对话框的左边部分说明了待创建对象的基本类型,这里主要有以下的几种类型:
对象(Object)基本的COM对象类型;
控制(Control)ActiveX Control类型的ATL对象;
其他(Miscellaneous)辅助功能,如对话框的生成等;
数据访问(Data Access)数据访问,支持MTS等。
右边部分说明了每种类型的详细内容,对于一般的COM服务程序,使用对象表中的简单对象(Simple Object)就可以了。
选定待创建对象的基本类型以后,单击“Next>;”按钮进入下一步,进入对象属性设置对话框,如图4和图5所示。
对象属性设置分为两个过程:先是对象名字标识的设定,然后是对对象的基本属性进行设置。首先是对象的名字标识设置。
在对象标识编辑框中输入待创建对象的名字,ATL对象向导将同步地根据用户输入的对象标识设定该对象的C++标识和COM标识。对象的C++标识包括对象的类名,cpp文件名和头文件名。COM标识包括对象在类型库中的CoClass段和实现的主接口的名字,同时还有在系统注册表中的类型名以及ProgID。
对象名字标识设置完成以后,选择对象属性页(Attribute)进入对象的属性设置页面。
对象的属性设置是ATL对象创建过程中最复杂的部分,包括以下几个主要部分:
对象的线程模型(Thread Model)
对象的线程模型是COM对象在多线程环境下被访问时对访问方式的控制,缺省情况下在ATL中采用的是套间模型Apartment,由系统通过消息队列方式提供并发控制。
对象的接口模型(Interface)
COM对象的接口可以是双接口(Dual Interface)。双接口不同于普通接口(Custom Interface) 之处在于双接口是从Automation基本接口IDispatch继承的,而普通接口是从IUnknown接口直接继承来的。缺省的接口模型是双接口。
对象的聚合模型(Aggregate)
COM规范不允许对象的实现继承,但是可以通过聚合方式重用其它的COM对象。ATL对象属性设置中的聚合模型可以指定待创建的COM对象是否支持聚合模型。缺省的选项是支持对象的聚合。
对象对错误处理的支持(Support ISupportErrorInfo)
选取这个选项可以在对象的运行过程中支持错误处理。缺省情况下这个选项不被选中。
对象对连接点的支持(Support Connection Points)
连接点是COM对象的事件机制。选中这个选项可以使待创建的COM对象具有发出事件的能力。缺省情况下该选项不被选中。
对象对自由线程调度的支持(Free Thread Marshaller,简称FTM)
对象的自由线程调度是对象在处于自由线程模型状态下,为了简化对象的访问过程而采用的一种优化策略。缺省情况下该选项不被选中。
对于上述的任何一个选项的详细描述都涉及到COM技术一些核心的内容,并且都已超出本文的范围,因此本文只对ATL给出的缺省选项加以说明,对这些内容感兴趣的读者可以参考Microsoft提供的文档。
完成了上面的设置以后,就可以按“OK”按钮完成对象的创建过程。下一步就是向所生成的ATL类的接口中加入成员函数的定义,以及接口成员函数的实现过程。
3. 加入接口定义,实现接口函数
加入了ATL类定义之后,我们可以打开Visual C++集成环境下项目管理器(Workspace)中的Class View来检查生成的类定义的情况。我们可以看到一个新的类已经生成,同时,还生成了相应的接口定义。ATL Object Wizard为我们生成了类定义的.h 和.cpp文件,此外还有用于接口定义的IDL文件。有了这些文件以后,我们就可以为接口加入成员函数,完成类的定义。
首先在Class View中选中相应的接口,显示为接口IATLTest,单击鼠标右键打开菜单,如图7。此d出式菜单定义了为接口加入属性和方法的 *** 作。选取其中的“Add Method...”项,可以为接口加入方法成员;选取“Add Property...”则可以为接口加入新的属性成员。
加入属性和方法的对话框可以参看图8和图9。如果我们要在接口中加入一个方法,则选取“Add Method...”菜单命令。假设方法名为ABC,方法的返回类型为COM规定的HRESULT类型。我们也可以定义非HRESULT返回类型的函数,但是这需要手工修改接口定义的IDL文件。我们定义ABC方法的一个参数为a,类型为整数型。完成了方法的定义以后,单击“OK”按钮则把此方法加入到接口中。
属性的加入过程是类似的。属性加入对话框要求指定属性的类型、名字以及属性的访问方式。在属性和方法的编辑对话框中都有一个“Attributes”按钮,在给出了一个属性或方法的基本定义之后,单击此按钮,可以对属性和方法的一些高级特性进行设置。
方法成员加入以后,我们可以通过Class View来检查ATL为我们所做的工作。首先我们看到ATL在接口的定义中加入了该方法的定义;同时在对应的ATL类定义中,也加入了一个相应的方法的定义;在类对应的.cpp文件中,加入了此方法的实现框架。此后,我们只要在这个函数框架中加入该方法的代码逻辑,一个接口函数的定义和实现就基本完成了。依照这种方式,我们可以完成整个COM对象的定义和实现。
完成以上的步骤之后,我们就可以编译连接应用了。
4. 编译连接应用、注册COM服务程序
对ATL工程的编译连接过程包括下面的几个步骤:
使用MIDL编译工程的IDL文件,形成接口定义的头文件和用于调度(Marshalling)的代码;
编译工程的.cpp文件形成目标文件;
连接目标文件,形成应用模块;
注册COM服务程序。
关于工程编译连接的其它部分同Visual C++中MFC工程的编译连接过程相似,这里只重点介绍一下COM服务程序的注册过程。
在ATL中,COM服务程序的注册是在工程编译连接的最后阶段,由ATL辅助完成的。在手工的COM编程中,服务程序的注册是比较麻烦的工作。在ATL中,系统通过读取在建立工程过程中形成的注册脚本文件来完成注册工作。注册脚本(Register Script 简称RGS)是ATL提供的文本方式的注册辅助文件。下面是注册脚本文件的一个实例。
HKCR - 表示注册表中COM对象的注册项,是HKEY_CLASS_ROOT的缩写
{
AuthTest.ActiveXObject.1 = s 'ActiveXObject Class'
{
CLSID = s ''
} - 对象的ProgID
AuthTest.ActiveXObject = s 'ActiveXObject Class'
{
CLSID = s ''
} -对象的与版本无关的ProgID
NoRemove CLSID -对象CLSID注册项
{
ForceRemove = s 'ActiveXObject Class'
{
ProgID = s 'AuthTest.ActiveXObject.1'
VersionIndependentProgID = s 'AuthTest.ActiveXObject'
InprocServer32 = s '%MODULE% -服务器类型,表示DLL服务器
{
val ThreadingModel = s 'both' -线程模型,这里是BOTH型
}
}
}
}
RGS文件包含注册COM服务程序的各项内容,通常我们不必修改此RGS文件,必要时我们也可以手工修改RGS文件来定制模块的注册过程。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)