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

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.41/5 (12投票s)

2005年12月31日

CPOL

11分钟阅读

viewsIcon

73881

downloadIcon

390

我们关注连接点,并为我们简单的DCOM“Hello, World!”服务器设置其一端。

引言

欢迎来到我的DCOM教程第5步。在本系列中,我将通过一个全面的教程和简单的示例,剥去DCOM的神秘面纱,消除其带来的头痛和困惑。好吧,不敢保证——但我会尽力而为。

如果你想跟着本教程一起学习,并在此过程中添加代码并使用Visual C++向导,那太好了。事实上,我强烈推荐这样做,否则本教程就是电子墨水(?)的巨大浪费。然而,我在编写教程时,自己也完全按照教程进行,并像我建议你那样使用Visual C++向导开发代码。事实上,截图是我为每一步开发文件时生成的!要下载这些已开发好的代码与你自己的进行比较,只需点击每一步顶部的“下载第n步文件 - n KB”链接。还有一个所有步骤文件的存档(即将推出),位于本教程的“问答”页面(即将推出)。我**仍然**建议你跟着我一起学习;这样,你就可以在编码的同时学习。如果你在本教程的过程中遇到任何问题,请随时

  • 在此页面底部的留言板上发帖。
  • 查看本教程的问答页面——即将推出。

如图1所示,展示了我们的软件最终将如何工作的流程图。客户端调用服务器上的一个方法,服务器然后通过连接点向客户端触发一个事件。这个连接点的事件接收器是在客户端实现的(使用MFC和ClassWizard!!!),客户端向其用户显示一条消息,告知用户服务器说“Hello!”

Diagram of our DCOM client/server set-up.

图1. 我们DCOM客户端/服务器设置的流程图。

请记住,本教程中我们开发软件的步骤如下:

  • 第1步:使用ATL项目向导创建服务器HelloWorldServ.NET
  • 第2步:修改ATL项目向导提供的启动文件。
  • 第3步:向服务器添加一个简单的ATL对象HelloWorld,以公开我们的功能。
  • 第4步:向服务器添加一个方法SayHello(),该方法触发客户端处理的事件。
  • 第5步:我们关注连接点,并设置其服务器端。
  • 更多步骤即将推出!

我们目前在教程的第5步。这一步我们完成了服务器的基本设计,如上面图1所示。我们已经定义并实现了IHelloWorld::SayHello()方法,但它仍然没有实现应有的功能——向客户端返回“Hello, World!”。现在是时候让它实现了。但在此之前,我们先来一点关于连接点是什么的入门——或者说复习?——然后再开始第5步?

连接点揭秘

在我们深入我们教程的第5步之前,请允许我花点时间揭开连接点的神秘面纱。下面图2展示了一个通用的场景,它适用于COM、DCOM,甚至函数回调,天呐。

A source and a sink.
图2. 源和接收器。

这涉及到两个对象,“源”和“接收器”。将“源”想象成家里厨房水槽的水龙头。你拧开把手,就会有东西流出来(希望是水)。它去哪里?如果没有任何堵塞,这些水就会流到底部并进入排水管(可以看作是“接收器”)。好的,所以东西是从源头流向接收器。在上面的厨房水槽类比中,这是水。然而,我从未见过一个计算机网络系统用水流过导线运行,所以显然DCOM中还有别的东西在起作用。

在DCOM中,网络上有“客户端”,网络上也有“服务器”。如果不使用连接点,信息只能单向流动:方法调用取代了我们的水,客户端取代了水龙头,服务器取代了排水管。这**大大地简化了问题**,但用户“拧开把手”(例如,点击一个按钮),“东西”(例如,方法调用)就“从”客户端“流出”。这些“流出的东西”然后通过DCOM“流”过网络。这些调用“流”到服务器,然后服务器收集它们并像“排水管”或我们的接收器一样工作。这是图3,它与图2几乎完全相同,但将客户端放在“源”的位置,将服务器放在“接收器”的位置,中间是网络。

Our client and server as the source and the sink.

图3. 我们的客户端和服务器作为源和接收器。

好的,现在我们有了像水一样流动的调用方法,很棒。然而,当客户端调用方法时,服务器会做很多可能对客户端感兴趣的事情。所以服务器到处触发事件。如果我们的客户端不在乎服务器触发事件,它就会忽略它们。然而,如果它关心,它会调用服务器的Advise()。然后,图3的源-接收器关系可以反过来考虑。

The reverse of Figure 2.

图4. 图3的反向。

当你遇到以下情况时,连接点就派上用场了:

  1. 客户端是方法调用的**源**。
  2. 服务器**接收**(即充当接收器)方法调用。
  3. “事件调用”从现在的服务器作为**源**发出。
  4. 客户端**接收**事件调用并执行某些操作。

正如你所见,这是一个往返过程。方法调用从**客户端**到**服务器**,然后事件调用从**服务器**到**客户端**,如图5所示。

A round-trip.

图5. 往返。这只是对我们设计(如图1所示)的重申。

Advise()步骤在上述第1项之前完成,而Unadvise()步骤(客户端重新变得冷漠)在第4项之后完成。客户端和服务器上的接触点,以及发生的Advise()Unadvise(),共同构成所谓的连接点。现在,我们准备开始第5步。

第5步:向事件源接口DHelloWorldEvents添加OnSayHello事件

我们开始吧?Visual C++ .NET 2003 让向事件源添加事件变得微不足道。只需使用Visual C++向导!但是,你必须知道点哪里。要添加一个实际上让我们的服务器说“hello”的方法,请打开类视图,然后点击加号展开HelloWorldServNETLib图标,如图6所示。

图6. 打开HelloWorldServNETLib图标,然后向DHelloWorldEvents添加方法。

右键单击DHelloWorldEvents接口图标,指向“添加”,然后单击“添加方法”,如上所示。这样做时,将出现“添加方法”向导,如下面的图7所示。

图7. 向DHelloWorldEvents dispinterface添加OnSayHello()方法。

我们希望将新方法命名为OnSayHello。将“返回类型”字段保留为HRESULT,并在“方法名称”框中输入OnSayHello,如图所示。

接下来,我们将向方法添加一个参数。这将是服务器运行的计算机名称。这将有助于我们在测试时——确保DCOM确实在工作,并且我们实际上是在一台与安装了客户端的计算机不同的计算机上运行。为此,请完成以下步骤:

  1. 在“参数属性”下,选中“In”复选框。
  2. 然后,在“参数类型”框中输入BSTRBSTR是一种特殊的COM兼容字符串类型。
  3. 该参数应给出服务器运行的计算机名称;因此,我们将参数命名为bstrHostName
  4. 在“参数名称”框中输入bstrHostName,然后单击“添加”。
  5. 完成后,“添加方法”向导应如上面图7所示。

你可能会注意到向导对话框中还有一个“IDL属性”选项卡。它包含更高级的设置;对我们来说,默认设置是合适的,因此请保留该选项卡上的默认设置。单击“完成”关闭向导,并将OnSayHello()方法添加到DHelloWorldEvents。Visual C++随后会打开HelloWorldServNET.idl文件,我们不再需要编辑它,因此可以关闭该文件。

要检查你的工作,请返回类视图。点击加号展开HelloWorldServNETLib图标,然后同样展开DHelloWorldEvents图标,如下面的图8所示。

图8. 验证我们新的OnSayHello()事件方法是否存在。

服务器:最后几步

现在是时候完成我们这个简单的COM服务器的实现了。切换到解决方案资源管理器。接下来,右键单击HelloWorldServNET.idl文件,然后单击“编译”。请确保已完成此步骤。

图9. 重新实现连接点DHelloWorldEvents,现在它有了OnSayHello()事件。

现在,在类视图中,右键单击CHelloWorld类,指向“添加”,然后单击“添加连接点”,如上面图9所示。我知道我们已经有一个连接点了,但是重新执行这个过程——就像我们现在这样——可以刷新项目,使我们能够从IHelloWorld::SayHello()的实现中触发OnSayHello()事件。

图10. 使用“实现连接点”向导选择DHelloWorldEvents连接点。

选择“添加连接点”命令后,将出现“实现连接点”向导,如图10所示。单击“**>**”按钮将DHelloWorldEvents dispinterface名称从“源接口”移动到“实现连接点”框中,然后单击“完成”。向导完成后,我们确实需要删除一些重复的代码。向导完成后,DHelloWorldEvents_CP.h文件将打开,如下面的图11所示。文件顶部应如下所示:

图11. 编辑器现在打开了一个新文件,名为DHelloWorldEvents_CP.h。这显示了我们新文件的开头几行。

还记得在第2步(如果你需要复习,请点击步骤名称)中,我们使用替换的新功能将项目中的所有_IHelloWorldEvents替换为DHelloWorldEvents吗?_IHelloWorldEvents定义在一个名为_IHelloWorldEvents_CP.h的文件中,该文件也被#includeHelloWorld.h中。因此,我们需要清理一下。为此,请执行以下操作:

  1. 首先,滚动浏览DHelloWorldEvents_CP.h文件,并删除你可能找到的任何**额外**的CProxyDHelloWorldEvents<T>类定义。此文件中应只定义**一个**类。如果已经是这种情况,则跳过此步骤。正确的定义如上面图11所示。
  2. 保存DHelloWorldEvents_CP.h文件并关闭它。
  3. 现在,打开解决方案资源管理器,然后单击_IHelloWorldEvents_CP.h文件的图标。
  4. 按Backspace键将_IHelloWorldEvents_CP.h文件从项目中删除。
  5. 在解决方案资源管理器中,双击HelloWorld.h文件的图标。
  6. HelloWorld.h文件中,删除这一行:
    #include "_IHelloWorldEvents_CP.h"
  7. 保存HelloWorld.h文件并关闭它。

最后,单击“文件”菜单,然后单击“全部保存”。完成后,类视图应如图12所示。

图12. 类视图的正确外观。

现在,在我们目前的设置下,为了向客户端说“hello”,我们只需在CHelloWorld::SayHello()中调用Fire_OnSayHello()成员函数。由于CProxyDHelloWorldEvents<T>实际上是CHelloWorld类的基类,所以这样做是有效的。在类视图中,双击CHelloWorld::SayHello()成员函数的图标,在编辑器中打开该函数定义。现在,添加下面清单1中**粗体**所示的代码。

STDMETHODIMP CHelloWorld::SayHello()
{
    USES_CONVERSION;

    // Get the network name of this computer

    TCHAR szComputerName[MAX_COMPUTERNAME_LENGTH + 1];
    DWORD dwSize = MAX_COMPUTERNAME_LENGTH + 1;

    if (!GetComputerName(szComputerName, &dwSize))
        return E_FAIL;
        // failed to get the name of this computer


    // Say Hello to the client

    Fire_OnSayHello(T2BSTR(szComputerName));

    return S_OK;
}
清单1. 要添加的代码,以完成CHelloWorld::SayHello()成员函数。

后记

现在我们已经完成了教程的第5步。这是服务器编码的结束。下一步,即第6步,将指导你完成最终的构建过程,以及如何在服务器和客户端计算机上设置你的新服务器(和客户端)。要转到上一步,第4步,请单击下面的**<< 上一步**。或单击**下一步>>**转到第6步(即将推出!)。如果你有疑问,可以尝试单击**问答**(即将推出!)转到可能对你有帮助的页面。

<< 上一步 | 下一步>> - 即将推出!

问答 - 即将推出!

提示:如果你遇到困难或不理解某些内容,通常是因为你在本教程中一直前进,但没有完全跟进并下载最新完成的步骤的代码。也许你可以回到前面的步骤,并在此过程中不清楚的地方重新学习,这可能会有帮助。另外,也可能是因为还有更多步骤尚未编写!敬请关注!

提示:另外,如果你有疑问,请在下面的留言板上发帖,留言板位于本文页面底部。你发帖后,我会收到一封电子邮件,然后大家都可以看到你的问题和我的回答。别忘了给这篇文章评分!如果你给了它低于5分,请在留言板(下方)说明原因,以便我能为所有人改进这些文章。

© . All rights reserved.