zoukankan      html  css  js  c++  java
  • 用MFC建立COM服务器对象的框架步骤

     

    用MFC建立COM服务器对象的框架步骤

    任务1:建立第一个COM服务器
    任务2:用VC调用COM接口
    任务3:用VB调用COM接口
    任务4:给接口增加新的属性和方法
    任务5:增加新的自定义接口
    任务6:继承接口类


    例子程序在tecsp/docs/samples/comMFCDemo下

    COM服务器:comMFCDemo
    COM对象:coDrawObj,coDrawObjLine,coDrawObjRect


    任务1:建立第一个COM服务器
       操作:
       1. 新建一个工程comMFCDemo,MFC AppWizard(dll),Regular DLL using shared MFC DLL,Automation.
       说明:此时已建立COM服务器的基本框架,包括用于实例化和注册用的
       DllCanUnloadNow(),DllGetObject(),DllRegisterServer()三个函数.

       2. 在工程中加入mfcdual.h,mfcdual.cpp文件(在tecsp/comm目录中,应将该目录放在头文件查找路径中)
          在stdafx.h中加入
          #include "mfcdual.h"
          #include "comMFCDemo_i.h"
       
       说明:mfcdual.h中定义了一些用于简化编程的宏定义,减少MFC COM编程的重复劳动.按照本文档维护COM对象接口将非常轻松简明.
             comMFCDemo_i.h是midl从odl文件中生成的,包括COM对象,接口定义,GUID定义等.

        3. 编译选项
           Project => Settings... => MIDL => Project Options
           加入:/h comMFCDemo_i.h
           MIDL将编译odl文件生成类型库comMFCDemo.tlb,头文件comMFCDemo_i.h,定义文件comMFCDemo_i.c.

    任务2:新增一个COM对象
        1. 用Class Wizard建立一个新类:CcoDrawObj,基类为CCmdTarget,选择Createable by type ID: comMFCDemo.coDrawObj.
        说明:为避免手工添加接口定义,先选择自动化接口,将会自动生成odl文件和相应的接口定义,只需做少量的修改工作即可.选择Createable by type ID是为了能够通过类厂来创建COM对象.

        2. 修改odl文件中IDualDrawObj的定义
        2.1 将
     [ uuid(13144AD4-ECC8-46F4-88F7-2FAC305595EA) ]
     dispinterface IcoDrawObj
            改成:
     [ uuid(13144AD4-ECC8-46F4-88F7-2FAC305595EA),
      oleautomation,
      dual
     ]
     interface IcoDrawObj : IDispatch
     interface IcoDrawObj : IDispatch
         说明:建议将COM对象的缺省接口定义成双接口,可以在多种环境中使用.

        2.2 删除properties: 和 methods:这两行

        2.3 在{}中定义属性和方法

      // IcoDrawObj 属性:
      [propput, helpstring("横坐标")]
      HRESULT x([in] short new_x);
      [propget, helpstring("横坐标")]
      HRESULT x([out, retval] short* ret_x);
      // IcoDrawObj 方法:
      [helpstring("绘制图形")]
      HRESULT Draw();
            说明:上面的注释应保留以方便接口继承时使用;helpstring用于属性和方法的注释说明,应加上;只读属性删除propput定义的两行.
            注意new_x和ret_x变量的书写方法,put属性使用new_前缀加上属性名,get属性使用ret_前缀加上属性名,这样是为了方便使用宏定义来自动生成属性声明和实现.

        2.4 修改coclass定义
            将
     coclass coDrawObj
     {
      [default] dispinterface IcoDrawObj;
     };
            改成:
     coclass coDrawObj
     {
      [default] interface IcoDrawObj;
     };
        2.5 编译odl文件
            选择FileView,在comMFCDemo.odl文件上点击右键,选择Compile comMFCDemo.odl,将生成类型库comMFCDemo.tlb,头文件comMFCDemo_i.h和定义文件comMFCDemo_i.c.
            将comMFCDemo_i.h和comMFCDemo_i.c加入工程中.
            选择Project => Settings... => comMFCDemo_i.c,在右侧面板选择C/C++ => Category:Precompiled Headers => Not using precompiled headers

        3. 修改COM对象头文件coDrawObj.h文件
        3.1 加入属性对应的内部成员变量:protected,m_前缀加上属性名
            short m_x;
        3.2 接口声明
     // ==============
     // 双接口支持开始
     // ==============
     
     // ----------------------
     // IcoDrawObj接口声明开始
     // ----------------------

     // 此处用宏定义是为了支持接口继承 ,在此处添加属性和方法
    #define DECLARE_INTERFACE_PART_IcoDrawObj \
      STDMETHOD(get_x)(THIS_ short FAR* ret_x); \
      STDMETHOD(put_x)(THIS_ short new_x); \
      STDMETHOD(Draw)(); \

     BEGIN_DUAL_INTERFACE_PART(coDrawObj, IcoDrawObj)
      DECLARE_INTERFACE_PART_IcoDrawObj
     END_DUAL_INTERFACE_PART(coDrawObj)

        需要修改:
     DECLARE_INTERFACE_PART_IcoDrawObj - IcoDrawObj是接口名字
     BEGIN_DUAL_INTERFACE_PART(coDrawObj, IcoDrawObj) - coDrawObj是组件名字,IcoDrawObj是接口名字。
     END_DUAL_INTERFACE_PART(coDrawObj) - coDrawObj是组件名字,IcoDrawObj是接口名字。

     // ----------------------
     // IcoDrawObj接口声明结束
     // ----------------------
     
     // ISupportErrorInfo支持
     DECLARE_DUAL_ERRORINFO()
      
     // ==============
     // 双接口支持结束
     // ==============

            说明:需要注意的是宏定义DECLARE_INTERFACE_PART_IcoDrawObj,最后面是接口的名字.该宏主要用于接口继承时,在子接口声明中声明父接口的属性和方法.以后也在该宏定义中添加新的属性和方法的声明.

        3.3 属性读写实现
            说明:属性读写实现基本结构都很相似,为减少代码书写量和书写错误,特采用宏定义方式.
           需要修改部份:
            宏定义IMPLEMENT_PROPERTIES_IcoDrawObj是为了用于接口继承,最后面是接口的名字。
            目前实现了两种属性读写:
            普通属性(short, float等常规类型)
              DECLARE_PUT_PROPERTY(objClass, baseClass, x)(short new_x) - 需改动部份:x 是属性名字,(short new_x) 与声明中相同。
              IMPLEMENT_PUT_PROPERTY(objClass, baseClass, x) - 需改动部份:x 是属性名字。
              DECLARE_GET_PROPERTY(objClass, baseClass, x)(short FAR* ret_x) - 需改动部份:x 是属性名字,(short FAR* ret_x) 与声明中相同。
              IMPLEMENT_GET_PROPERTY(objClass, baseClass, x) - 需改动部份:x 是属性名字。

            接口指针属性
            因为接口指针的赋值需要调用AddRef方法,故特别增加此宏定义。使用的唯一不同之处在于PROPERTY后面增加_INTERFACE后缀。

    // ================
    // 属性读写实现开始
    // ================

    // ---------------------------
    // IcoDrawObj 属性读写实现开始
    // ---------------------------

    #define IMPLEMENT_PROPERTIES_IcoDrawObj(objClass, baseClass) \
    DECLARE_PUT_PROPERTY(objClass, baseClass, x)(short new_x) \
     IMPLEMENT_PUT_PROPERTY(objClass, baseClass, x) \
    DECLARE_GET_PROPERTY(objClass, baseClass, x)(short FAR* ret_x) \
     IMPLEMENT_GET_PROPERTY(objClass, baseClass, x) \

    // ---------------------------
    // IcoDrawObj 属性读写实现结束
    // ---------------------------

    // ================
    // 属性读写实现结束
    // ================

    // ============
    // 方法实现开始
    // ============

        3.4 方法实现
           MFC采用嵌套类的形式实现多接口,方法的实现基本结构相同,故采用宏定义减少代码书写量和书写错误。
           解决方法是每个接口的方法都调用嵌套父类的同名函数,这样嵌套类方法的实现全部是同一种结构,可以用宏定义生成。
           对每一个方法在嵌套父类中创建一个proteced virtual的同名成员函数,建立一个以接口名字命名的实现文件IcoDrawObj.cpp,将同名成员函数的实现放在里面。这样在接口定义好后,可以让程序员只编写这个文件中的函数实现,达到统一控制接口定义的目的。

        3.4.1 嵌套类方法实现
            以下嵌套类方法实现的宏定义放在头文件coDrawObj.h中属性读写实现的后面。
            需要修改部份:
            宏定义IMPLEMENT_METHODS_IcoDrawObj用于接口继承,最后面是接口的名字。
            STDMETHODIMP objClass::X##baseClass::Draw()最后的方法名字
     pThis->Draw(); 最后的方法名字

    // ============
    // 方法实现开始
    // ============

    // -----------------------
    // IcoDrawObj 方法实现开始
    // -----------------------

    #define IMPLEMENT_METHODS_IcoDrawObj(objClass, baseClass) \
    STDMETHODIMP objClass::X##baseClass::Draw() \
    { \
     BEGIN_NEST_INTERFACE_FRAME(objClass, baseClass) \
     pThis->Draw();\
     END_NEST_INTERFACE_FRAME \
    } \

    // -----------------------
    // IcoDrawObj 方法实现结束
    // -----------------------

    // ============
    // 方法实现结束
    // ============

        3.4.2 嵌套父类方法实现
           IcoDrawObj.cpp文件样本(接口定义完成后,程序员仅需编写此文件中的函数实现即可):

    // IcoDrawObj.cpp

    #include "stdafx.h"
    #include "coDrawObj.h"

    void CcoDrawObj::Draw()
    {
     AfxMessageBox( "This is coDrawObj.");
    }


        4. 修改coDrawObj.cpp文件
        4.1 构造函数colDrawObj()
     // 自动化支持
     EnableAutomation();
     // 聚合支持
     EnableAggregation(); 
        4.2 删除IID定义
           删除类似以下这行,因为前面已在工程中加入了MIDL自动生成的comMFCDemo_i.c文件,我们可以在一个文件中管理全部的GUID定义。
    static const IID IID_IcoDrawObj =
    { 0x13144ad4, 0xecc8, 0x46f4, { 0x88, 0xf7, 0x2f, 0xac, 0x30, 0x55, 0x95, 0xea } };

        4.3 多接口支持定义

            将

    BEGIN_INTERFACE_MAP(CcoDrawObj, CCmdTarget)
     INTERFACE_PART(CcoDrawObj, IID_IcoDrawObj, Dispatch)
    END_INTERFACE_MAP()

            改成:

    // 多接口支持定义
    BEGIN_INTERFACE_MAP(CcoDrawObj, CCmdTarget)
     INTERFACE_PART(CcoDrawObj, IID_IcoDrawObj, coDrawObj)
     DUAL_ERRORINFO_PART(CcoDrawObj)
    END_INTERFACE_MAP()

        需要修改部分:CcoDrawObj为COM对象类,IID_IcoDrawObj为接口GUID,coDrawObj为接口名字删去I前缀。

        4.4 类厂实现
        系统已自动加入,类似以下这行:
    // {09671A2C-04FA-495E-A40C-410073A3A36E}
    IMPLEMENT_OLECREATE(CcoDrawObj, "comMFCDemo.coDrawObj", 0x9671a2c, 0x4fa, 0x495e, 0xa4, 0xc, 0x41, 0x0, 0x73, 0xa3, 0xa3, 0x6e)

        4.5 实现标准的基接口方法
        加入以下代码:

    // 实现标准的IDispatch方法
    DELEGATE_DUAL_INTERFACE(CcoDrawObj, coDrawObj)
    // 实现标准的IUnknown方法
    // DELEGATE_IUNKNOWN_INTERFACE(CcoDrawObj, coDrawObj)

       需要修改部分:CcoDrawObj为COM对象类,coDrawObj为接口名字删去I前缀。

       对自动化接口使用第一个宏定义,对自定义接口使用第二个宏定义。
       每个接口需要一行宏定义,修改部份为最后的coDrawObj为接口名字删去I前缀。

        4.6 ISupportErrorInfo支持

        加入以下一行:

    // ISupportErrorInfo 支持实现
    IMPLEMENT_DUAL_ERRORINFO(CcoDrawObj, IID_IcoDrawObj)

        需要修改部分:CcoDrawObj为COM对象类,IID_IcoDrawObj为缺省接口GUID。
        说明:目前仅支持一个接口(请使用缺省接口),以后再加入多接口支持。

        4.7 接口属性实现
        每个接口加入以下一行:

    // 接口属性实现
    IMPLEMENT_PROPERTIES_IcoDrawObj(CcoDrawObj, coDrawObj)

        需要修改部份:IcoDrawObj为接口名字,CcoDrawObj为COM对象类,coDrawObj为接口名字删去I前缀。


        4.8 接口方法实现
        每个接口加入以下一行:

    // 接口方法实现
    IMPLEMENT_METHODS_IcoDrawObj(CcoDrawObj, coDrawObj)

        需要修改部份:IcoDrawObj为接口名字,CcoDrawObj为COM对象类,coDrawObj为接口名字删去I前缀。


        5. 注册COM服务器
        好了,现在这个支持双接口的COM服务器已经完成了,赶快注册吧。
        Tools => Register Control
        COM服务器:comMFCDemo
        自动化对象:coDrawObj,支持双接口IcoDrawObj,属性x,方法Draw
       

    任务2:用VC调用COM接口

      1. 在工程中加入新项目:vcDemo,选择Dialog Base
         在项目中加入../comMFCDemo_i.h和../comMFCDemo_i.c文件
            选择Project => Settings... => comMFCDemo_i.c,在右侧面板选择C/C++ => Category:Precompiled Headers => Not using precompiled headers

      2. 在App文件中InitInstance函数中加入COM库初始化:
     if ( !AfxOleInit() )
      return FALSE;
      3. 在对话框中加入一个按钮,定义它的点击消息
      4. 在vcDemoDlg.cpp中

    #include "../comMFCDemo_i.h"

    IcoDrawObj* pIcoDrawObj = NULL;

    BOOL CVcDemoDlg::OnInitDialog()
    {
    ...
     HRESULT hr;
     hr = CoCreateInstance( CLSID_coDrawObj, NULL, CLSCTX_INPROC_SERVER, IID_IcoDrawObj, reinterpret_cast<void**>(&pIcoDrawObj));
     if ( FAILED(hr) )
      return FALSE;

    ...
    }

    BOOL CVcDemoDlg::DestroyWindow()
    {
     if ( pIcoDrawObj )
     {
      pIcoDrawObj->Release();
      pIcoDrawObj = NULL;
     }
     return CDialog::DestroyWindow();
    }

    void CVcDemoDlg::OnButton1()
    {
     if ( pIcoDrawObj )
     {
      pIcoDrawObj->Draw();
     }
    }

        5. 好了,现在可以编译运行,看看点击按钮后出现了什么?


    任务3:用VB调用COM接口

       我们创建的是自动化对象可以在VB中方便使用
       1. 创建VB应用
       2. 加入COM对象引用
          Project => References... => Browse.. => ../Debug/comMFCDemo.tlb

       3. 创建一个按钮,生成它的点击事件
    Private Sub Command1_Click()
      Dim coDrawObj As New coDrawObj
      coDrawObj.Draw
    End Sub

       4. 好了,可以运行,点击按钮。

    任务4:给接口增加新的属性和方法

       1. 修改odl文件
     interface IcoDrawObj : IDispatch
     {
      // IcoDrawObj 属性:
      [propput, helpstring("横坐标")]
      HRESULT x([in] short new_x);
      [propget, helpstring("横坐标")]
      HRESULT x([out, retval] short* ret_x);
    >>  [propput, helpstring("纵坐标")]
    >>  HRESULT y([in] short new_y);
    >>  [propget, helpstring("纵坐标")]
    >>  HRESULT y([out, retval] short* ret_y);
      // IcoDrawObj 方法:
      [helpstring("绘制图形")]
      HRESULT Draw();
    >>  [helpstring("关于")]
    >>  HRESULT AboutMe();
     };
          编译odl文件

        2. 在COM对象头文件coDrawObj.h中加入属性对应的内部成员变量
           short m_y;

        3. 在COM对象头文件coDrawObj.h中修改接口声明
    #define DECLARE_INTERFACE_PART_IcoDrawObj \
      STDMETHOD(get_x)(THIS_ short FAR* ret_x); \
      STDMETHOD(put_x)(THIS_ short new_x); \
    >>  STDMETHOD(get_y)(THIS_ short FAR* ret_y); \
    >>  STDMETHOD(put_y)(THIS_ short new_y); \
      STDMETHOD(Draw)(); \
    >>  STDMETHOD(AboutMe)(); \

        4. 在COM对象头文件coDrawObj.h中修改属性读写实现
    #define IMPLEMENT_PROPERTIES_IcoDrawObj(objClass, baseClass) \
    DECLARE_PUT_PROPERTY(objClass, baseClass, x)(short new_x) \
     IMPLEMENT_PUT_PROPERTY(objClass, baseClass, x) \
    DECLARE_GET_PROPERTY(objClass, baseClass, x)(short FAR* ret_x) \
     IMPLEMENT_GET_PROPERTY(objClass, baseClass, x) \
    >> DECLARE_PUT_PROPERTY(objClass, baseClass, y)(short new_y) \
    >>  IMPLEMENT_PUT_PROPERTY(objClass, baseClass, y) \
    >> DECLARE_GET_PROPERTY(objClass, baseClass, y)(short FAR* ret_y) \
    >>  IMPLEMENT_GET_PROPERTY(objClass, baseClass, y) \


        4. 在COM对象头文件coDrawObj.h中修改接口方法实现

    #define IMPLEMENT_METHODS_IcoDrawObj(objClass, baseClass) \
    STDMETHODIMP objClass::X##baseClass::Draw() \
    { \
     BEGIN_NEST_INTERFACE_FRAME(objClass, baseClass) \
     pThis->Draw();\
     END_NEST_INTERFACE_FRAME \
    } \
    >> STDMETHODIMP objClass::X##baseClass::AboutMe() \
    >> { \
    >>  BEGIN_NEST_INTERFACE_FRAME(objClass, baseClass) \
    >>  pThis->AboutMe();\
    >>  END_NEST_INTERFACE_FRAME \
    >> } \

        5. 创建嵌套父类CcoDrawObj的protected virtual成员函数AboutMe
           将函数实现移到IcoDrawObj.cpp中,交给程序员去完成。
    void CcoDrawObj::AboutMe()
    {
     AfxMessageBox( "This is coDrawObj AboutMe().");
    }


        6. 编译,注册。现在可以在vcDemo中加入  pIcoDrawObj->AboutMe();测试一下。


    任务5:增加新的自定义接口
       缺省自动化接口提供对其它开发平台程序员的支持,COM对象内部之间还需要一些内部的工作需要用自定义接口实现。

       1. 修改odl文件
          增加新接口(接口uuid是用guidgen生成的)
     [ uuid(8C1A8020-627B-4c37-AE70-51F9AD56FFBD),
      object,
      helpstring("coDrawObj的计算方法")
     ]
     interface ICalc : IUnknown
     {
      [helpstring("计算")]
      HRESULT Calc();
     };

          将新接口加入到COM对象类中
     coclass coDrawObj
     {
      [default] interface IcoDrawObj;
    >>  interface ICalc;
     };
          编译odl文件
        2. 修改COM对象头文件coDrawObj.h
        2.1 增加自定义接口声明
     // ----------------------
     // ICalc接口声明开始
     // ----------------------

    #define DECLARE_INTERFACE_PART_ICalc \
      STDMETHOD(Calc)(); \

     BEGIN_INTERFACE_PART(Calc, ICalc)
      DECLARE_INTERFACE_PART_ICalc
     END_INTERFACE_PART(Calc)

     // ----------------------
     // ICalc接口声明结束
     // ----------------------

        需要修改:
     DECLARE_INTERFACE_PART_ICalc - ICalc是接口名字
     BEGIN_INTERFACE_PART(coDrawObj, ICalc) - Calc是接口名字删去I前缀,ICalc是接口名字。
     END_INTERFACE_PART(Calc) - Calc是接口名字删去I前缀,ICalc是接口名字。

          2.2 接口方法实现
    // -----------------------
    // ICalc 方法实现开始
    // -----------------------

    #define IMPLEMENT_METHODS_ICalc(objClass, baseClass) \
    STDMETHODIMP objClass::X##baseClass::Calc() \
    { \
     BEGIN_NEST_INTERFACE_FRAME(objClass, baseClass) \
     pThis->Calc();\
     END_NEST_INTERFACE_FRAME \
    } \

    // -----------------------
    // ICalc 方法实现结束
    // -----------------------
        需要修改部份:IMPLEMENT_METHODS_ICalc最后为接口名字
    STDMETHODIMP objClass::X##baseClass::Calc() 最后为接口方法名字
     pThis->Calc();最后为接口方法名字

        2.3 创建嵌套父类CcoDrawObj的protected virtual成员函数Calc
           将函数实现移到ICalc.cpp中,交给程序员去完成。
    void CcoDrawObj::Calc()
    {
     AfxMessageBox( "This is coDrawObj Calc().");
    }

        3. 修改COM对象实现文件coDrawObj.cpp
        加入
    // 多接口支持定义
    BEGIN_INTERFACE_MAP(CcoDrawObj, CCmdTarget)
     INTERFACE_PART(CcoDrawObj, IID_IcoDrawObj, coDrawObj)
    >> INTERFACE_PART(CcoDrawObj, IID_ICalc, Calc)
     DUAL_ERRORINFO_PART(CcoDrawObj)
    END_INTERFACE_MAP()
      需要修改:
         IID_ICalc - 接口的GUID
         CcoDrawObj - COM对象类
         Calc - 接口名字删去I前缀


    // 实现ICalc的标准IUnknown方法
    DELEGATE_IUNKNOWN_INTERFACE(CcoDrawObj, Calc)
        需要修改:    
         CcoDrawObj - COM对象类
         Calc - 接口名字删去I前缀


    IMPLEMENT_METHODS_ICalc(CcoDrawObj, Calc)

         需要修改:
         ICalc - 接口名字
         CcoDrawObj - COM对象类
         Calc - 接口名字删去I前缀

        4. 编译运行

  • 相关阅读:
    php7与其他版本共存
    centos源码安装mysql5.7
    禁用composer update命令
    lumen怎么得到当前Uri的控制器、Action、路由规则
    配置lumen的log为daily模式
    laravel如何打印orm封装的sql语句
    nginx 重写URL尾部斜杠
    Laravel的Nginx重写规则--让路由支持末尾加斜线
    laravel redis存数组并设置过期时间
    openresty
  • 原文地址:https://www.cnblogs.com/aoyihuashao/p/1983170.html
Copyright © 2011-2022 走看看