自己关于一个基础ATL/COM的总结(1)

来源:互联网 发布:c语言解惑pdf百度网盘 编辑:程序博客网 时间:2024/06/06 02:32

http://www.vckbase.com/vckbase/columnist/yangfeng/这个是杨老师写的关于COM的文章 

下面是个人看的能理解的部分 

   最近看了一些关于COM的东西!!开始的时候不知所云,看了半天不知道在讲什么东西.....现在大致有了一些了解个人理解COM就是接口的封装只要获取了COM的GLSID地址(全球唯一的标识地址)或者获取ProgID地址既在注册表里面关于组建的注册地址那么就可以调用这个组建和组建里面的函数功能。个人理解有点像你得到了某个东西的ID那么你就可以使用这个东西!下面是自己在网上找的一个简单的实例。

     COM的最初的想法是为了打败电子做电子表格软件的对手LOTUS-123,于是想如果我能在WORD程序中嵌入了EXCEL等软件,并采取自定义的结构方式,对文件进行储存,那顾客在买了WORD的时候是不是不用再卖LOTUS_123了最初是建立一个子目录,把DOC,XLS存在这同一目录中或者修改文件储存结构,在DOC结构接触扩展出包容XLS的结构但是都有缺点最后微软提出了一个非常完美的设计方案把磁盘文件的管理方案移植到了文件中-------复合文件,俗称“文件中的文件系统”

     这样微软就解决了怎么保存XLS(EXCEL)数据的问题了。那么接下来就要解决另外一个问题就是当WORD程序读取复合文件的问候遇到了XLS数据的时候他怎么启动EXCEL文件呢?启动后如何让EXCEL自己去读入、解析、显示XLS数据呢?

CLSID概念有了一个非常简单的解决方案,那就是在数据对象前面,保留有处理这个数据的程序名字

         ----DOC文件存根目录

               -----DOC数据流1

               -----DOC数据流2

        -----EXCEL使用的XLS子存储

               ------CLSID(其实就是一个号码)

               ------XLS数据流

 

CLSID的位置在注册表里的HKEY_CLASS_ROOT   -->CLSID      ---->LocalServer32中的默认:REG_SZ  数据:D:/PROGRA~1/MICROS~4/Office/EXCEL.EXE.

   问题来了:这个在张三的计算机上EXCEL路径是:"C:/office/Excel.exe如果把这个DOC复制到李四的计算机上使用EXCEL路径是:“D:/.......................................那就不能用了!

 

typedef struct _GUID {
DWORD Data1; // 随机数
WORD Data2; // 和时间相关
WORD Data3; // 和时间相关
BYTE Data4[8]; // 和网卡MAC相关
} GUID;

typedef GUID CLSID;  // 组件ID
typedef GUID IID;    // 接口ID
#define REFCLSID const CLSID &

// 常见的声明和赋值方法
CLSID CLSID_Excel = {0x00024500,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
struct __declspec(uuid("00024500-0000-0000-C000-000000000046")) CLSID_Excel;
class DECLSPEC_UUID("00024500-0000-0000-C000-000000000046") CLSID_Excel;
// 注册表中的表示方法
{00024500-0000-0000-C000-000000000046}
用一个号码间接表示程序名,的确是个 Good idea,实现了组件位置的透明性,并方便地扩展出 DCOM(远程组件)

 

函数功能说明CLSIDFromProgID()、CLSIDFromProgIDEx()由 ProgID 得到 CLSID。没什么好说的,你自己都可以写,查注册表贝ProgIDFromCLSID()由 CLSID 得到 ProgID,调用者使用完成后要释放 ProgID 的内存(注5)CoCreateGuid()随机生成一个 GUIDIsEqualGUID()、IsEqualCLSID()、IsEqualIID()比较2个ID是否相等StringFromCLSID()、StringFromGUID2()、StringFromIID()由 CLSID,IID 得到注册表中CLSID样式的字符串,注意释放内存

 

容器(或者说客户端)就是这样和组件进行对话,协商调用的。如果组件甲实现了 IA 接口,那么容器就会使用它,如果组件乙没有提供 IA 接口,但是它提供了 IB 接口,那么容器就会调用 IB 接口的函数......如此,容器程序根本就不需要知道组件到底是干什么的,组件到底是用什么语言开发的,组件的磁盘位置到底在哪里,它都可以正常运行。

 

请大家牢牢记住一条原则:COM 组件是运行在分布式环境中的。比如,你写了一个组件程序(DLL或EXE),那么使用者可能是在本机的某个进程内加载组件(INPROC_SERVER);也可能是从另一个进程中调用组件的进程(LOCAL_SERVER);也可能是在这台计算机上调用地球那边计算机上的组件(REMOTE_SERVER)。所以在理解和设计的时候,要时时刻刻想起这句话

 

HRESULT含义S_OK0x00000000成功S_FALSE0x00000001函数成功执行完成,但返回时出现错误E_INVALIDARG0x80070057参数有错误E_OUTOFMEMORY0x8007000E内存申请错误E_UNEXPECTED0x8000FFFF未知的异常E_NOTIMPL0x80004001未实现功能E_FAIL0x80004005没有详细说明的错误。一般需要取得 Rich Error 错误信息(注1)E_POINTER0x80004003无效的指针E_HANDLE0x80070006无效的句柄E_ABORT0x80004004终止操作E_ACCESSDENIED0x80070005访问被拒绝E_NOINTERFACE0x80004002不支持接口

 


、HRESULT 的结构

  HRESULT 其实是一个双字节的值,其最高位(bit)如果是0表示成功,1表示错误。具体参见 MSDN 之"Structure of COM Error Codes"说明。我们在程序中如果需要判断返回值,则可以使用比较运算符号;switch开关语句;也可以使用VC提供的宏:

      HRESULT hr = 调用组件函数;      if( SUCCEEDED( hr ) ){...} // 如果成功      ......      if( FAILED( hr ) ){...} // 如果失败      ......
二、组件的启动和释放

实在我们写程序的时候到比较简单,请大家遵守几个原则:
  1、启动组件得到一个接口指针(Interface)后,不要调用AddRef()。因为系统知道你得到了一个指针,所以它已经帮你调用了AddRef()函数;
  2、通过QueryInterface()得到另一个接口指针后,不要调用AddRef()。因为......和上面的道理一样;
  3、当你把接口指针赋值给(保存到)另一个变量中的时候,请调用AddRef();
  4、当不需要再使用接口指针的时候,务必执行Release()释放;
  5、当使用智能指针的时候,可以省略指针的维护工作;(注1

 

三、内存分配和释放

三、内存分配和释放

  自从学习了C语言,老师就教导我们说:对于动态内存的申请和释放,一定要遵守“谁申请,谁释放”的原则。在此原则的指导下,不仅是我、不仅是你,就连特级大师都设计了这样怪怪的函数:
 
函数说明评论GetWindowText(HWND,LPTSTR,int)取得窗口标题。需要在参数中给出保存标题所使用的内存指针,和这块内存的尺寸。晕!我又不知道窗口标题的长度,居然还要我提供尺寸?!没办法,只能估摸着给一个大一些的尺寸吧。sprintf(char *,const char *,...)格式化一个字符串。这个函数不用给出缓冲区的长度啦。恩,虽然不用给出长度了,但你敢给个小尺寸吗?哼!int CListBox::GetTextLen(int)
CListBox::GetText(int,LPTSTR)
取得列表窗中子项目的标题。需要调用两个函数,先取得长度,然后分配内存,再实际取得标题内容。真烦!

  说实在的,不但函数调用者感觉别扭,就连函数设计者心情也不会爽的,而这一切都是为了满足所谓“谁申请,谁释放”的原则。 解决这个问题最好的方式就是:函数内部根据实际需要动态申请内存,而调用者负责释放。这虽然违背了上述原则,但 COM 从方便性和效率出发,确实是这么设计的。
 
 C语言C++语言Windows 平台COMIMalloc 接口BSTR申请malloc()newGlobalAlloc()CoTaskMemAlloc()Alloc()SysAllocString()重新申请realloc() GlobalReAlloc()CoTaskRealloc()Realloc()SysReAllocString()释放free()deleteGlobalFree()CoTaskMemFree()Free()SysFreeString()

 

 

好了现在正是开始一个简单的组件创建VC6.0

步骤1.、建立一个ATL/COM的工程  选择Dynamic Link Library(DLL) 表示建立一个 DLL 的组件程序。(工程名字叫Simple_ATL)
        注释:    Executable(EXE) 表示建立一个 EXE 的组件程序。
                Service(EXE) 表示建立一个服务程序,系统启动后就会加载并执行的程序。
               Allow merging of proxy/stub code 选择该项表示把“代理/存根”代码合并到组件程序中,否则需要单独编译,单独注册代理存根程序。代理/存根

步骤2、新建一个类名字叫First_ATL

Threading Model 选择组件支持的线程模型。COM 中的线程,我认为是最讨厌,最复杂的部分。COM 线程和公寓的概念,留待后续介绍。现在吗......大家都选 Apartment,它代表什么那?简单地说:当在线程中调用组件函数的时候,这些调用会排队进行。因此,这种模式下,我们可以暂时不用考虑同步的问题。(注1)
   Interface 接口基本类型。Dual 表示支持双接口(注2),这个非常 非常重要,非常非常常用,但我们今天不讲。Custom 表示自定义借口。切记!切记!我们的这第一个 COM 程序中,一定要选择它!!!!

步骤三、如果你现在在工作区中单击了“ClassView”标签,那么你会注意到向导在其中添加了一串东西。我们想添加的第一个东西是一个方法,可以在“IFirst_ATL”上右击鼠标键,并选择“Add Method”。

[in] long Num1, [in] long Num2, [out] long *ReturnVal

  简单地说来,我们声明了两个long类型的参数,这两个值是传入的([in]),还有一个最后传出的返回值结果([out])。(你第一次看到这样的东西可能会有些奇怪,但是如果你读了一两本关于COM的书的话,就会觉得亲切多了。)现在就可以单击OK按钮了。然后,单击“ClassView”标签,并展开所有的“+”标志,使得树型视图完全展开。你会在接口(IFirst_ATL)的顶部看到我们的“AddNumbers”方法以及我们给予它的参数。在这个方法上双击鼠标键,并插入以下的代码:

STDMETHODIMP CFirst_ATL::AddNumbers(long Num1, 
         long Num2, long *ReturnVal)
{
   // TODO: Add your implementation code here
   *ReturnVal = Num1 + Num2;
   return S_OK;
}

 

 

这样你的一个简单的ATL/COM就创建成功了

接下来是使用这个简单的组件

建立一个新的工程MFC APP wizard对话框工程

// 你需要指明Simple_ATL工程的路径来引用这个头文件
#include "../Simple_ATL/Simple_ATL.h"
#include <iostream.h>
// 把以下的内容从Simple_ATL工程目录的Simple_ATL_i.c文件中复制过来
// 注意:你也可以直接包含Simple_ATL_i.c文件,我在此只想清楚地表明这些const常量来自何处以及它们的样子
const IID IID_IFirst_ATL =
   {0xC8F6E230,0x2672,0x11D3,
   {0xA8,0xA8,0x00,0x10,0x5A,0xA9,0x43,0xDF}};
const CLSID CLSID_First_ATL =
   {0x970599E0,0x2673,0x11D3,
   {0xA8,0xA8,0x00,0x10,0x5A,0xA9,0x43,0xDF}};
void main(void)
{
   // 声明一个HRESULT变量以及一个Simple_ATL接口的指针
   HRESULT     hr;
   IFirst_ATL   *IFirstATL = NULL;
   // 现在初始化COM
   hr = CoInitialize(0);
   // 使用SUCCEEDED宏来看看我们是否能够获得接口的指针
   if(SUCCEEDED(hr))
   {
     hr = CoCreateInstance( CLSID_First_ATL, NULL, 
       CLSCTX_INPROC_SERVER,
       IID_IFirst_ATL, (void**) &IFirstATL);
     // 如果成功了,那么调用AddNumbers方法
     // 否则给用户显示一条适当的信息
     if(SUCCEEDED(hr))
     {
       long ReturnValue;
       IFirstATL->AddNumbers(5, 7, &ReturnValue);
       cout << "The answer for 5 + 7 is: " 
         << ReturnValue << endl;
       IFirstATL->Release(); 
     }
     else
     {
       cout << "CoCreateInstance Failed." << endl;
     }
   }
   // 卸载COM
   CoUninitialize();
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

原创粉丝点击