DCOM D-Mystified.NET 2003:DCOM教程,第3步






3.81/5 (12投票s)
在此步骤中,我们使用ATL简单对象向导向服务器添加一个简单的COM对象。此外,使用FinalConstruct()和FinalRelease()正确初始化和清理COM对象。
引言
欢迎来到我的DCOM教程的第3步。在本系列中,我将通过一个全面的教程和一个直接的例子,消除DCOM的神秘感、头痛和困惑。好的,不保证——但我会好好尝试。
如果你想跟着本教程学习,并在进行过程中添加代码并使用Visual C++向导,那就太好了。事实上,我强烈推荐这样做,否则本教程就是电子墨水(?)的巨大浪费。但是,我自己也会严格按照教程进行,并像我所说的那样开发代码并使用Visual C++向导。截图实际上来自我为每个步骤开发的文件!要下载这些已开发的代码以与你的代码进行比较,只需点击每一步顶部的“下载第n步文件 - n KB”链接。还有一个(即将推出)的针对本教程所有步骤文件的存档,位于“问答”页面(即将推出)。我仍然建议你跟着我们一起学习;这样,你可以在编码的同时学习。如果你在学习本教程的过程中遇到问题,请随时
- 在此页面底部的留言板上发帖。
- 查看本教程的问答页面 - 即将推出。
图1显示了我们的软件最终将如何工作的示意图。客户端调用服务器上的一个方法,然后服务器通过连接点向客户端触发一个事件。这个连接点的事件接收器在客户端实现(使用MFC和ClassWizard!!!),然后客户端向用户显示一条消息,告诉用户服务器说了“你好!”
请记住,本教程中我们开发软件的步骤如下:
- 第1步:使用ATL项目向导创建服务器,HelloWorldServ.NET。
- 第2步:修改ATL项目向导提供的入门文件。
- 第3步:向服务器添加一个简单的ATL对象
HelloWorld
,以公开我们的功能。 - 第4步:向服务器添加一个方法
SayHello()
,该方法触发客户端处理的事件。 - 第5步:我们研究连接点并设置服务器端的连接点。
- 更多步骤即将推出!
我们目前在本教程的第3步,我们将使用ATL对象向导向服务器添加一个简单的COM对象,即HelloWorld
对象。这一步会很快过去,所以不要让我再唠叨了,我们开始吧。
第3步:向服务器添加一个简单的HelloWorld COM对象
要向ATL服务器添加COM对象,我们可以使用Visual C++.NET 2003提供的ATL简单对象向导,也可以手动添加代码。我喜欢在可以的时候使用Visual C++提供的向导,但话说回来,我就是懒:)。
我们继续。打开类视图,右键单击“HelloWorldServNET
”文本——加粗且位于最顶部——然后指向“添加”,再点击“添加类”。“添加类”对话框将打开,如下图所示的图2。如所示,打开“Visual C++”文件夹,打开“ATL”文件夹,点击左侧窗格中的“ATL”文件夹,然后点击右侧窗格中的“ATL简单对象”,再点击“打开”。
ATL简单对象向导将出现,如下图所示的图3。点击“**名称**”选项卡,然后在“短名称”框中键入HelloWorld
,如下图所示。HelloWorld
是我们COM对象的名称,负责公开客户端想要看到的功能。COM如何工作以及COM如何做到这一点超出了本文的范围。
HelloWorld
。请注意,我们在上面保持“属性”复选框未选中。同样,我希望你,亲爱的读者,最大限度地减少隐藏在你面前的东西,以便你可以边学边做。属性会破坏这个目的。请注意,当我们键入“短名称”框中的HelloWorld
时,向导会为我们填写其余字段。有关这些字段含义的更多信息,请单击“帮助”。在本教程中,我们将保留这些字段的默认设置,但也许你特定的COM应用程序可能需要更改其值。
现在我们准备双击检查“选项”选项卡的设置是否正确。因此,点击“选项”选项卡,然后确保向导如下所示,**图4**。
HelloWorld
对象指定正确的选项。你可能需要根据你的具体应用程序更改这些设置,但就本教程而言,这些设置是正确的。**提示**:你可以通过将鼠标指针悬停在要了解的设置上来快速获取有关每个设置含义的帮助。
我所做的与默认值不同的选择是:
- **将聚合更改为否**:我们不希望支持将此对象聚合到另一个对象中。
- **将接口从Dual更改为Custom,并取消选中Automation Compatible**:同样,我们只需要一个简单的接口。创建Dual或Automation Compatible接口会引入大量不必要的额外代码,而这对于我们这样一个简单的“Hello, World!”示例来说是不必要的。
- **在支持下,选中了连接点**:同样,根据我们在**图1**中的模型,我们希望在调用服务器上的方法后,服务器能够向客户端触发一个事件,而这就是通过连接点实现的。所以,让我们让Visual C++为我们提供代码来支持这些。
一旦你满意,点击“完成”。Visual C++.NET 2003将生成与我们新对象相关的资源和源文件。它可能还会打开编辑器中的新文件。让我们关闭编辑器中当前打开的所有文件,以便为下一步重新开始。因此,此步骤中的下一个操作是单击菜单栏上的“窗口”菜单,然后单击“关闭所有文档”。
为了美观
当我们完成ATL简单对象向导的使用后,类视图应该显示如**图5**所示。不过,我们还需要做最后一个更改。请注意,在**图5**中,事件接口的名称不是_IHelloWorldEvents
,而是DHelloWorldEvents
?这是我对代码进行了一些小更改的结果。
HelloWorld
对象并更改事件接口名称后,类视图。我们的事件接口的DHelloWorldEvents
名称是Microsoft的一个松散约定,我正在遵循它。DHelloWorldEvents
是一个dispinterface
,所以在其名称前面添加了一个**D**。明白了吗?这只是我自己的审美观在作祟……如果你不喜欢这个新名称,那就不要更改它。但是,从现在开始,所有内容都将引用DHelloWorldEvents
,而不是_IHelloWorldEvents
。
还记得“在文件中查找”吗?现在也有类似“在文件中替换”的功能
所以这个改变看起来很吓人,对吧?我们必须打开项目中的每一个文件,并确保在_IHelloWorldEvents
出现的每一个地方,我们都替换成了DHelloWorldEvents
。实际上,Visual C++.NET 2003编辑器中有一个很棒的新功能可以减轻我们的烦恼。要使用该功能,请点击“编辑”菜单,然后点击“替换”。“替换”对话框将打开,如下图所示的图6。
_IHelloWorldEvents
更改为DHelloWorldEvents
。这里真的没有什么好写的,因为它看起来和以前的“替换”对话框一样。啊哈!在“搜索”分组框下,有单选按钮指定你希望替换在哪里发生。我们希望我们的查找和替换操作跨越**整个项目**,所以点击“当前项目”,如下图所示。接下来,点击“全部替换”。Visual C++将显示警告,如下图所示的图7。
如果一切顺利,你应该会看到一个对话框快速出现然后消失;这是正常的,因为该对话框只是报告替换操作在整个项目中的进度!最后,Visual C++将报告它已完成,如下图所示的图8。
如果一切顺利,类视图现在应该与**图5**完全相同。最后,你需要确保保存你的更改。为此,请点击“文件”菜单,然后点击“全部保存”。还有最后一件事要做。显然,上面的“替换”选项会替换某个项目文件中`#include`指令中的文件名,但不会更改相应的*.H*文件的名称。所以我们需要自己来做,以避免编译器错误。请执行以下操作:
- 转到解决方案资源管理器,然后打开文件HelloWorld.H。
- 将**列表1**中显示的.H代码替换为**列表2**中显示的.H代码,然后保存文件。
#include "DHelloWorldEvents_CP.h"
#include "_IHelloWorldEvents_CP.h"
幕后笔记
我们现在已经完成了本教程的第3步。我们向项目中添加了一个名为HelloWorld
的简单COM对象,然后,为了美观,使用项目范围的替换操作修改了其dispinterface
的名称,这是一个Visual Studio .NET中的很棒的新功能!当然,我也有我的审美原因,但我真的想更改接口的名称,以便突出Visual Studio .NET的“跨项目替换”功能。
对象的自定义设置和清理
在完成此步骤之前,让我带你了解一下向导为你覆盖的两个不错的函数,它们具有默认实现。这些函数允许你在服务器上提供自定义处理,即在CoCreateInstance()
成功的那一刻——有点像“COM的构造函数”。此外,当最后一个客户端调用你的对象的IUnknown::Release()
(请参阅其他文章或文档了解更多信息)时,ATL框架还提供了一个函数,帮助你提供服务器对象自定义清理。
这两个函数分别称为FinalConstruct()
和FinalRelease()
,在我们的项目中,它们在HelloWorld.h中被实现,如下面的**列表3**所示。函数本身用**粗体**突出显示。
class ATL_NO_VTABLE CHelloWorld :
public CComObjectRootEx<CCOMSINGLETHREADMODEL>,
public CComCoClass<CHELLOWORLD, &CLSID_HelloWorld>,
public IConnectionPointContainerImpl<CHELLOWORLD>,
public CProxyDHelloWorldEvents<CHELLOWORLD>,
public IHelloWorld
{
public:
CHelloWorld() // initialize things to NULL or
// zero, but that's it here
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_HELLOWORLD)
DECLARE_NOT_AGGREGATABLE(CHelloWorld)
BEGIN_COM_MAP(CHelloWorld)
COM_INTERFACE_ENTRY(IHelloWorld)
COM_INTERFACE_ENTRY(IConnectionPointContainer)
END_COM_MAP()
BEGIN_CONNECTION_POINT_MAP(CHelloWorld)
CONNECTION_POINT_ENTRY(__uuidof(DHelloWorldEvents))
END_CONNECTION_POINT_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct() // custom initialization here
{
return S_OK;
}
void FinalRelease() // custom cleanup here
{
}
public:
};
OBJECT_ENTRY_AUTO(__uuidof(HelloWorld), CHelloWorld)
请注意,FinalConstruct()
的返回类型是HRESULT
。因此,如果CoCreateInstance()
过程中没有其他失败,FinalConstruct()
将在该过程的最后被调用,*如果*之前的引用计数为零。因此,如果你的逻辑在FinalConstruct()
体内失败,你可以返回任何你想要的HRESULT
或DWORD
Win32系统错误代码,而这就是客户端将看到的客户端CoCreateInstance()
调用的返回值。
在COM中,你永远无法确定操作系统何时会调用构造函数或析构函数。没错,COM将这种控制权从你手中夺走了。你唯一能知道的是,第一个客户端调用了你的对象的CoCreateInstance()
(或其变种),以及**最后一个客户端**调用了IUnknown::Release()
。请注意,由于COM对象是引用计数的,因此上述两个函数仅在第一次CoCreateInstance()
和最后一次IUnknown::Release()
时被调用。所以要小心你在这些函数中放入什么逻辑。
但我什么时候会使用FinalConstruct/FinalRelease?
例如,假设你的COM对象拥有——并且跟踪——一个Hashable
。因此,调用FinalConstruct()
是让对象读取配置文件中的配置值并将其存储在哈希表中,供以后调用方法时使用的一个好地方。如果你的哈希映射(例如,字符串到指针),你将在对象上的引用计数降至零且Windows想要销毁对象时,想要释放与这些指针相关的内存。因此,你覆盖FinalRelease()
并提供代码来迭代你的哈希表,查找然后释放每个指针所指向的内存。
进入下一步(或上一步)!
要继续下一步,即第4步,请单击下面的“**下一步>>**”链接。单击下面的“**<<上一步**”可以访问第2步并刷新你的记忆,甚至访问第1步。
**提示**:如果你遇到麻烦或无法理解某事,通常是因为你在本教程中做得太快而没有彻底遵循,并且下载了最新完成的步骤的代码。也许如果你回到前面的步骤,并在不清楚的地方仔细研究本教程,这可能会有帮助。此外,也可能是因为还有更多步骤尚未编写!敬请期待!
**提示**:另外,如果你有问题,请随意在下面的留言板上发帖,该留言板位于本文页面底部。我会在你这样做时收到电子邮件,然后每个人都可以看到你的问题和我的答案。别忘了给这篇文章评分!如果你给的分数低于5,请在留言板上说明原因,以便我能为所有人改进这些文章。