Developing COM Components using VC-ATL

第一节 用 ATL 创建 COM 组件


Project Name 编辑框中输入项目名如 MyProj ,并选择合适的 Location 后,按确认按钮进入下一个对话框: ATL  COM  Appwizard  -  step 1 of  1 ,在 Server Type 中选择 Dynamic  Link  Library [ DLL ] ,即进程内服务器,这是最快的组件。
选中 Support  MFC 选择项。
在按下 Finish Ok 按钮后,一个组件的框架已经建立。

二、给模型增加组件 MyCom
VC++ 菜单 Insert 中选中 New ATL  Object… 菜单项,出现 ATL Object  Wizard 对话框。
在左边的 Category 中选择 Objects ,右边的 Objects 中选中 Simple  Object 项。按 Next 按钮。
在出现的 ATL Object  Wizard 属性对话框中 Names 卡片中的八个编辑框中左上方的 Short Name 编辑框中输入短名如 MyCom ,其他七个编辑框的内容会自动生成。然后按确认按钮退出。

三、给组件增加方法 ( 函数 ) MyF1 MyF2 MyF3 MyF4
VC++ 工作平台的左边的 Workspace ClassView 卡片中找到接口 IMyCom 项,按右键,在出现的快捷菜单中选择 Add  Method … ,出现 Add  Method  to Interface 对话框,在对话框中输入要增加的函数的函数名、参数和返回值类型。然后,按确认按钮退出。
先增加函数 MyF1
  
函数名为: MyF1
  
参数:无
MyCom.cpp 文件中插入代码:
STDMETHODIMP CMyCom::MyF1()
{
 AFX_MANAGE_STATE(AfxGetStaticModuleState())
 // TODO: Add your implementation code here
 AfxMessageBox("
欢迎使用我的组件 ");
 return S_OK;
}
用同样的方法给组件增加函数 MyF2
函数名为: MyF2
参数为:
  [in] BSTR str,
  [out, retval] int* val
插入代码:
STDMETHODIMP CMyCom::MyF2(BSTR str, int *val)
{
 AFX_MANAGE_STATE(AfxGetStaticModuleState())
 // TODO: Add your implementation code here
 CString  sStr(str);
 int  n = sStr.GetLength();
 *val = n;
 return S_OK;
}
用同样的方法给组件增加函数 MyF3
函数名为: MyF3
参数为:
  [in] BSTR str,
  [out, retval] BSTR* retstr
插入代码 :
STDMETHODIMP CMyCom::MyF3(BSTR str, BSTR *retstr)
{
 AFX_MANAGE_STATE(AfxGetStaticModuleState())
 // TODO: Add your implementation code here
 CString sStr(str);
 CString sRetstr = "
组件收到你的信息: <" + sStr + ">/n 特此告之。 ";
 CComBSTR  temp(sRetstr);
 *retstr = temp;
 return S_OK;
}
用同样的方法给组件增加函数 MyF4
函数名为: MyF4
参数为:
  [in] int x,
  [out, retval] int* val
插入代码:
STDMETHODIMP CMyCom::MyF4(int x, int *val)
{
 AFX_MANAGE_STATE(AfxGetStaticModuleState())
 // TODO: Add your implementation code here
 *val = x+1;
 return S_OK;
}
编绎工程,生成组件DLL。
如果是在 Win2k 或者 WinXp 的计算机上完成了上面的三步操作,就会在 debug 子目录下生成组件的 DLL 文件,并且完成自动注册。但是在 Win98 的计算机上,注册要运行如下命令才能完成:
regsrv32  C:/MyProj/Debug/MyProj.dll



1

1

2
,把服务器类型库导入客户
2-1
StdAfx.h 文件中加入代码 :
#import  "../MyProj/MyProj.tlb"
最后的 StdAfx.h 文件如下:
// stdafx.h : include file for standard system include files,
//  or project specific include files that are used frequently, but
//      are changed infrequently
//
#if !defined(AFX_STDAFX_H__2B646017_28AD_4CDE_9792_CB8F9A5C6B39__INCLUDED_)
#define AFX_STDAFX_H__2B646017_28AD_4CDE_9792_CB8F9A5C6B39__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#define VC_EXTRALEAN  // Exclude rarely-used stuff from Windows headers
#include <afxwin.h>         // MFC core and standard components
#include <afxext.h>         // MFC extensions
#include <afxdisp.h>        // MFC Automation classes
#include <afxdtctl.h>  // MFC support for Internet Explorer 4 Common Controls
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h>   // MFC support for Windows Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT
#import  "../MyProj/MyProj.tlb"
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_STDAFX_H__2B646017_28AD_4CDE_9792_CB8F9A5C6B39__INCLUDED_)
编译 StdAfx.cpp
这样在客户端的 debug 目录下生成组件的类型库头文件 (.tlh) 和类型库实现文件 (.tli)
2-2 在使用组件的源文件上方使用名字空间,最后的源文件上面部分代码如下
// MyExeDlg.cpp : implementation file
//
#include "stdafx.h"
#include "MyExe.h"
#include "MyExeDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
using namespace MYPROJLib;
以下代码略
 
3 ,初始化 COM
HRESULT hr=CoInitialize(NULL);

4
,获得服务器组件的 CLSID
CLSID  clsid; 
hr=CLSIDFromProgID(OLESTR("MyProj.MyCom"),&clsid);
if(FAILED(hr))
{
         AfxMessageBox("COM Failed");
         return;
}

5
,创建 COM 服务器组件实例,获得组件接口指针
IMyCom  *ptr = NULL;
hr=CoCreateInstance(clsid,NULL,CLSCTX_INPROC_SERVER,__uuidof(IMyCom),(LPVOID*)&ptr); 

6-1
,在 OnMyF1() 中使用 COM 对象 MyF1()
ptr->MyF1();
ptr->Release(); //
释放实例

6-2
,在 OnMyF2() 中使用 COM 对象 MyF2()
char str[32];
sprintf(str,"Len=%d",ptr->MyF2("abcdefg"));
AfxMessageBox(str);
ptr->Release();// 释放实例

6-3
,在 OnMyF3() 中使用 COM 对象 MyF3()
AfxMessageBox(ptr->MyF3("abcdefg"));
ptr->Release(); // 释放实例

6-4
,在 OnMyF4() 中使用 COM 对象 MyF4()
int x=8; char str[32];
sprintf(str,"x=%d,x+1=%d",x,ptr->MyF4(x));
AfxMessageBox(str);
ptr->Release();// 释放实例

7
,终止 COM
CoUninitialize();
下面为客户测试端四个函数的完整代码。
// MyExeDlg.cpp : implementation file
//
 
#include "stdafx.h"
#include "MyExe.h"
#include "MyExeDlg.h"
 
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
 
using namespace MYPROJLib;
/
// CAboutDlg dialog used for App About
 
class CAboutDlg : public CDialog
{
public:
         CAboutDlg();
 
// Dialog Data
         //{{AFX_DATA(CAboutDlg)
         enum { IDD = IDD_ABOUTBOX };
         //}}AFX_DATA
 
         // ClassWizard generated virtual function overrides
         //{{AFX_VIRTUAL(CAboutDlg)
         protected:
         virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
         //}}AFX_VIRTUAL
 
// Implementation
protected:
         //{{AFX_MSG(CAboutDlg)
         //}}AFX_MSG
         DECLARE_MESSAGE_MAP()
};
 
CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
         //{{AFX_DATA_INIT(CAboutDlg)
         //}}AFX_DATA_INIT
}
 
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
         CDialog::DoDataExchange(pDX);
         //{{AFX_DATA_MAP(CAboutDlg)
         //}}AFX_DATA_MAP
}
 
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
         //{{AFX_MSG_MAP(CAboutDlg)
                   // No message handlers
         //}}AFX_MSG_MAP
END_MESSAGE_MAP()
 
/
// CMyExeDlg dialog
 
CMyExeDlg::CMyExeDlg(CWnd* pParent /*=NULL*/)
         : CDialog(CMyExeDlg::IDD, pParent)
{
         //{{AFX_DATA_INIT(CMyExeDlg)
                   // NOTE: the ClassWizard will add member initialization here
         //}}AFX_DATA_INIT
         // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
         m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
 
void CMyExeDlg::DoDataExchange(CDataExchange* pDX)
{
         CDialog::DoDataExchange(pDX);
         //{{AFX_DATA_MAP(CMyExeDlg)
                   // NOTE: the ClassWizard will add DDX and DDV calls here
         //}}AFX_DATA_MAP
}
 
BEGIN_MESSAGE_MAP(CMyExeDlg, CDialog)
         //{{AFX_MSG_MAP(CMyExeDlg)
         ON_WM_SYSCOMMAND()
         ON_WM_PAINT()
         ON_WM_QUERYDRAGICON()
         ON_BN_CLICKED(IDC_BUTTON1, OnMyF1)
         ON_BN_CLICKED(IDC_BUTTON2, OnMyF2)
         ON_BN_CLICKED(IDC_BUTTON3, OnMyF3)
         ON_BN_CLICKED(IDC_BUTTON4, OnMyF4)
         //}}AFX_MSG_MAP
END_MESSAGE_MAP()
 
/
// CMyExeDlg message handlers
 
BOOL CMyExeDlg::OnInitDialog()
{
         CDialog::OnInitDialog();
 
         // Add "About..." menu item to system menu.
 
         // IDM_ABOUTBOX must be in the system command range.
         ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
         ASSERT(IDM_ABOUTBOX < 0xF000);
 
         CMenu* pSysMenu = GetSystemMenu(FALSE);
         if (pSysMenu != NULL)
         {
                   CString strAboutMenu;
                   strAboutMenu.LoadString(IDS_ABOUTBOX);
                   if (!strAboutMenu.IsEmpty())
                   {
                            pSysMenu->AppendMenu(MF_SEPARATOR);
                            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
                   }
         }
 
         // Set the icon for this dialog. The framework does this automatically
         // when the application's main window is not a dialog
         SetIcon(m_hIcon, TRUE);                            // Set big icon
         SetIcon(m_hIcon, FALSE);                  // Set small icon
        
         // TODO: Add extra initialization here
        
         return TRUE; // return TRUE unless you set the focus to a control
}
 
void CMyExeDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
         if ((nID & 0xFFF0) == IDM_ABOUTBOX)
         {
                   CAboutDlg dlgAbout;
                   dlgAbout.DoModal();
         }
         else
         {
                   CDialog::OnSysCommand(nID, lParam);
         }
}
 
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
 
void CMyExeDlg::OnPaint()
{
         if (IsIconic())
         {
                   CPaintDC dc(this); // device context for painting
 
                   SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
 
                   // Center icon in client rectangle
                   int cxIcon = GetSystemMetrics(SM_CXICON);
                   int cyIcon = GetSystemMetrics(SM_CYICON);
                   CRect rect;
                   GetClientRect(&rect);
                   int x = (rect.Width() - cxIcon + 1) / 2;
                   int y = (rect.Height() - cyIcon + 1) / 2;
 
                   // Draw the icon
                   dc.DrawIcon(x, y, m_hIcon);
         }
         else
         {
                   CDialog::OnPaint();
         }
}
 
// The system calls this to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CMyExeDlg::OnQueryDragIcon()
{
         return (HCURSOR) m_hIcon;
}
 
void CMyExeDlg::OnMyF1()
{
         // TODO: Add your control notification handler code here
         HRESULT hr=CoInitialize(NULL);
         CLSID clsid;
         hr=CLSIDFromProgID(OLESTR("MyProj.MyCom"),&clsid);
         if(FAILED(hr))
         {
                   AfxMessageBox("COM Failed");
                   return;
         }
         IMyCom *ptr = NULL;
         hr=CoCreateInstance(clsid,NULL,CLSCTX_INPROC_SERVER,__uuidof(IMyCom),(LPVOID*)&ptr);
         ptr->MyF1();
         ptr->Release(); // 释放实例
         CoUninitialize();
}
 
void CMyExeDlg::OnMyF2()
{
         // TODO: Add your control notification handler code here
         HRESULT hr=CoInitialize(NULL);
         CLSID clsid;
         hr=CLSIDFromProgID(OLESTR("MyProj.MyCom"),&clsid);
         if(FAILED(hr))
         {
                   AfxMessageBox("COM Failed");
                   return;
         }
         IMyCom *ptr = NULL;
         hr=CoCreateInstance(clsid,NULL,CLSCTX_INPROC_SERVER,__uuidof(IMyCom),(LPVOID*)&ptr);
         char str[32];
         sprintf(str,"Len=%d",ptr->MyF2("abcdefg"));
         AfxMessageBox(str);
         ptr->Release();// 释放实例
         CoUninitialize();
}
 
void CMyExeDlg::OnMyF3()
{
         // TODO: Add your control notification handler code here
         HRESULT hr=CoInitialize(NULL);
         CLSID clsid;
         hr=CLSIDFromProgID(OLESTR("MyProj.MyCom"),&clsid);
         if(FAILED(hr))
         {
                   AfxMessageBox("COM Failed");
                   return;
         }
         IMyCom *ptr = NULL;
         hr=CoCreateInstance(clsid,NULL,CLSCTX_INPROC_SERVER,__uuidof(IMyCom),(LPVOID*)&ptr);
         AfxMessageBox(ptr->MyF3("abcdefg"));
         ptr->Release(); // 释放实例
         CoUninitialize();
}
 
void CMyExeDlg::OnMyF4()
{
         // TODO: Add your control notification handler code here
         HRESULT hr=CoInitialize(NULL);
         CLSID clsid;
         hr=CLSIDFromProgID(OLESTR("MyProj.MyCom"),&clsid);
         if(FAILED(hr))
         {
                   AfxMessageBox("COM Failed");
                   return;
         }
         IMyCom *ptr = NULL;
         hr=CoCreateInstance(clsid,NULL,CLSCTX_INPROC_SERVER,__uuidof(IMyCom),(LPVOID*)&ptr);
         int x=8; char str[32];
         sprintf(str,"x=%d,x+1=%d",x,ptr->MyF4(x));
         AfxMessageBox(str);
         ptr->Release();// 释放实例
         CoUninitialize();
}
 
 
通过这个简单的例子,我们做了最简单的COM组件,及测试组件的客户端,在这里只是教大家如何用VC ATL做COM组件,在接下来这一篇里Developing COM Components using VC-ATL(2)将对COM的基本原理进行介绍,以及对这一例子的源代码进行剖析。
 
Developing COM Components using VC-ATL(1)完
 
 第二节 COM 基础知识
COM 的产生
应用程序被开发来支持公司业务运转,应用程序获取作为输入的数据,处理基于业务规则的数据,和提供作为输出的数据和信息。基于这一事实,所有的应用程序都有三个要素:
n          用户界面或呈现要素,通过它们可以输入数据。
n          应用程序逻辑或业务规则要素,它们有助于实现对输入数据执行有效性、可用性操作。
n          数据存储或数据管理要素,它们管理数据的存储和恢复。
这三个要素养组成了用于应用程序开发模型或体系结构的基础。
 
根据具体的用途和配置,开发都在他们的应用程序中决定采用何种体系结构时有三种选择:
n          单层模型
n          双层模型
n          三层或多层模型
 
作为一个开发者,在决定应用程序的体系结构之前,你要考虑以下因素:
n          伸缩性:这指的是应用程序适应新的硬件环境的性能。例如,当一个应用程序从一个单处理器计算机移到一个多处理计算机时,它是否能使用新增加的处理器,这些处理器在新硬件上是否发挥了作用。
n          可用性:这指的是应用程序满足用户增加要求的能力。可能有一百个用户正在请求同样的数据,但是只有少数用户获得数据,其他的用户则显示一则消息“请稍后再试”。
n          扩展性:这指的是应用程序有时递增地增加特色来提高性能和提供更好的功能给用户的能力。
n          维护性:这指的是根据人力和时间使用最小化的资源和最大化的有效性来响应修修复改错误的能力。
n          安全性:这指的是应用程序维持数据完整性、可靠性的能力。任何未经认可的用户都不允许篡改数据。
n          性能:这指的是应用程序在最短的时间内响应用户请求的能力。用户不要等待很长时间就能获得请求数据。
 
为了设计更健壮的应用程序体系结构,三 ( ) 层体系结构就运用而生。三层体系结构是一种逻辑应用程序观点,在这种体系结构中应用程序分成以下三层逻辑组成部分:
n          用户服务层。用户服务层管理用户界面。
n          业务服务层。业务服务层管理由应用程序实现的业务逻辑。业务逻辑由支配应用程序处理的规则组成。这一层对用户封装了业务逻辑,因而用户不必了解业务逻辑或业务规则是如何使用的。业务规则的任何变化将导致仅对业务服务层的更改,而应用程序的其余部分保持不变。业务服务层实现用于应用程序的多条规则。这一层扮演了用户服务层和数据服务层间连接的角色。
n          数据服务层。数据服务层负责维护数据,通常为 RDBMS 。这一层维护数据的有效性和完整性,并且负责从数据存储中增加、修改和删除数据。
 
现在,我们如何来做业务服务层呢 ? 答案就是 COM
 
什么是 COM?
COM 即为组件对象模型,它为开发组件定义了标准。它定义了组件交互标准、协议标准和组件的物理结构来确保组件间的互用性。一个组件就是一个执行特定操作的程序或二进制对象。下面我们就从组件的一些特点和功能来理解一下 COM
n          COM 是一种规范。 COM 规范描述了为创建能共同操作的组件而必须遵守的标准。这个标准描述了 COM 是什么和它们是如何运转的,以及 说明了如何可动态交替更新组件。
n          COM 是一套服务。 COM 规范是由一套服务或 APIs 来支持的。 COM 库提供这些服务,它们是 WIN32 平台操作系统的一部分并且可用于当作其他操作系统的一个单独的包。
n          COM 是面向对象的。 COM 组件对象有标识、状态和行为。
n          COM 能够容易的用户化和升级你的应用程序。 COM 组件动态地相互联结,并且 COM 定义了定位其他组件和鉴定其功能的标准。因此,组件可以交换而不必重新编绎整个应用程序。
n          COM 能够分布式应用。位置透明性是 COM 的特性之一。这使得你可以编写 COM 组件而不必考虑它们的位置。组件可以移动而不要求对使用它们的应用程序作任何改变。
n          COM 组件可以用任何语言编写。任何能够处理二进制语言都能用于创建 COM 对象。能创建组件的语言和工具包括 C/C++ JAVA VISUAL BASIC VISUAL C++ 等。
 
        到目前为止, 微软提倡的这套 COM 软件开发模式是最为符合现在计算机工业软件生产的方法。 Microsoft 的许多技术,如 ActiveX, DirectX 以及 OLE 等都是基于 COM 而建立起来的。并且 Microsoft 的开发人员也大量使用 COM 组件来定制他们的应用程序及操作系统。在 COM 构架下,程序员可以开发出各种各样的功能专一的组件,然后将它们按照需要组建起来,构成复杂的应用系统。由此带来的好处是多方面的:
n          使用组件的最大好处首先是可以将它们动态的插入或卸出应用系统,是真正意义上的软件模块即插即用。 组件可以在运行时刻,在不被重新链接或编译应用程序的情况下, 可以将系统中的组件用新的组件替换掉,以便即时方便地进行系统的升级和定制。 这样做的最大好处是程序员可以利用最新的技术写出的最新组件取代以有的旧组件而整个系统的其他部分不需要一点改动。应用程序可以随新组件不断取代旧的组件而渐趋完善且健壮。而且利用已有的组件,用户还可以快速的建立全新的应用。 ( 注意:替换的组件均为自定义的组件,不是 COM 本身的运行时间库中的组件 )
n          二进制重用。按照 COM 规范,组件可以在多个应用系统中重复利用同一组件。
n          分布式应用。利用 COM/DCOM 组件技术,可以方便的将应用系统扩展到网络环境下。
n          COM 与语言无关。可以使用多种语言编写 COM 组件,如 DELPHI VISUAL BASIC… 这使得程序员可以利用自己擅长的语言来编写组件,而无须学习其他专门的语言来编写组件。
n          COM 与平台无关。利用已编写的组件可以方便地在不同平台间移植。
 
在三层体系结构中,创建 COM 组件来实现业务服务层。因为 COM 组件能够相互作用,每个组件必须严格地贯彻由 COM 拟定的完整的标准。
 
COM 具有显著的优点在于它提供了三个独有的特性:
n          调用组件间功能的二进制标准。 COM 确保用 VISUAL BASIC 开发的客户端和用 VISUAL C++ 开发并且运行于 Solaris Mac 服务端的组件间完全的二进制的兼容性。因此, COM 允许你创建确实是独立于语言和操作系统的代码模块。
n          代码重用。因为接口是文档化的,所以创建对象的代码能被不同的客户使用。
n          版本控制。基于 COM 的组件是自我版本化的。暗示新的功能可以增加到组件中,而不影响已使用该组件的用户 (/ 客户 )
 
由此可见, COM 组件是真正地面向对象,提供简易的用户化,提供地址透明并且能用任何语言编写。正由于 COM 具有这些优点, COM 得到了广泛应用。
 
COM 组件是真正地面向对象
n          传统的面向对象编程
  面向对象编程是一个被广大编程人员和工业界认同已久的概念。面向对象程序设计语言让开发者按照现实世界里人们思考问题的模式来编写程序,它让开发者更好地用代码直接表达现实中存在的对象,这样开发代码简单并且易于维护。面向对象语言有以下三个最重要的概念:
n          封装 Encapsulation 强调隐藏对象的实现细节,对象的使用者仅仅通过定义好的接口使用对象。
n          继承 Inheritance 新的对象可以利用旧的对象的功能。
n          多态 Polymorphism 根据所使用的对象展现多种不同行为的能力。
而从程序的整体来看,面向对象编程提供给用户的最重要的概念则是源代码重用,它对于提高编写程序的效率极为重要。但是代码的共享与重用一旦应用于实践中仍然存在种种问题,如版本的升级、接口 [1] 的变化、在不同程序设计语言之间共享代码等等。对于这些困难原有的面向对象程序设计方法并没有相应的答案,这就是组件对象模型提出的背景。
n          基于组件编程
它与传统面向对象模型有着根本的区别,传统的面向对象编程模型的重点是源程序以及系统分析和设计过程,面向对象归根到底还是基于源代码的重用,不是真正意义上的面向对象。相反, COM 组件的概念则强调大的软件系统如何由不同的小组件构成整体应用程序体系结构。
将工程分解为逻辑组件是面向组件分析和设计的基础,这也是组件软件的基础。组件软件系统由可重用的二进制形式的软件组件模块组成,只需要相当小的改动就可以将这些来自不同开发商的组件模块组合在一起。特别重要的是这样的组合并不需要源代码,也不需要重新编译,组件之间是通过基于二进制的规范进行通讯的,这被称为二进制重用。组件模块是独立于编程语言的,使用组件的客户程序和组件之间除了通过标准的方法进行通讯以外,彼此不做任何限定。
组件可以划分为不同的类型,包括可视化组件如按钮或者列表框;功能组件如打印或者拼写检查。例如一个基于组件的架构可以提供将多个开发商的拼写检查组件插入到另一个开发商的字处理应用程序中的机制,这样用户可以根据自己的喜好方便地选择和替换字处理软件。
 
COM 允许你创建相互联合起来形成应用程序的独立的、可重用的组件。在 COM 中,组件间的相互作用是基于 Client/Server 模型的 ( 即客户机和服务器模型 ) 。基于这个模型, COM 组件可以分类为:
n          Client 使用由其他组件提供的功能。
n          Server 有其他组件可能使用的预先确定的功能的组件。
客户程序和组件程序是相对的,进行功能请求调用的是客户程序,而响应该请求的是组件程序。组件程序也可以作为客户程序去调用其它的组件程序,即提供服务给客户端的 COM 服务器可能是客户本身。正是这种角色的转换和相互调用关系使组件程序最终构成一个软件系统。
 
    服务器组件的类型
    COM 设计为允许组件相互通信而不管它们的地址。例如,用户接口组件将最好定位于接近客户端。另一方面,提供远程数据统计计算的组件将随同将被操作的数据可能定位在一台分离的计算机上。 COM 服务端组件可分为三类:
n          进程内组件 进程内服务器 [2] 是装入客户端的进程空间内的组件,即客户程序和组件程序在同一个进程地址空间内。在客户端和服务端组件间有大量数据转移操作的情况下是理想的。进程内服务器会更快地装载。由于它占用和客户端应用程序同样的地址空间,它可以与客户端更快的通信。进程内服务器是通过将组件作为动态连接库 (DLL) 的形式来实现的。 DLL 允许特定的一套功能以分离于可执行的、以 DLL 为扩展名的文件进行存储。只有当程序需要它们时, DLL 才将其装入内存中,客户程序将组件程序加载到自己的进程地址空间后再调用组件程序的函数。
n          本地 ( 即进程外 ) 组件 进程外组件指客户程序和组件程序分别处在不同的进程空间地址中。进程外服务器对需要运行于独立的处理空间或作为独立客户端应用程序的线程的组件是理想的。这种服务器的类型自身是 EXE ,因此具有独立的进程的资格。由于数据必须从一个地址空间移到另一个地址空间,因此这些服务器就会慢得多。由于进程外服务器是可执行的,它们运行在自己的线程内。当客户端代码正在执行时,客户端不锁住服务器。进程外服务器对需要表现为独立的应用程序的组件也是理想的。例如, Microsoft Internet Explorer 的应用程序是本地服务器的例子。客户端和服务端的通信是通过进程内的通信协议进行的,这个通信协议是 IPC。
n          远程组件 远程服务器与本地服务器类似,除了远程服务器是运行在通过网络连接的分离的计算机上。这种功能是使用 DCOM 实现的。 DCOM 的优点在于它并不要求任何特别的编程来使具有功能。另外服务端和客户端通信是通过 RPC 通信协议进行的。
 
对于这三种不同的服务器组件,客户程序和组件程序交互的内在方式是完全不同的。但是对于功能相同的进程内和进程外组件,从程序编写的角度看,客户程序是以同样的方法来使用组件程序的,客户程序不需要做任何的修改。
 
COM 的真相
COM 是一种软件体系结构,这种体系结构允许用不同的软件商提供的组件来构造应用程序和系统。它是一套二进制的和网络标准,允许任何软件相互通信而不管硬件、操作系统 OS 和用于开发的编程语言。 COM 不是一种编程语言而是一套规范,它定义了组件怎么样可以相互通信。
    每个 COM 组件被编写成满足由 COM 规定的二进制标准。这一些标准是:
n          组件要求注意它们自己 ( 即组件对象 [3]) 的创建和销毁
n          组件要求以标准方式提供它们的功能 ( 即通过接口 [4])
n          组件的位置要求对用户透明
 
   使用组件的优点在于具有将它们动态装载或卸出应用程序系统的能力。为了达到这一目的,组件必须满足两个需求:
n          组件必须动态链接 [5] 。这允许它们在运行时改变。如果应用程序是由每次组件改变时静态链接的组件组成,那么应用程序就等同于一个单一的应用程序。
n          在实现时组件必须隐藏。每个组件具有唯一识别自己的 ID 作为标识。这些 ID 出现在系统注册表 HKEY_CLASSES_ROOT 目录里。
动态链接对于组件而言是一个至关重要的要求,而实现细节隐藏则是动态链接的一个必要条件。
 
    COM 的工作方式
    到目前为止,显然 COM 并不仅仅是书面形式的规范。它也包含系统级的代码,即它自身的实现。 COM 规则出现在 COM 运行库里。
    组件对象库,即 COM 运行时间库是一个系统组件,这个系统组件提供能够在进程内、进程外或通过网络进行调用的 COM
    COM 核心,简单地讲,是组件对象和客户端使用二进制标准如何交互的规范。 COM 在操作系统内的实现为 COM 运行时间库。 COM 运行时间库包括:
n          API 函数
n          服务
n          允许应用程序在进程内控制内存分配的标准机制
n         
    组件对象库是通过 Windows3.1 中的 COMPOBJ.DLL 以及 Windows NT Windows 9X 中的 OLE32.DLL 实现的。
 
    接口
n          概述
n          组件对象高度地封装。组件对象的内部实现对用户完全隐藏,根本无法知道组件对象在使用何种数据结构和这些数据是如何被函数处理的。每个组件有一个接口,这个接口是一种且唯一的访问组件对象功能的方式。接口是由一组虚函数的声明组成。它使得预期的行为和响应清淅化。接中并不具有这些函数的实现。函数是通过组件类 (CoClass) 实现的。组件类实例化时生成组件对象。
n          接口是用称为虚函数表 (VTable) 的内存结构实现的。无论何时创建组件对象,组件对象也在内存中创建一张对应的虚函数表。虚函数表是由一系列指针组成,这些指针存储了由组件对象实现的成员函数的地址。
n          客户端创建一个接口指针,接口指针指向一个虚指针,虚指针指向虚函数表。使用接口指针和虚指针,客户端就可以访问组件对象实现的成员函数。
VTable 是一个函数指针数组的内存结构。每一个数组元素包含的是一个由组件所实现的函数地址。对于 COM 而言,接口也就是此内存结构,其他东西,均是 COM 不关心的实现细节。
n          特征
n          接口是集合在同一个名称 ( 是一个系统唯一的 ID 值,称 IID) 下的相关函数 (/ 方法 ) 的集合。这说明接口必须是全球唯一的。
n          如前所述,当组件类实例化时产生组件对象。接口是由组件对象实现的函数的集合。接口不可以被实例化,因为它没有实现。
n          组件之间的通讯是基于接口的。接口是组件和其客户之间严格类型化的契约。实现相同接口的两个对象就被认为是多态的,这里的多态不包含诸如基类指针指向派生类对象的意义,这里是指同一个接口可以由多个组件对象以不同方法实现。
n          组件对象可以实现多个接口。例如,银行交易组件 (BankTransaction) 支持两个接口, IDebit ICredit ,即借和贷;又例如, Microsoft SQL SERVER 数据库服务器组件 (SqlImplementation) 支持两个接口,一个是维护数据处理,另一个是维护数据定义。数据处理接口提供增加、更新和删除数据方法,然而,数据定义接口提供创建数据库、表和视图的方法。
n          接口没有版本化并且是不变的,如果功能必须改变以适应一个接口,则将创建一个有唯一标识的完全新接口。组件对象实现这两个接口,因而解决了版本问题。为较早版本的接口制作的客户端仍旧可以访问原来的接口。
n          注意
n          原来的旧接口是可以改变的,但是接口中的函数说明不能改变,即接口中的函数的函数名、参数形式不能改变,因为这样改变就需要使得客户端调用服务的代码进行修改和进行重新编绎。但是接口中的函数的函数体是可以修改的。这就是接口不变性的本质,也是 COM 规范。
 
    COM 标识符
        因为全球都在开发和使用组件,因此需要标识唯一地标识每个组件。
        开发软件基金会 (OSF) 研究出一种能产生唯一标识符的算法,称之为全球唯一标识符 (UUID) 。在 COM 中, UUID 被称之为全局唯一标识符 (GUID) GUID 是能分配给接口、组件类和类型库的 128 位或 16 字节数。 GUID 唯一地标识组件。
        生成 GUID 的算法根据以下几个方面:
n          当前日期和时间。
n          网络适配器卡地址。
n          时针序。
n          自动递增计数器。
        COM 使用的 128 位的接口标识符使得我们可能创建大约 340282366920900000000000000000000000000 个独立的接口,足够为将来 10782897524560000000 年每秒创建一万亿个接口。
        网卡地址是相互不同的,对没有网卡的机器,地址对使用中的机器保持唯一性。
        GUID 可能通过执行 UUIDGEN.EXE GUIDGEN.EXE 产生。 GUIDGEN 是装载 Microsoft Visual Studio 中的一个工具,它通常在 C:/Program Files/Microsoft Visual Studio/Common/Tools/GUIDGEN.EXE
        GUID 分为三类,具体见下:
n          CLSID 是唯一地标识类或组件的 GUID ,传统地, CLSID 的一般形式为 CLSID_<unique identifier> ,在本例中:
MyProj_i.c 中表示为
const CLSID CLSID_MyCom = {0xFEB7BDEF,0xFB6F,0x446B,{0xBE,0x31,0xDF,0x0A,0x3A,0xD3,0x91,0xBA}};
MyProj.idl 中表示为
         [
                   uuid(FEB7BDEF-FB6F-446B-BE31-DF0A3AD391BA),
                   helpstring("MyCom Class")
         ]
         coclass MyCom
         {
                   [default] interface IMyCom;
         };
MyCom.rgs 中表示为
HKCR
{
         MyProj.MyCom.1 = s 'MyCom Class'
         {
                   CLSID = s '{FEB7BDEF-FB6F-446B-BE31-DF0A3AD391BA}'
         }
         MyProj.MyCom = s 'MyCom Class'
         {
                   CLSID = s '{FEB7BDEF-FB6F-446B-BE31-DF0A3AD391BA}'
                   CurVer = s 'MyProj.MyCom.1'
         }
         NoRemove CLSID
         {
                   ForceRemove {FEB7BDEF-FB6F-446B-BE31-DF0A3AD391BA} = s 'MyCom Class'
                   {
                            ProgID = s 'MyProj.MyCom.1'
                            VersionIndependentProgID = s 'MyProj.MyCom'
                            ForceRemove 'Programmable'
                            InprocServer32 = s '%MODULE%'
                            {
                                     val ThreadingModel = s 'Apartment'
                            }
                            'TypeLib' = s '{FE651184-11DE-4D01-BD69-B07DDFA12D0C}'
                   }
         }
}
n          ProgID 显然,上面的 CLSID 难以记忆且难以使用。 ProgID 是分配给对象的用户友好名。 ProgID 不可能是单一的。每个 ProgID 映射到 CLSID 。命名习惯可以是 <program>.<component>.<verson> 。在本例中:
MyCom.rgs 中可以看到,有这么两句
                            ProgID = s 'MyProj.MyCom.1'
                            VersionIndependentProgID = s 'MyProj.MyCom'
             通过分别地调用函数 ProgIDFromCLSID CLSIDFromProgID 可以将 ProgID 转换为 CLSID 和将 CLSID 转换为 ProgID
             如在本示例中的客户端程序中
HRESULT hr=CoInitialize(NULL);
     CLSID clsid;
     hr=CLSIDFromProgID(OLESTR("MyProj.MyCom"),&clsid);
     if(FAILED(hr))
     {
              AfxMessageBox("COM Failed");
              return;
     }
  调用函数 CLSIDFromProgID 把作为第一个参数的 ProgID CLSID 存放到第二个参数中。
n          IID 是唯一标识接口的 GUID 。按照惯例, IID 的一般形式为 IID_<unique identifier> ,在本例中:
MyProj_i.c 中表示为
const IID IID_IMyCom = {0x65460F9C,0x3BAB,0x4055,{0x88,0x5A,0x8E,0xD5,0x9F,0x5F,0xA9,0xB0}};
MyProj.idl 中表示为
   [
            object,
            uuid(65460F9C-3BAB-4055-885A-8ED59F5FA9B0),
            dual,
            helpstring("IMyCom Interface"),
            pointer_default(unique)
   ]
   interface IMyCom : IDispatch
   {
            [id(1), helpstring("method MyF1")] HRESULT MyF1();
            [id(2), helpstring("method MyF2")] HRESULT MyF2([in] BSTR str,[out, retval] int* val);
            [id(3), helpstring("method MyF3")] HRESULT MyF3([in] BSTR str,[out, retval] BSTR* retstr);
            [id(4), helpstring("method MyF4")] HRESULT MyF4([in] int x,[out, retval] int* val);
   };
n          TypeLibID 是标识系统上的类型库。按照惯例, TypeLibID 的一般形式为 LIBID_< 组件工程名 >Lib ,在本例中:
MyProj_i.c 中表示为
const IID LIBID_MYPROJLib = {0xFE651184,0x11DE,0x4D01,{0xBD,0x69,0xB0,0x7D,0xDF,0xA1,0x2D,0x0C}};
MyProj.idl 中表示为
[
   uuid(FE651184-11DE-4D01-BD69-B07DDFA12D0C),
   version(1.0),
   helpstring("MyProj 1.0 Type Library")
]
library MYPROJLib
{
   importlib("stdole32.tlb");
   importlib("stdole2.tlb");
};
MyCom.rgs 中也可以看到这么一句
'TypeLib' = s '{FE651184-11DE-4D01-BD69-B07DDFA12D0C}'
 
COM 对象的注册角色
如果 COM 组件用文件名装载并且组件位置或名字变化了,客户应用程序将不能访问组件。文件名无法提供在一个系统上的不同的组件版本,因而造成了不同版本商之间的产品冲突。
COM 通过借助标识符 ID 注册系统注册表中的 COM 组件的位置来处理这个问题。 COM 组件的 ID 必须保证是系统唯一的,以避免与不同产品的冲突,并且客户可不必知道组件的物理位置而可以访问组件,这也就是所谓的组件对客户的透明化。
COM 类注册在 HKEY_CLASSES_ROOT/CLSID 主键下。对每个 CLSID 主键,有一个子键 InprocServer32 ,这个子键的默认值为 DLL 的文件名。
      组件可以使用 regsver32 程序注册。这个程序提供进入注册表的组件。
     DLL 文件可以使用以下命令注册:
     Regsver32 <DLL filename>
     为了注册 EXE 文件,命令语法为:
<EXE filename>/regserver
注册 COM 组件读取的注册文件是 MyCom.rgs
     在本例中,注册表的结构为 ( 红色为 CLSID 绿色为 IID 蓝色为 LIBID )
HKEY_CLASSES_ROOT/CLSID/{ FEB7BDEF-FB6F-446B-BE31-DF0A3AD391BA }
    默认 MyCom Class
/InprocServer32
默认 C:/MyProj/Debug/MyProj.dll
Apartment ThreadingModel
/ProgID
默认 MyProj.MyCom.1
/Programmable
/TypeLib
默认 { FE651184-11DE-4D01-BD69-B07DDFA12D0C }
/VersionIndependentProgID
默认 MyProj.MyCom
 
HKEY_CLASSES_ROOT/MyProj.MyCom
/CLSID
默认 { FEB7BDEF-FB6F-446B-BE31-DF0A3AD391BA }
/CurVer
默认 MyProj.MyCom.1
 
     HKEY_CLASSES_ROOT/MyProj.MyCom.1
/CLSID
默认 { FEB7BDEF-FB6F-446B-BE31-DF0A3AD391BA }
 
     HKEY_CLASSES_ROOT/Interface/{ 65460F9C-3BAB-4055-885A-8ED59F5FA9B0 }
          默认 IMyCom
/ProxyStubClsid
/ProxyStubClsid32
/TypeLib
默认 { FE651184-11DE-4D01-BD69-B07DDFA12D0C }
Version 1.0
 
     HKEY_CLASSES_ROOT/TypeLib/{ FE651184-11DE-4D01-BD69-B07DDFA12D0C }
/1.0
        默认 MyProj 1.0 Type Library
/1.0/0
    /1.0/0/win32
        默认 C:/MyProj/Debug/MyProj.dll
/1.0/FLAGS
/1.0/HELPDIR
    默认 C:/MyProj/Debug/
 
     接口类型
    完整的 COM 编程系统是基于接口的。接口可以分为以下几类:
n          标准接口 COM 系统提供的接口
n          自定义接口 由开发者创建的接口
    大多数由标准接口实现的方法被系统调用。这样的标准接口有 IUnknown IDispatch IClassFactory IOle IDateObject IStream IStorage…
    自定义接口的目的是提供更多的功能给用户。
   
    标准接口
    COM 系统提供的不同的标准接口有:
n          IUnknown IUnknown 是所有接口的基接口。甚至自定义接口也执行 IUnknown 所有的 COM 组件都必须实现这个接口。 IUnknown 用于组件对象的生命周期管理。它也可以查询被组件对象使用的其他接口。
下面的等式阐明了 C++ 对象和 COM 对象之间的关系:
C++ 对象 + IUnknown=COM 对象
IUnknown 接口用于组件对象的生命周期管理,它执行以下方法实现这个功能
n          AddRef
n          Release
组件对象可以被多个应用程序使用,每次执行应用程序,需要将组件装载进内存,在 32 Windows Windows NT ,每次应用程序请求组件时,组件的拷贝则映射到应用程序的地址空间并增加引用计数;类似的,每次应用程序和组件断开连接,则减小引用计数。操作系统维护着组件对象存在的连接计数。当计数下降为 0 ,则暗示没有应用程序访问组件并销毁组件。
组件的引用计数使用函数 AddRef Release 维护。 AddRef 增加计数;而 Release 负责减小计数。
IUnknown 还有一个称为 QueryInterface 的函数,它提供了所支持接口的列表。 QueryInterface 方法带有两个参数,第一个是一个标识所需要接口的接口标识符,第二个是 QueryInterface 存放接口地址的指针。因此, QueryInterface 用来提供接口的指针来访问组件的功能。
n          IDispatch 当客户端正常时,脚本语言或 Visual Basic 应用程序调用组件对象的函数, IDispatch 接口查询函数的地址并执行它。当组件相互调用时就使用 IDispatch
IDispatch 接口是为那些需要和客户特别是 Visual Basic 和脚本语言等不支持指针的客户相互作用的组件实现的。考虑一个实例, Visual Basic 应用程序在 Microsoft Access 数据库中存储数据并使用 Microsoft Excel 创建图表。这里, Visual Basic Microsoft Access 数据库组件及 Microsoft Excel 图表组件相互作用。这只有所有组件都实现了 IDispatch 接口才可能。
IDispatch 接口是用来暴露组件功能的标准接口。但是它由于需依次调用系统函数而增加了系统的开销。
n          IClassFactory IClassFactory 接口用于创建新的 COM 对象的实例。
到现在,你已经熟悉组件的不同类型,即进程内、本地和远程的。假设你的组件是一个进程内组件,你如何创建类的一个实例呢 ? 答案是 C++ new 操作符。但是如果组件是本地的或是远程的呢 ? 那么,显然 new 操作符不是答案。
COM 对这个问题的答案是 IClassFactory 接口。
IClassFactory 接口是特定设计用来当实现时创建另一个本地类或远程类的实例的接口。
IClassFactory 接口有 CreateInstance LockServer 两个方法。
CreateInstance 方法实际上创建 COM 对象的实例。
LockServer 方法用来保持组件在内存中以致允许更快地创建实例。
n          IOle IOleObject 是实现 OLE 对象嵌套的主要接口。 IOleLink 是使用 OLE 对象链接的主要接口。
n          IDataObject 用于实现拖放式功能的接口。
n          IStream IStorage IStream IStorage 接口用于创建和管理存储对象。这些接口典型用于数据来自多个源的 OLE 应用程序。
IDataObject IStream IStorage 三个接个均使用统一数据传输协议 (UDT) ,它描述了通过不同类型的应用程序的交换标准。
 
小结
为什么需要组件对象模型 COM?
COM 组件是执行特定操作的程序或二进制对象。 COM 为开发组件定义了标准、组件交互、协议标准以及组件的物理结构来确保组件的互操作性。 COM 规范在 COM 运行时间库中体现,它提供了在进程中、进程外及跨网络的调用组件的能力。
COM 的工作原理是什么 ?
COM 允许你创建独立的、可重用的、共同联合形成应用程序的组件。在 COM 中,组件之间的互操作是基于客户 / 服务器模型的。组件对象是高度封装的。组件对象的内部实现对用户是完全隐藏的。每个组件都有接口,接口是唯一的可以访问组件对象功能的方法。
接口的特征是什么 ?
接口没有任何实现,所以所有接口不能被实例化。接口是非版本化的并且不可改变。
什么是 UUID/GUID?
当开发了组件并全局使用它时,它使用所谓的通用唯一标识行 UUID 来唯一标识组件。在 COM 中, UUID 是指全局唯一标识符 GUID GUID 分为 CLSID IID LIBID 三类。
对组件的十一个文件的剖析
一、 stdafx.h
代码选取

#define STRICT
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif
#define _ATL_APARTMENT_THREADED
#include <afxwin.h>
#include <afxdisp.h>
#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
extern CComModule _Module;
#include <atlcom.h>

代码剖析
n          包含了 ATL 头文件
#include <atlbase.h>
#include <atlcom.h>
n          两个声明
#define _ATL_APARTMENT_THREADED
定义了将成为 apartment 模型的 DLL 默认线程模型。
_Module CComModule 类型的对象, CComModule 实现了 COM 服务器的基本功能,实现并提供了诸如注册、实例化所支持对象以及通过对象映射管理生命周期等服务。注意因为它通过 ATL 头文件所引用,所以 CComModule 对象必须是全局的且必须命名为 _Module
另外,这个类也是 ATL 的中心,正如主函数对任何程序的入口点 (C/C++) ,这个类的模块对象处理几个任务,如:服务器锁计数、注册本地服务器的类工厂、根据注册表注册和反注册。
 
二、 stdafx.cpp

#ifdef _ATL_STATIC_REGISTRY
#include <statreg.h>
#include <statreg.cpp>
#endif
#include <atlimpl.cpp>

 
三、 MyProj.def 输出定义文件
     代码选取

; MyProj.def : Declares the module parameters.
LIBRARY      "MyProj.DLL"
EXPORTS
         DllCanUnloadNow     @1 PRIVATE
         DllGetClassObject   @2 PRIVATE
         DllRegisterServer   @3 PRIVATE
         DllUnregisterServer @4 PRIVATE

代码剖析
也称模块定义文件,只有进程内服务器 DLL 才产生,它提供了被链接程序的信息 (DLL 文件的名字 )
 
四、 MyProj.tlb 类型库
编绎后生成,客户端使用时用 #import 方式导入之。
 
五、 MyProj.idl 接口定义语言文件
    代码选取

import "oaidl.idl"; // 此文件包括 IDispatch 接口的定义, import 相当于 #include
import "ocidl.idl";
         [
                   object,
                   uuid(65460F9C-3BAB-4055-885A-8ED59F5FA9B0),
                   dual,
                   helpstring("IMyCom Interface"),
                   pointer_default(unique)
         ]
         interface IMyCom : IDispatch
         {
                   [id(1), helpstring("method MyF1")] HRESULT MyF1();
                   [id(2), helpstring("method MyF2")] HRESULT MyF2([in] BSTR str,[out, retval] int* val);
                   [id(3), helpstring("method MyF3")] HRESULT MyF3([in] BSTR str,[out, retval] BSTR* retstr);
                   [id(4), helpstring("method MyF4")] HRESULT MyF4([in] int x,[out, retval] int* val);
         };
 
[
         uuid(FE651184-11DE-4D01-BD69-B07DDFA12D0C),
         version(1.0),
         helpstring("MyProj 1.0 Type Library")
]
library MYPROJLib
{
         importlib("stdole32.tlb");
         importlib("stdole2.tlb");
 
         [
                   uuid(FEB7BDEF-FB6F-446B-BE31-DF0A3AD391BA),
                   helpstring("MyCom Class")
         ]
         coclass MyCom
         {
                   [default] interface IMyCom;
         };
};

代码剖析
IDL 文件由三部分组成,每部分又都由一对中括号和一对大括号组成。每部分也都包含一个 UUID 来唯一的标识自己
n          接口部分: COM 接口定义
[
                   object,
                   uuid(65460F9C-3BAB-4055-885A-8ED59F5FA9B0), // 接口唯一标识符 (GUID/IID)
                   dual, // 表示客户机可用两种方法访问这个接口,一种是支持指针的非脚本语言,如 VC ;一种是非脚本语言,如 VB ASP
                   helpstring("IMyCom Interface"), // 将提示字符串与此接口建立联系
                   pointer_default(unique) // 指定除参数表中所列属性之外所有指针的缺省特征 ,unique 表示指针可以是 NULL ,但不支持别名
         ]
         interface IMyCom : IDispatch
         {
                   [id(1), helpstring("method MyF1")] HRESULT MyF1();
                   [id(2), helpstring("method MyF2")] HRESULT MyF2([in] BSTR str,[out, retval] int* val);
                   [id(3), helpstring("method MyF3")] HRESULT MyF3([in] BSTR str,[out, retval] BSTR* retstr);
                   [id(4), helpstring("method MyF4")] HRESULT MyF4([in] int x,[out, retval] int* val);
         };// 接口中的方法
n          类型库部分
[
         uuid(FE651184-11DE-4D01-BD69-B07DDFA12D0C),
         version(1.0),
         helpstring("MyProj 1.0 Type Library")
]
library MYPROJLib
{
         importlib("stdole32.tlb");
         importlib("stdole2.tlb");
                   < 组件类部分 >
};
n          组件类部分 ( 组件类嵌套在类型库里面 )
     [
              uuid(FEB7BDEF-FB6F-446B-BE31-DF0A3AD391BA),
              helpstring("MyCom Class")
     ]
     coclass MyCom
     {
              [default] interface IMyCom;
     };
手工为接口添加一个方法,该修改哪些地方 ?
        假设组件类名叫 CMyCom, 接口叫 IMyCom ,要加入的方法是 MyF5
首先在 IDL 文件中找到接口 IMyCom 的定义,在其中加入如下方法定义:
       [id(5), helpstring("method MyF5")] HRESULT MyF1( [out,retval] VARIANT_BOOL *ret );
注意: id 中的数字不要和已经存在的 id 重复。
其次,在 CMyCom 的类定义头文件中加入如下成员函数声明:
public:
STDMETHOD(MyF5)(/*[out, retval]*/ VARIANT_BOOL *ret );
最后,在 CMyCom 类的实现 Cpp 文件中加入函数的实现:
STDMETHODIMP CMyCom::MyF5( VARIANT_BOOL *ret )
{
         AFX_MANAGE_STATE(AfxGetStaticModuleState())
         // TODO: Add your implementation code here
     *ret = VARAINT_TRUE;
         return S_OK;
}









六、 MyCom.h
     代码选取

// MyCom.h : Declaration of the CMyCom
#ifndef __MYCOM_H_
#define __MYCOM_H_
#include "resource.h"       // main symbols
// CMyCom
class ATL_NO_VTABLE CMyCom :
         public CComObjectRootEx<CComSingleThreadModel>,
         public CComCoClass<CMyCom, &CLSID_MyCom>,
         public IDispatchImpl<IMyCom, &IID_IMyCom, &LIBID_MYPROJLib>
{
public:
         CMyCom(){}
 
DECLARE_REGISTRY_RESOURCEID(IDR_MYCOM)
DECLARE_PROTECT_FINAL_CONSTRUCT()
 
BEGIN_COM_MAP(CMyCom)
         COM_INTERFACE_ENTRY(IMyCom)
         COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
 
// IMyCom
public:
         STDMETHOD(MyF4)(/*[in]*/ int x,/*[out, retval]*/ int* val);
         STDMETHOD(MyF3)(/*[in]*/ BSTR str,/*[out, retval]*/ BSTR* retstr);
         STDMETHOD(MyF2)(/*[in]*/ BSTR str,/*[out, retval]*/ int* val);
         STDMETHOD(MyF1)();
};
 
#endif //__MYCOM_H_

代码剖析
n          CComObjectRootEx
是每个 COM 组件必须继承的类之一,这个类管理组件的引用计数。由于引用计数是很重要的,所有的 ATL COM 组件必须从 CComObjectRootEx 继承。
n          CComCoClass
如果一个类从 CComCoClass 继承, ATL 确保了这个类一定是默认的类工厂对象。 ATL 支持一个默认的工厂实现,并且有使得可以恢复对象 CLSID 和设置错误信息的函数。
n          BEGIN_COM_MAP(CMyCom)
         COM_INTERFACE_ENTRY(IMyCom)
         COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
BEGIN_COM_MAP END_COM_MAP 这两个宏定义了 COM 的接口映射。列在映射中的接口是 QueryInterface 可以返回的接口指针的接口。
n          另外 CComObject 类实现了属于 IUnKnown 接口的方法。这个类一直是最大的派生类,它提供了聚合和锁模型的选择。但是,对 QueryInterface AddRef Release 的调用会委派给 CComObjectRootEx
n          还有,我们在这里看到了和接口定义语言文件模样差不多的四个函数的声明
         STDMETHOD(MyF4)(/*[in]*/ int x,/*[out, retval]*/ int* val);
         STDMETHOD(MyF3)(/*[in]*/ BSTR str,/*[out, retval]*/ BSTR* retstr);
         STDMETHOD(MyF2)(/*[in]*/ BSTR str,/*[out, retval]*/ int* val);
         STDMETHOD(MyF1)();
         那么它们两者间有什么意义区别呢 ?
         在接口定义语言文件里有如下接口函数 ( 方法 )
         interface IMyCom : IDispatch
         {
                   [id(1), helpstring("method MyF1")] HRESULT MyF1();
                   [id(2), helpstring("method MyF2")] HRESULT MyF2([in] BSTR str,[out, retval] int* val);
                   [id(3), helpstring("method MyF3")] HRESULT MyF3([in] BSTR str,[out, retval] BSTR* retstr);
                   [id(4), helpstring("method MyF4")] HRESULT MyF4([in] int x,[out, retval] int* val);
         };
         这都是对接口而言的,接口只是包含了这些函数的集合,它并没有实现这些函数。函数是通过组件类 (CMyCom class) 实现的。组件类实例化时生成组件对象。
 
七、 MyCom.cpp
     代码选取

// MyCom.cpp : Implementation of CMyCom
#include "stdafx.h"
#include "MyProj.h"
#include "MyCom.h"
// CMyCom
STDMETHODIMP CMyCom::MyF1()
{
         AFX_MANAGE_STATE(AfxGetStaticModuleState())
         // TODO: Add your implementation code here
         AfxMessageBox(" 欢迎使用我的组件 ");
         return S_OK;
}

代码剖析
组件类对接口函数的具体实现。
 
八、 MyProj.h
    代码选取
   
    代码剖析
    虚函数表等内部定义
 
九、 MyProj.cpp
    代码选取

// MyProj.cpp : Implementation of DLL Exports.
// Note: Proxy/Stub Information
//      To build a separate proxy/stub DLL,
//      run nmake -f MyProjps.mk in the project directory.
#include "stdafx.h"
#include "resource.h"
#include <initguid.h>
#include "MyProj.h"
#include "MyProj_i.c"
#include "MyCom.h"
CComModule _Module; //
BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_MyCom, CMyCom) // 对象 MAP ,对每个服务器实现有组件都有一个入口
END_OBJECT_MAP()
class CMyProjApp : public CWinApp
{
public:
// Overrides
         // ClassWizard generated virtual function overrides
         //{{AFX_VIRTUAL(CMyProjApp)
         public:
    virtual BOOL InitInstance();
    virtual int ExitInstance();
         //}}AFX_VIRTUAL
         //{{AFX_MSG(CMyProjApp)
                   // NOTE - the ClassWizard will add and remove member functions here.
                   //    DO NOT EDIT what you see in these blocks of generated code !
         //}}AFX_MSG
         DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CMyProjApp, CWinApp)
         //{{AFX_MSG_MAP(CMyProjApp)
                   // NOTE - the ClassWizard will add and remove mapping macros here.
                   //    DO NOT EDIT what you see in these blocks of generated code!
         //}}AFX_MSG_MAP
END_MESSAGE_MAP()
CMyProjApp theApp;
BOOL CMyProjApp::InitInstance()
{
    _Module.Init(ObjectMap, m_hInstance, &LIBID_MYPROJLib);
    return CWinApp::InitInstance();
}
int CMyProjApp::ExitInstance()
{
    _Module.Term();
    return CWinApp::ExitInstance();
}
// Used to determine whether the DLL can be unloaded by OLE
STDAPI DllCanUnloadNow(void)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    return (AfxDllCanUnloadNow()==S_OK && _Module.GetLockCount()==0) ? S_OK : S_FALSE;
}
// Returns a class factory to create an object of the requested type
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
    return _Module.GetClassObject(rclsid, riid, ppv);
}
// DllRegisterServer - Adds entries to the system registry
STDAPI DllRegisterServer(void)
{
    // registers object, typelib and all interfaces in typelib
    return _Module.RegisterServer(TRUE);
}
// DllUnregisterServer - Removes entries from the system registry
STDAPI DllUnregisterServer(void)
{
    return _Module.UnregisterServer(TRUE);
}







十、 MyProj_i.c
    代码选取

/* this file contains the actual definitions of */
/* the IIDs and CLSIDs */
/* link this file in with the server and any clients */
/* File created by MIDL compiler version 5.01.0164 */
/* at Sun Nov 30 23:39:23 2003
 */
/* Compiler settings for C:/MyProj/MyProj.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: allocation ref bounds_check enum stub_data
*/
//@@MIDL_FILE_HEADING( )
#ifdef __cplusplus
extern "C"{
#endif
#ifndef __IID_DEFINED__
#define __IID_DEFINED__
typedef struct _IID
{
    unsigned long x;
    unsigned short s1;
    unsigned short s2;
    unsigned char c[8];
} IID;
#endif // __IID_DEFINED__
#ifndef CLSID_DEFINED
#define CLSID_DEFINED
typedef IID CLSID;
#endif // CLSID_DEFINED
const IID IID_IMyCom = {0x65460F9C,0x3BAB,0x4055,{0x88,0x5A,0x8E,0xD5,0x9F,0x5F,0xA9,0xB0}};
const IID LIBID_MYPROJLib = {0xFE651184,0x11DE,0x4D01,{0xBD,0x69,0xB0,0x7D,0xDF,0xA1,0x2D,0x0C}};
const CLSID CLSID_MyCom = {0xFEB7BDEF,0xFB6F,0x446B,{0xBE,0x31,0xDF,0x0A,0x3A,0xD3,0x91,0xBA}};
#ifdef __cplusplus
}
#endif

 
十一、 MyCom.rgs
    代码选取

HKCR
{
         MyProj.MyCom.1 = s 'MyCom Class'
         {
                   CLSID = s '{ FEB7BDEF-FB6F-446B-BE31-DF0A3AD391BA }'
         }
         MyProj.MyCom = s 'MyCom Class'
         {
                   CLSID = s '{ FEB7BDEF-FB6F-446B-BE31-DF0A3AD391BA }'
                   CurVer = s 'MyProj.MyCom.1'
         }
         NoRemove CLSID
         {
                   ForceRemove { FEB7BDEF-FB6F-446B-BE31-DF0A3AD391BA } = s 'MyCom Class'
                   {
                            ProgID = s 'MyProj.MyCom.1'
                            VersionIndependentProgID = s 'MyProj.MyCom'
                            ForceRemove 'Programmable'
                            InprocServer32 = s '%MODULE%'
                            {
                                     val ThreadingModel = s 'Apartment'
                            }
                            'TypeLib' = s '{ FE651184-11DE-4D01-BD69-B07DDFA12D0C }'
                   }
         }
}

代码剖析
组件注册时用,在这里你可以更改组件的在注册时存放在注册表中的名称及组件的其他相关注册信息。
 
Developing COM Components using VC-ATL(2)  





 
Visual Basic Visual C++ 组件调用相互通
 
n          Visual Basic 调用 Visual Basic 组件
n          Visual C++ 调用 Visual Basic 组件
n          Visual Basic 调用 Visual C++ 组件
n          Visual C++ 调用 Visual C++ 组件
 
n          编写 Visual Basic 组件
n          组件功能:完成三种类型的信用卡号码的合法性检查
n          编写步骤:
1.         新建一工程,工程类型选择 ActiveX DLL ,把工程名由默认的 Project1 改为 ValidateCardServer ,并把缺省的类模块 Class1 改为 IValidateCard ,最后在此类里敲入如下代码。
 

Option Explicit
 
Public Function fnValidateCreditCard(ByVal strCCNumber As String, ByVal strCCType As String) As Boolean
    Dim bValid As Boolean
    strCCType = Trim(strCCType)
    Select Case UCase(strCCType)
        Case "VISA"
            strCCType = "V"
        Case "MASTER"
            strCCType = "M"
        Case "American"
            strCCType = "A"
    End Select
    If fnPrefixCheck(strCCNumber, strCCType) And fnLengthCheck(strCCNumber, strCCType) And fnLuhnCheck(strCCNumber, strCCType) Then
        bValid = True
    Else
        bValid = False
    End If
    fnValidateCreditCard = bValid
End Function
 
Private Function fnPrefixCheck(strCCNumber As String, strCCType As String) As Boolean
    Dim bValidPrefix As Boolean
    Select Case UCase(strCCType)
        Case "V"
            If InStr(1, strCCNumber, "4") = 1 Then
                bValidPrefix = True
            End If
        Case "M"
            If InStr(1, strCCNumber, "51") = 1 Or _
               InStr(1, strCCNumber, "52") = 1 Or _
               InStr(1, strCCNumber, "53") = 1 Or _
               InStr(1, strCCNumber, "54") = 1 Or _
               InStr(1, strCCNumber, "55") = 1 Then
               bValidPrefix = True
            End If
        Case "A"
            If InStr(1, strCCNumber, "34") = 1 Or _
               InStr(1, strCCNumber, "37") Then
               bValidPrefix = True
            End If
    End Select
    fnPrefixCheck = bValidPrefix
End Function
 
Private Function fnLengthCheck(strCCNumber As String, strCCType As String) As Boolean
    Dim bValidLength As Boolean
    Select Case UCase(strCCType)
        Case "V"    'A visa card has a 13 digit or a 16 digit number
            If Len(strCCNumber) = 13 Or Len(strCCNumber) = 16 Then
                bValidLength = True
            End If
        Case "M"    'A mastercard has a 16 digit number
            If Len(strCCNumber) = 16 Then
                bValidLength = True
            End If
        Case "A"    'American Express has a 15 digit number
            If Len(strCCNumber) = 15 Then
                bValidLength = True
            End If
    End Select
    fnLengthCheck = bValidLength
End Function
 
Private Function fnLuhnCheck(strCCNumber As String, strCCType As String) As Boolean
    Dim bValidLuhn As Boolean
   
    Dim strRev As String
    Dim strCh As String
    Dim intNumber As Integer
    Dim strNumberFinal As String
    Dim intSum As Integer
    Dim intTemp As Integer
    strRev = StrReverse(strCCNumber)
   
    For intTemp = 1 To Len(strRev)
        strCh = Mid(strRev, intTemp, 1)
        intNumber = CInt(strCh)
        If intTemp Mod 2 = 0 Then
            intNumber = intNumber * 2
            If intNumber > 9 Then
                intNumber = intNumber - 9
            End If
        End If
        strNumberFinal = strNumberFinal & intNumber
    Next intTemp
   
    For intTemp = 1 To Len(strNumberFinal)
        intSum = intSum + Mid(strNumberFinal, intTemp, 1)
    Next intTemp
   
    If intSum Mod 10 = 0 Then
        bValidLuhn = True
    Else
        bValidLuhn = False
    End If
    fnLuhnCheck = bValidLuhn
End Function

2.         编绎组件。从 File 菜单中选择 Make ValidateCardServer. dll…
n          Visual Basic 调用 Visual Basic 组件
n          编写步骤:
1.         新建一个标准工程,工程名设置为 VBTestVBComProj ;引入组件,选择菜单 Project->References… ,点击浏览,选择 C:/VBCom/ ValidateCardServer.dll ;敲入如下窗体代码。

 

Private Sub Command1_Click()
Dim comobj As New ValidateCardServer.IValidateCard
If comobj.fnValidateCreditCard(Text1.Text, Text2.Text) = True Then
    MsgBox "valid card"
Else
    MsgBox "invalid card"
End If
End Sub

2.         编绎运行测试程序。
n          代码测试:在信用卡号码中输入 4567890123456783 ,在信用卡类型中输入 visa ,点击确定,弹出 "valid card" 的提示框;在信用卡号码中输入 4567890123456789 ,在信用卡类型中输入 visa ,点击确定按钮,弹出 "invalid card" 的提示框。
 





Visual C++ 调用 Visual Basic 组件
编写步骤:
n          使用 Visual C++ 创建一个基于对话框的应用程序,工程名为 VCTestVBComProj ,同上面 VBTestVBComProj 工程一样,在对话框上面放置两个文本框和一个确定按钮。
n          在客户端中导入服务器的类型库。导入服务器的类型库将允许你在客户中使用服务器方法和属性。
现在,为了访问服务器的功能,它的类型库应被导入客户的工作空间。这相当于在 Visual Basic 环境中从访问 COM 组件的工程中设置组件引用。在 MFC C++ 中,这使用 #import 语句完成。
#import 关键字在带有扩展名 tlh( 类型库头文件 ) 的文件中创建了包装类的头,以及在另一个带有扩展名 tli( 类型库实现文件 ) 的文件中实现了它。这些文件在客户工作空间输出目录中显示,通常是在当前工作空间的 Debug 目录下。
包装类是 #import 语句创建的类,它封装了服务器信息。它用来访问客户定义在服务器中的属性和方法。它显示在 tlh 文件中在前面用关键字 namespace 的一块区域。包装类的名字是服务器空间名字插入关键字 Lib 。如果你的服务器组件名字是 Server ,那它的名字是 SERVERLib 。注意:这里讲的是针对 Visual C++ 创建的组件,而不是 Visual Basic 创建的组件。
2-1 StdAfx.h 文件中加入代码 :

 

// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
//      are changed infrequently
//
 
#if !defined(AFX_STDAFX_H__A07C8507_082B_4B60_9880_AEB5ADB6D68A__INCLUDED_)
#define AFX_STDAFX_H__A07C8507_082B_4B60_9880_AEB5ADB6D68A__INCLUDED_
 
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
 
#define VC_EXTRALEAN           // Exclude rarely-used stuff from Windows headers
 
#include <afxwin.h>         // MFC core and standard components
#include <afxext.h>         // MFC extensions
#include <afxdisp.h>        // MFC Automation classes
#include <afxdtctl.h>                   // MFC support for Internet Explorer 4 Common Controls
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h>                            // MFC support for Windows Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT
 
#import "../VBCom/ValidateCardServer.dll"// 如果你的服务器组件是用 Visual C++ 创建,那么你应该导入有 .tlb 扩展名的类型库文件来代替 .dll 文件。具体示例见 Developing COM Components using VC-ATL(1)
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
 
#endif // !defined(AFX_STDAFX_H__A07C8507_082B_4B60_9880_AEB5ADB6D68A__INCLUDED_)

2-2 编译 StdAfx.cpp ,当编绎 #import 语句结构时编绎器产生包装类,这个包装类将封装服务器组件的功能以致它能被客户访问。这个信息将在客户工程空间中输出目录中创建的两个文件:类型库头文件 (tlh) 和类型库实现文件 (tli) 。包装类的名称将是默认的服务器名。所以,在本例中,它的名字将是 ValidateCardServer
如果服务器组件是用 Visual C++ 创建,那么包装类的名字将是大写的服务器名后面跟上 Lib 。如果 Visual C++ 创建的服务器组件有一个名字 ValidateCardServer ,那么包装类的名字将是 VALIDATECARDSERVERLib
2-3 现在你必须从对话框访问服务器组件的功能。为此,在 VCTestVBComProjDlg.cpp 文件中增加如下的语句:

// VCTestVBComProjDlg.cpp : implementation file
//
 
#include "stdafx.h"
#include "VCTestVBComProj.h"
#include "VCTestVBComProjDlg.h"
 
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
 
using namespace ValidateCardServer;
/
// CAboutDlg dialog used for App About
 
class CAboutDlg : public CDialog
{
public:
         CAboutDlg();

n          初始化 COM 库。应用程序必须在调用 COM 函数前初始化 COM 库。
因为客户和组件间的交互是通过 COM 库,所以必须初始化 COM 库。为了初始化 COM 库,可调用 CoInitialize 函数。 CoInitialize 函数的返回值是 HRESULT 数据类型,它暗示了该操作是成功还是失败。返回值可以是 S_OK S_FALSE
语法:
HRESULT CoInitialize(NULL);
n          获得服务器的 CLSID 。客户应用程序只有在获得 COM 服务器组件的 CLSID 后才能实例化服务器。
为了创建服务器的实例,必须获得服务器的 CLSID 。因为 COM CLSID 很难记住,所以你必须使用可读的 ProgID 获得 CLSID 。这通过调用 CLSIDFromProgID 函数完成。这个函数带有两个参数:输入参数 ProgID 和输出参数 CLSID
语法:
HRESULT  CLSIDFromProgID(LPCOLESTR lpszProgID,LPCLSID pclsid);
n          创建 COM 服务器组件的实例。创建 COM 服务器组件的实例并返回所需接口的指针。
一旦获得组件的 CLSID ,它可以用来创建 COM 服务器的实例。使用 CoCreateInstance 函数来创建服务器组件的实例。 COM 将不提供实例化对象的指针, COM 只提供接口的指针。
语法:
HRESULT  CoCreateInstance(
REFCLSID rclsid,       // 要创建组件的 CLSID
LPUNKNOWN punkouter,// 当对象支持聚合时使用,否则为 NULL
DWORD dwclsContext, // 对象初化化环境 ( 进程内、进程外或远程 )
REFIID riid,            // 要获得类的接口的 ID
LPVOID *ppv           //[out] 所需求接口的指针
);
1.         CoCreateInstance 函数的第一个参数是实例化组件的 CLSID ,它通过 CLSIDFromProgID 函数获得。为了寻找所需要的组件, CoCreateInstance 函数使用它在注册表中的 HKEY_CLASSES_ROOT/CLSID 部分寻找匹配。如果未发现匹配,则 CoCreateInstance 失败。
2.         CoCreateInstance 函数的第二个参数是用来指明你是否创建对象作为聚合对象的一部分。既然该程序不使用聚合,故使用 NULL
3.         CoCreateInstance 函数的第三个参数是用来指明组件运行的环境。在 COM 中,你可以创建三种类型的组件:进程内、进程外和远程。下面的表显示了不同的类环境变量。
Class 环境
说明
CLSCTX_INPROC_SERVER
指明进程内服务器。
CLSCTX_INPROC_HANDLER
指明进程内服务器的特定类型,在客户进程中运行但类的实例被远程访问。
CLSCTX_LOCAL_SERVER
指明了本地 ( 进程外 ) 服务器。
CLSCTX_REMOTE_SERVER
指明了远程服务器,使用 CoCreateInstanceEx 函数。
CLSCTX_SERVER
指明如果不是特定的组件类型。这个值找回进程内、进程外、远程中的第一个变量。
4.         CoCreateInstance 函数的第四个参数指明所需要接口的接口标识符 IID 。接口 ID __uuidof 函数返回。接口指针在第五个参数中返回。
5.         输出参数到指定接口指针。
n          使用 COM 对象。在获得接口指针之后,客户应用程序能调用 COM 服务器对象的方法和属性。
         CoCreateInstance 函数返回一个组件的接口指针。使用这个指针,可以访问服务器的属性和方法。
n          终止 COM 库。在释放 COM 对象后,客户应用程序应指示不再需要已初始化的 COM 库。
在释放 COM 对象的实例后,初始化的 COM 库不再需要。为了释放初始化的 COM 库,可调用函数 CoUninitialize
语法:
void CoUninitialize();
n          附完整代码

         void CVCTestVBComProjDlg::OnButton1()
{
         // TODO: Add your control notification handler code here
         HRESULT hr=CoInitialize(NULL);//step 3
 
         CLSID clsid;
         hr=CLSIDFromProgID(OLESTR("ValidateCardServer.IValidateCard"),&clsid);//step 4
 
         _IValidateCard * cardptr;
         hr=CoCreateInstance(clsid,NULL,CLSCTX_INPROC_SERVER,__uuidof(_IValidateCard),(LPVOID *)&cardptr);
         if(FAILED(hr))
         {
                   AfxMessageBox("Server Creation Failed");
                   return;
         }//step 5
 
         char cCardNum[20];
         GetDlgItemText(IDC_EDIT1,cCardNum,20);
         char cCardType[10];
         GetDlgItemText(IDC_EDIT2,cCardType,10);
         _bstr_t bstCardNum(cCardNum),bstCardType(cCardType);
         long lStatus;
         lStatus=cardptr->fnValidateCreditCard(bstCardNum,bstCardType);
         if(lStatus==-1)
                   AfxMessageBox("valid card");
         else
                   AfxMessageBox("invalid card");//step 6         
 
         CoUninitialize();//step 7
}







编写Visual C++组件
4.         编写实现函数功能的代码
 
     ValidateCard.cpp清单

        

     // ValidateCard.cpp : Implementation of CValidateCard
#include "stdafx.h"
#include "ValidateCreditCardServer.h"
#include "ValidateCard.h"
 
/
// CValidateCard
 
 
STDMETHODIMP CValidateCard::ValidateCreditCard(BSTR bstrCCNo, BSTR bstrCCType, long *val)
{
         AFX_MANAGE_STATE(AfxGetStaticModuleState())
 
         // TODO: Add your implementation code here
    CString strType(bstrCCType);
         strType.MakeUpper();
         if(strType.Compare("VISA")==0)
                   bstrCCType=SysAllocString(OLESTR("V"));
         else if(strType.Compare("MASTER")==0)
                   bstrCCType=SysAllocString(OLESTR("M"));
         else if(strType.Compare("AMERICAN")==0)
                   bstrCCType=SysAllocString(OLESTR("A"));
         else
         {
                   *val=0;
                   return S_FALSE;
         }
 
         if((PrefixCheck(bstrCCNo,bstrCCType)==S_OK) && (LengthCheck(bstrCCNo,bstrCCType)==S_OK) && (LuhnCheck(bstrCCNo,bstrCCType)==S_OK))
         {
                   *val=1;
                   return S_OK;
         }
         else
         {
                   *val=0;
                   return S_FALSE;
         }
         return S_FALSE;
}
 
STDMETHODIMP CValidateCard::LuhnCheck(BSTR bstrCCNo, BSTR bstrCCType)
{
         AFX_MANAGE_STATE(AfxGetStaticModuleState())
 
         // TODO: Add your implementation code here
         CString strobj(bstrCCNo);
         CString strReverse,strFinal;
         int iNum,iSum;
         char cTemp;
 
         strReverse=strobj;
         strReverse.MakeReverse();
 
         for(int iTemp=0;iTemp<strReverse.GetLength();iTemp++)
         {
                   cTemp=strReverse.GetAt(iTemp);
                   iNum=atoi(&cTemp);
                   if(iTemp % 2 !=0)
                   {
                            iNum=iNum*2;
                            if(iNum>9)
                                     iNum-=9;
                   }
                   cTemp=iNum+'0';
                   strFinal+=cTemp;
         }
 
         for(iTemp=0;iTemp<strFinal.GetLength();iTemp++)
         {
                   cTemp=strFinal.GetAt(iTemp);
                   iSum+=atoi(&cTemp);
         }
         if(iSum % 10 == 0)
                   return S_OK;
         else
                   return S_FALSE;
}
 
STDMETHODIMP CValidateCard::LengthCheck(BSTR bstrCCNo, BSTR bstrCCType)
{
         AFX_MANAGE_STATE(AfxGetStaticModuleState())
 
         // TODO: Add your implementation code here
         CString strType(bstrCCType),strLength(bstrCCNo);
         int ilen=strLength.GetLength();
         if(strType=="V")
         {
                   if(ilen==16)return S_OK;
         }
         else if(strType=="M")
         {
                   if(ilen==16)return S_OK;
         }
         else if(strType=="A")
         {
                   if(ilen==15)return S_OK;
         }
         return S_FALSE;
}
 
STDMETHODIMP CValidateCard::PrefixCheck(BSTR bstrCCNo, BSTR bstrCCType)
{
         AFX_MANAGE_STATE(AfxGetStaticModuleState())
 
         // TODO: Add your implementation code here
         CString strNumber(bstrCCNo),strType(bstrCCType);
         if(strType=="V")
         {
                   if(strNumber.Find("4",0)>=0)
                            return S_OK;
         }
         else if(strType=="M")
         {
                   if(strNumber.Find("51",0)>=0 || strNumber.Find("52",0)>=0 || strNumber.Find("53",0)>=0 || strNumber.Find("54",0)>=0)
                            return S_OK;
         }
         else if(strType=="A")
         {
                   if(strNumber.Find("34",0)>=0 || strNumber.Find("37",0)>=0)
                            return S_OK;
         }
         return S_FALSE;
}

5.         编绎组件
编绎会读取 ValidateCard.rgs 文件进行组件的自动注册,若在 Windows 9X 环境下,请运行 regsvr32 程序进行注册,具体参见
Developing COM Components using VC-ATL(2-3)  COM 对象的注册角色
同时编绎完成后,会
Debug 目录下生成 ValidateCreditCardServer.dll
Debug 上一层目录下,即工程名工作空间目录下生成 ValidateCreditCardServer.tlb
这两个文件都可以导入到客户端,测试时分别会介绍这两种方法。



n          Visual Basic 调用 Visual C++ 组件
n          编写步骤:
1.         新建一个标准工程,工程名设置为 VBTestVCComProj ;引入组件,选择菜单 Project->References… ,点击浏览,选择 C:/ValidateCreditCardServer/Debug/ ValidateCreditCardServer.dll ;敲入如下窗体代码 ( 别忘了在窗体上放两个文本框和一个按钮哦 )
 
Private Sub Command1_Click()
Dim comobj As New VALIDATECREDITCARDSERVERLib.ValidateCard
If comobj.ValidateCreditCard(Text1.Text, Text2.Text) = 1 Then
    MsgBox "valid card"
Else
    MsgBox "invalid card"
End If
End Sub
 
2.         编绎运行测试程序。
n          代码测试:同上 ( )
 
n          Visual C++ 调用 Visual C++ 组件
在这里我不再详细介绍步骤了,相信通过前面两个例子介绍,你应该掌握了其具体的操作要点。在这里我只扼要地提一下纲领。
1.         使用 Visual C++ 创建一个基于对话框的应用程序,工程名为 VCTestVCComProj ,同上面几个测试工程一样,在对话框上面放置两个文本框和一个确定按钮。
2.         在客户端中导入服务器的类型库
2-1 StdAfx.h 文件中加入代码
#import "../ValidateCreditCardServer/ValidateCreditCardServer.tlb"
#import "../ValidateCreditCardServer/Debug/ValidateCreditCardServer.dll"
这两种方法都可以 !
2-2 编译 StdAfx.cpp 产生包装类,即在客户工程空间中输出目录 (C:/VCTestVCComProj/Debug) 中创建的两个文件:类型库头文件 (ValidateCreditCardServer.tlh) 和类型库实现文件 (ValidateCreditCardServer.tli)
 
2-3 对话框访问服务器组件的功能需要使用服务器包装类名字空间。为此,在 VCTestVCComProjDlg.cpp 文件头部中增加如下的语句:
using namespace VALIDATECREDITCARDSERVERLib;
        
3.         初始化 COM
4.         获得服务器的 CLSID
5.         创建 COM 服务器组件的实例
6.         使用 COM 对象
7.         终止 COM
 
VCTestVCComProjDlg.cpp 清单

 

// VCTestVCComProjDlg.cpp : implementation file
//
 
#include "stdafx.h"
#include "VCTestVCComProj.h"
#include "VCTestVCComProjDlg.h"
 
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
 
using namespace VALIDATECREDITCARDSERVERLib;
 
BEGIN_MESSAGE_MAP(CVCTestVCComProjDlg, CDialog)
         //{{AFX_MSG_MAP(CVCTestVCComProjDlg)
         ON_WM_SYSCOMMAND()
         ON_WM_PAINT()
         ON_WM_QUERYDRAGICON()
         ON_BN_CLICKED(IDC_BUTTON1, OnButton1)
         //}}AFX_MSG_MAP
END_MESSAGE_MAP()
 
 
void CVCTestVCComProjDlg::OnButton1()
{
         // TODO: Add your control notification handler code here
         HRESULT hr=CoInitialize(NULL);// 初始化 COM
 
         CLSID clsid;
         hr=CLSIDFromProgID(OLESTR("ValidateCreditCardServer.ValidateCard"),&clsid);// 获得服务器的 CLSID
 
         IValidateCard * cardptr;
         hr=CoCreateInstance(clsid,NULL,CLSCTX_INPROC_SERVER,__uuidof(IValidateCard),(LPVOID *)&cardptr);
         if(FAILED(hr))
         {
                   AfxMessageBox("Server Creation Failed");
                   return;
         }// 创建 COM 服务器组件的实例
 
         char cCardNum[20],cCardType[10];
         GetDlgItemText(IDC_EDIT1,cCardNum,20);
         GetDlgItemText(IDC_EDIT2,cCardType,10);
         _bstr_t bstCardNum(cCardNum),bstCardType(cCardType);
         long lStatus;
         lStatus=cardptr->ValidateCreditCard(bstCardNum,bstCardType);
         if(lStatus==1)
                   AfxMessageBox("valid card");
         else
                   AfxMessageBox("invalid card");          // 使用 COM 对象
 
         CoUninitialize();// 终止 COM
}

 


8.         编绎运行测试程序。
n           代码测试:同上 ( )


 
 
最后让我们分析一下 C:/VCTestVCComProj/Debug/ValidateCreditCardServer.tlh 作为 Developing COM Components using VC-ATL(3) 的结尾吧。
 
ValidateCreditCardServer.tlh 清单

 

// Created by Microsoft (R) C/C++ Compiler Version 12.00.8168.0 (ad98f509).
//
// c:/vctestvccomproj/debug/ValidateCreditCardServer.tlh
//
// C++ source equivalent of Win32 type library ../ValidateCreditCardServer/Debug/ValidateCreditCardServer.dll
// compiler-generated file created 12/19/03 at 03:29:03 - DO NOT EDIT!
 
#pragma once
#pragma pack(push, 8)
 
#include <comdef.h>
 
namespace VALIDATECREDITCARDSERVERLib {…………………………………..……… 名字空间
 
//
// Forward references and typedefs
//
 
struct /* coclass */ ValidateCard;………………………………………………………………..….. 组件类
struct __declspec(uuid(" b1d1c73c-e340-4fdc-8734-92ecec64dc4e "))
/* dual interface */ IValidateCard;………………………………………………….……………… 用户接口
 
//
// Smart pointer typedef declarations
//
 
_COM_SMARTPTR_TYPEDEF(IValidateCard, __uuidof(IValidateCard));………..……… 智能指针
 
//
// Type library items
//
 
struct __declspec(uuid(" c79635d2-7b86-4b20-a83c-0565dca54c3c "))………………… 组件类 CLSID
ValidateCard;
    // [ default ] interface IValidateCard
 
struct __declspec(uuid(" b1d1c73c-e340-4fdc-8734-92ecec64dc4e "))…………………… 用户接口 IID
IValidateCard : IDispatch
{
    //
    // Wrapper methods for error-handling
    //
 
    long ValidateCreditCard (
        _bstr_t bstrCCNo,
        _bstr_t bstrCCType );
    HRESULT LuhnCheck (
        _bstr_t bstrCCNo,
        _bstr_t bstrCCType );
    HRESULT LengthCheck (
        _bstr_t bstrCCNo,
        _bstr_t bstrCCType );
    HRESULT PrefixCheck (
        _bstr_t bstrCCNo,
        _bstr_t bstrCCType );……………………………………………….………….. 接口方法 ( 对外 )
 
    //
    // Raw methods provided by interface
    //
 
    virtual HRESULT __stdcall raw_ValidateCreditCard (
        BSTR bstrCCNo,
        BSTR bstrCCType,
        long * val ) = 0;
    virtual HRESULT __stdcall raw_LuhnCheck (
        BSTR bstrCCNo,
        BSTR bstrCCType ) = 0;
    virtual HRESULT __stdcall raw_LengthCheck (
        BSTR bstrCCNo,
        BSTR bstrCCType ) = 0;
    virtual HRESULT __stdcall raw_PrefixCheck (
        BSTR bstrCCNo,
        BSTR bstrCCType ) = 0;…………………………………………………..……. 接口方法 ( 对内 )
};
 
//
// Wrapper method implementations
//
 
#include "c:/vctestvccomproj/debug/ValidateCreditCardServer.tli"………….. 类型库实现文件
 
} // namespace VALIDATECREDITCARDSERVERLib……………………………..……….. 名字空间
 
#pragma pack(pop)

 

参照 COM 组件端 C:/ValidateCreditCardServer/ValidateCreditCardServer.idl
 
ValidateCreditCardServer.idl 清单

// ValidateCreditCardServer.idl : IDL source for ValidateCreditCardServer.dll
//
 
// This file will be processed by the MIDL tool to
// produce the type library (ValidateCreditCardServer.tlb) and marshalling code.
 
import "oaidl.idl";
import "ocidl.idl";
         [
                   object,
                   uuid(B1D1C73C-E340-4FDC-8734-92ECEC64DC4E),……………………………. IID 部分
                   dual,
                   helpstring("IValidateCard Interface"),
                   pointer_default(unique)
         ]
         interface IValidateCard : IDispatch
         {
                   [id(1), helpstring("method ValidateCreditCard")] HRESULT ValidateCreditCard([in] BSTR bstrCCNo,[in]BSTR bstrCCType,[out,retval]long *val );
                   [id(2), helpstring("method LuhnCheck")] HRESULT LuhnCheck([in] BSTR bstrCCNo,[in]BSTR bstrCCType);
                   [id(3), helpstring("method LengthCheck")] HRESULT LengthCheck([in] BSTR bstrCCNo,[in]BSTR bstrCCType);
                   [id(4), helpstring("method PrefixCheck")] HRESULT PrefixCheck([in] BSTR bstrCCNo,[in]BSTR bstrCCType);
         };
 
[
         uuid(B3404054-C33E-4931-B737-5783CEEFECDB),………………………………… LIBID 部分
         version(1.0),
         helpstring("ValidateCreditCardServer 1.0 Type Library")
]
library VALIDATECREDITCARDSERVERLib
{
         importlib("stdole32.tlb");
         importlib("stdole2.tlb");
 
         [
                   uuid(C79635D2-7B86-4B20-A83C-0565DCA54C3C),…………………….…… CLSID 部分
                   helpstring("ValidateCard Class")
         ]
         coclass ValidateCard
         {
                   [default] interface IValidateCard;
         };
};

小结 :
Developing COM Components using VC-ATL(3) 主要介绍了 Visual C++ Visual Basic 编写的组件的相互间的调用。在接下来一篇里将介绍双接口。
 
Developing COM Components using VC-ATL(3)  





确定创建服务器组件的最有效的工具
当你着手创建 COM 组件时,你可以选择与 Visual Basic Delphi MFC ATL 不同的工具。让我们比较一下每个工具,来揭露它们各自的优点和缺点。
当着手写 COM 类时,你可以有几种选择,你可以选择用 C++ 满足 COM 。这种选择提供了最大的灵活性但是很枯燥。另外,你可以选择 Visual C++ 提供的两种主要的框架来写 COM MFC ATL
MFC 使用文档视图体系结构设计良好的开发基于 Windows 的应用程序。使用 MFC 设计基于 Windows 应用程序要求运行时间库 MFC42.DLL 来执行。当用于开发确定类型的应用程序时, MFC 遇到要求大的运行时间库开销。 MFC 有利于像基于 Windows 的单层应用程序、请求许多 GUI 客户端的应用程序、 ActiveX 控件和 OLE 文档的开发。对 COM 的支持是后来增加的并且依靠它的文档视图体系结构。虽然 Visual C++ 向导使得用 MFC 创建组件容易很多,但于框架结构先天设计缺陷,所以它并不是一种灵活的方法。而且,组件以体积大和要求扩展 DLL 而告终。
相反, ATL 是针对 COM 来设计的。此外, ATL 提供像聚合和包容不同的类合成技术的完全支持。 ATL 使用 C++ 模板类的多继承技术来实现 COM 类。 ATL 允许 COM 组件、自动化服务器和 ActiveX 控件的创建。使用 ATL 创建的 COM 更小更快。 ATL 提供许多 COM 接口的本地支持和由 Windows NT 服务器支持的新线程模型。由于 ATL 主要使用模板的概念,创建的组件大小很小,并且运行期 DLL 很小并可动态加载。
所以,你应用 ATL 创建 COM
组件功能:同上 ( 编写 Visual Basic 组件 )
组件将包含以下函数: ValidateCreditCard PrefixCheck LengthCheck LuhnCheck 。仅 ValidateCreditCard 可以被应用程序调用来封装组件的工作方式。
为了创建验证信用卡的服务器组件,你需要执行以下步骤:
1.         创建应用程序框架
启动 VC++6.0 ,从 File 菜单选项选中 New 菜单项,在出现的 New 对话框中选中 Projects 卡片,从显示的工程类型中选中 ATL COM  AppWizard 。指定工程工作空间名,即在 Project Name 编辑框中输入项目名 ValidateCreditCardServer ,以区别于 Visual Basic 组件工程 ValidateCardServer 选择合适的 Location 后,按确认按钮进入下一个对话框: ATL  COM  Appwizard  -  step 1 of  1 ,在 Server Type 中选择 Dynamic  Link  Library [ DLL ] ,即进程内服务器组件。选中 Support  MFC 选项。在按下 Finish Ok 按钮后,一个组件的框架已经建立。现在让我们来看一下生成的文件的清单
n          stdafx.h
n          stdafx.cpp
n          ValidateCreditCardServer.idl
n          ValidateCreditCardServer.rc
n          Resource.h
n          ValidateCreditCardServer.def
n          ValidateCreditCardServerps.def
n          ValidateCreditCardServer.h
n          ValidateCreditCardServer.cpp
n          ValidateCreditCardServer.dsp
n          ValidateCreditCardServer.dsw
n          ValidateCreditCardServer.ncb
n          ValidateCreditCardServerps.mk
n          ValidateCreditCardServer.clw
ValidateCreditCardServer.cpp清单

// ValidateCreditCardServer.cpp : Implementation of DLL Exports.
 
 
// Note: Proxy/Stub Information
//      To build a separate proxy/stub DLL,
//      run nmake -f ValidateCreditCardServerps.mk in the project directory.
 
#include "stdafx.h"
#include "resource.h"
#include <initguid.h>
#include "ValidateCreditCardServer.h"
 
#include "ValidateCreditCardServer_i.c"
#include "ValidateCard.h"
 
 
CComModule _Module;
 
BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_ValidateCard, CValidateCard)
END_OBJECT_MAP()
 
class CValidateCreditCardServerApp : public CWinApp
{
public:
 
// Overrides
         // ClassWizard generated virtual function overrides
         //{{AFX_VIRTUAL(CValidateCreditCardServerApp)
         public:
    virtual BOOL InitInstance();
    virtual int ExitInstance();
         //}}AFX_VIRTUAL
 
         //{{AFX_MSG(CValidateCreditCardServerApp)
                   // NOTE - the ClassWizard will add and remove member functions here.
                   //    DO NOT EDIT what you see in these blocks of generated code !
         //}}AFX_MSG
         DECLARE_MESSAGE_MAP()
};
 
BEGIN_MESSAGE_MAP(CValidateCreditCardServerApp, CWinApp)
         //{{AFX_MSG_MAP(CValidateCreditCardServerApp)
                   // NOTE - the ClassWizard will add and remove mapping macros here.
                   //    DO NOT EDIT what you see in these blocks of generated code!
         //}}AFX_MSG_MAP
END_MESSAGE_MAP()
 
CValidateCreditCardServerApp theApp;
 
BOOL CValidateCreditCardServerApp::InitInstance()
{
    _Module.Init(ObjectMap, m_hInstance, &LIBID_VALIDATECREDITCARDSERVERLib);
    return CWinApp::InitInstance();
}
 
int CValidateCreditCardServerApp::ExitInstance()
{
    _Module.Term();
    return CWinApp::ExitInstance();
}
 
/
// Used to determine whether the DLL can be unloaded by OLE
 
STDAPI DllCanUnloadNow(void)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    return (AfxDllCanUnloadNow()==S_OK && _Module.GetLockCount()==0) ? S_OK : S_FALSE;
}
 
/
// Returns a class factory to create an object of the requested type
 
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
    return _Module.GetClassObject(rclsid, riid, ppv);
}
 
/
// DllRegisterServer - Adds entries to the system registry
 
STDAPI DllRegisterServer(void)
{
    // registers object, typelib and all interfaces in typelib
    return _Module.RegisterServer(TRUE);
}
 
/
// DllUnregisterServer - Removes entries from the system registry
 
STDAPI DllUnregisterServer(void)
{
    return _Module.UnregisterServer(TRUE);
}

2.         创建框架中的组件
Insert 菜单中选中 New ATL  Object… 菜单项,出现 ATL Object  Wizard 对话框。
ATL Object  Wizard 中,在左边的 Category 中选择 Objects ,右边的 Objects 中选中 Simple  Object 项。按 Next 按钮。
在出现的 ATL Object  Wizard 属性对话框中 Names 卡片中的八个编辑框中左上方的 Short Name 编辑框中输入短名 ValidateCard ,其他七个编辑框的内容会自动生成。在这里我们也对这一对话框的其他七个编辑框的内容作一个详细地剖析:
C++
Short Name ValidateCard// 用户输入
Class CValidateCard// 产生组件类的实现类的类名
.H File ValidateCard.h// 产生组件类的实现类的头文件
.CPP File ValidateCard.cpp// 产生组件类的实现类的实现文件
COM
CoClass ValidateCard// 产生的组件类,区别于组件类的实现类
Interface IValidateCard// 创建组件类时,同时创建一个此组件类的一个用户接口
Type ValidateCard Class// 组件类的类型
ProgID ValidateCreditCardServer.ValidateCard// CLSIDFromProgID 调用,用它得到 CLSID
然后按确认按钮退出。这将生成如上的信息并且在需要的文件中同样地作一个登陆,特别是 IDL 文件。在这里我们比较一下创建组件前后的两个版本的 IDL 文件。
创建组件前的 ValidateCreditCardServer.idl 文件,即 创建应用程序框架后的 IDL 文件。

    
// ValidateCreditCardServer.idl : IDL source for ValidateCreditCardServer.dll
//
 
// This file will be processed by the MIDL tool to
// produce the type library (ValidateCreditCardServer.tlb) and marshalling code.
 
import "oaidl.idl";
import "ocidl.idl";
 
[
         uuid(B3404054-C33E-4931-B737-5783CEEFECDB),
         version(1.0),
         helpstring("ValidateCreditCardServer 1.0 Type Library")
]
library VALIDATECREDITCARDSERVERLib
{
         importlib("stdole32.tlb");
         importlib("stdole2.tlb");
 
};

 


创建组件后的 IDL 文件
 
ValidateCreditCardServer.idl清单

import "oaidl.idl";
import "ocidl.idl";
         [
                   object,
                   uuid(B1D1C73C-E340-4FDC-8734-92ECEC64DC4E),
                   dual,
                   helpstring("IValidateCard Interface"),
                   pointer_default(unique)
         ]
         interface IValidateCard : IDispatch
         {
         };
 
[
         uuid(B3404054-C33E-4931-B737-5783CEEFECDB),
         version(1.0),
         helpstring("ValidateCreditCardServer 1.0 Type Library")
]
library VALIDATECREDITCARDSERVERLib
{
         importlib("stdole32.tlb");
         importlib("stdole2.tlb");
 
         [
                   uuid(C79635D2-7B86-4B20-A83C-0565DCA54C3C),
                   helpstring("ValidateCard Class")
         ]
         coclass ValidateCard
         {
                   [default] interface IValidateCard;
         };
};

 

文中黑体部分表示在框架中加入组件类后形成的代码,在框架中加入组件类之前,只有类型库部分,而在创建组件类后我们可以看到上面部分是一个组件类的用户接口,下面部分是一个组件类,其嵌套在类型库中。呵呵,三部分全到齐了。
另外,也说明一下 VALIDATECREDITCARDSERVERLib 这几个字母的用处,大家想到没有呀 ? 呵呵,它就是在客户端中使用名字空间时使用的呀。详细请见 Visual C++ 调用 Visual C++ 组件
 
3.         增加组件的功能。
要实现这一点,你必须增加检验信用卡有效性的函数。如何增加函数到你的组件,请按以下步骤操作:
VC++ 工作平台的左边的 Workspace ClassView 卡片中找到接口 IValidateCard 项,按右键,在出现的快捷菜单中选择 Add  Method … ,出现 Add  Method  to Interface 对话框,在对话框中输入要增加的函数的函数名、参数和返回值类型。在这里,我们在 Method Name 中输入
ValidateCreditCard
Parameters 中输入
[in] BSTR bstrCCNo,[in]BSTR bstrCCType,[out,retval]long *val
然后,按确认按钮退出。
类似地输入以下三个函数
函数功能
Method Name
Parameters
检验信用卡的号码是否合法
LuhnCheck
[in] BSTR bstrCCNo,[in]BSTR bstrCCType
检验信用卡的长度是否合法
LengthCheck
[in] BSTR bstrCCNo,[in]BSTR bstrCCType
检验信用卡的类型是否合法
PrefixCheck
[in] BSTR bstrCCNo,[in]BSTR bstrCCType
现在你可以看见为你的组件创建的接口定义语言文件的变化,你将在 IDL 文件 ValidateCreditCardServer.idl 中看到上面增加的所有函数的入口。
 
ValidateCreditCardServer.idl清单

// ValidateCreditCardServer.idl : IDL source for ValidateCreditCardServer.dll
//
 
// This file will be processed by the MIDL tool to
// produce the type library (ValidateCreditCardServer.tlb) and marshalling code.
 
import "oaidl.idl";
import "ocidl.idl";
         [
                   object,
                   uuid(B1D1C73C-E340-4FDC-8734-92ECEC64DC4E),
                   dual,
                   helpstring("IValidateCard Interface"),
                   pointer_default(unique)
         ]
         interface IValidateCard : IDispatch
         {
                   [id(1), helpstring("method ValidateCreditCard")] HRESULT ValidateCreditCard([in] BSTR bstrCCNo,[in]BSTR bstrCCType,[out,retval]long *val );
                   [id(2), helpstring("method LuhnCheck")] HRESULT LuhnCheck([in] BSTR bstrCCNo,[in]BSTR bstrCCType);
                   [id(3), helpstring("method LengthCheck")] HRESULT LengthCheck([in] BSTR bstrCCNo,[in]BSTR bstrCCType);
                   [id(4), helpstring("method PrefixCheck")] HRESULT PrefixCheck([in] BSTR bstrCCNo,[in]BSTR bstrCCType);
         };
...

另外它也在组件类的实现类的头文件 ValidateCard.h 和实现文件 ValidateCard.cpp 中分别加入函数的入口。
 
 

// ValidateCard.h : Declaration of the CValidateCard
 
#ifndef __VALIDATECARD_H_
#define __VALIDATECARD_H_
 
#include "resource.h"       // main symbols
 
/
// CValidateCard
class ATL_NO_VTABLE CValidateCard :
         public CComObjectRootEx<CComSingleThreadModel>,
         public CComCoClass<CValidateCard, &CLSID_ValidateCard>,
         public IDispatchImpl<IValidateCard, &IID_IValidateCard, &LIBID_VALIDATECREDITCARDSERVERLib>
{
public:
         CValidateCard()
         {
         }
 
DECLARE_REGISTRY_RESOURCEID(IDR_VALIDATECARD)
 
DECLARE_PROTECT_FINAL_CONSTRUCT()
 
BEGIN_COM_MAP(CValidateCard)
         COM_INTERFACE_ENTRY(IValidateCard)
         COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
 
// IValidateCard
public:
         STDMETHOD(PrefixCheck)(/*[in]*/ BSTR bstrCCNo,/*[in]*/BSTR bstrCCType);
         STDMETHOD(LengthCheck)(/*[in]*/ BSTR bstrCCNo,/*[in]*/BSTR bstrCCType);
         STDMETHOD(LuhnCheck)(/*[in]*/ BSTR bstrCCNo,/*[in]*/BSTR bstrCCType);
         STDMETHOD(ValidateCreditCard)(/*[in]*/ BSTR bstrCCNo,/*[in]*/BSTR bstrCCType,/*[out,retval]*/long *val);
};
 
#endif //__VALIDATECARD_H_

 










,创建一个客户程序
MFC  AppWizard ( exe ) 创建一个基于对话框的应用程序 MyExe
在对话框中放置四个按钮,分别为 MyF1MyF2 MyF3 MyF4
ClassWizard 生成单击按钮的四个响应函数 OnMyF1() OnMyF2() OnMyF3() OnMyF4()
,创建一个基于对话框的客户程序。
2
,把服务器类型库导入客户工作平台。
3
,初始化 COM 库。
4
,获得服务器的 GLSID
5
,创建 COM 组件服务器组件的实例。
6
,使用 COM 对象。
7
,终止 COM 库。
创建 COM 组件客户


一、创建模型 ( 工程 ) MyProj
VC++6.0 工作平台中,点击菜单 File 下的 New 菜单项,在出现的 New 对话框中选中 Projects 卡片,在列表框中选中 ATL COM  AppWizard (活动模板库组件导航)。   一、创建一个模型 ( 工程 ) MyProj
二、给模型 ( 工程 ) 增加一个组件 MyCom
三、给组件增加方法 ( 函数 ) MyF1 MyF2 MyF3 MyF4
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值