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

编写简单的COM/ATL DLL的入门教程

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (41投票s)

2004年11月6日

CPOL

7分钟阅读

viewsIcon

387135

downloadIcon

3865

一篇涉及事件、方法和属性等简单问题的文章。

引言

在论坛讨论中,我遇到许多想编写COM/ATLDLL但不知道如何从组件创建属性、方法或引发事件的人,或者缺乏创建它们的根本知识。在这篇文章中,我将一步一步地使用VC++ 6.0编写一个简单的ATL DLL,并演示属性、方法和事件的创建与使用。

使用的约定

  • 符号‘-->‘表示作者注释。
  • 符号‘|‘表示菜单操作,例如,File|New表示点击文件菜单的新建项。

让我们开始

由于我写的是一篇入门文章,所以我包含了很多截图来解释COM DLL的创建过程,并将一步一步地向你解释。

  1. 打开Visual Studio 6.0,点击菜单项File|NEW,会弹出以下对话框。
    图 1: Visual Studio App Wizard
    Sample screenshot
  2. 现在选择ATL COM AppWizard,给项目起一个名字SimpleAtlCom,然后点击OK接受项目设置,在下一步你会看到这个对话框。
    图 2 ATL/COM App Wizard
    figure 2
  3. 点击Finish接受项目设置

    以上两个步骤将为你创建一个空的COM DLL项目

  4. 现在从菜单Insert|新内容 ATL Object... ,向项目中添加一个ATL对象。点击上述菜单项时,你会看到此图。
    图 3: ATL Object Wizard
    figure 3
  5. 选择Object|Simple对象,然后点击Next,接着你会看到这个属性页。请看下图。
    图 4: 选择SimpleAtl Object后的ATL Object Wizard
    figure4
  6. 在这里(图 4),将Short Name设置为SimpleObj,你将看到此对话框中的其他字段会自动生成。
    图 5 : 显示ATL对象的属性
    figure 5
  7. 在图 5中,我将解释一切。
    • 首先看到的是Threading Model;这里我选择了Compiler default,即apartment模式,因为大多数使用我们组件的其他应用程序都倾向于这种模式。
    • 第二看到的是Interface,我再次选择了Wizard Default的Dual模式(使用Dual interface的好处是,你也可以在脚本语言中使用它)。
    • Aggregationfree thread Marshaler这两个主题我们暂时不讲,因为它们对于本文来说过于高级。
    • 现在看ISupportErrorInfo,它用于向使用我们接口的客户端应用程序返回丰富的文本信息。由于我们不打算向客户端返回任何错误信息,所以我将此复选框保持未选中状态。
    • 最后但同样重要的是,要从组件调用事件,你需要ConnectionPoint接口的支持。所以勾选支持Connection Point的复选框,然后点击OK添加对象。
  8. 现在你的项目中会添加一个新的IDL文件(simpleAtlCom.idl)。这里,看一下IDL文件,它看起来是这样的
    // SimpleAtlCom.idl : IDL source for SimpleAtlCom.dll
    
    //
    
    // This file will be processed by the MIDL tool to
    
    // produce the type library (SimpleAtlCom.tlb) and marshalling code.
    
    import "oaidl.idl";
    
    import "ocidl.idl";
    
    // Above two File are define the IDispatch Inteface etc.
    
    [
    
    object,
    
    uuid(10CDF249-A336-406F-B472-20F08660D609),
    
    // Unique Id OF Object
    
    dual,
    
    // State our Interface is Dually Suported
    
    helpstring("ISimpleObj Interface"),
    
    pointer_default(unique)
    
    ]
    
    // Our Empty Interface
    
    interface ISimpleObj : IDispatch
    
    {
    
    };
    
    [
    
    uuid(8B1C3F79-07BA-44F8-8C47-AE2685488DFA),
    
    version(1.0),
    
    // our Library Name
    
    helpstring("SimpleAtlCom 1.0 Type Library")
    
    ]
    
    library SIMPLEATLCOMLib
    
    {
    
        importlib("stdole32.tlb");
    
        importlib("stdole2.tlb");
    
        [
    
        uuid(9B5BC0F8-7421-4C46-AA5F-539ECCAFCB82),
    
        helpstring("_ISimpleObjEvents Interface")
    
       ]
    
       // Disinterface Provides support for raising events,
       //as I already told you about that above
       dispinterface _ISimpleObjEvents
    
       {
    
           properties:
      
           methods:
     
        };
    
       [
    
       uuid(27BF0027-BECC-4847-AF91-99652BCE9791),
    
       helpstring("SimpleObj Class")
    
       ]
    
       // Our object base class where actual coding of our Property 
       //and Event resides
        coclass SimpleObj
    
        {
    
            [default] interface ISimpleObj;
    
             [default, source] dispinterface _ISimpleObjEvents;
    
        };
    
    };
  9. 现在让我们为接口添加属性和方法。让我们创建一个基于类应用程序的简单应用程序。无论如何,你知道什么是属性和方法吗?如果不知道,这里是它们的简要描述。方法是接口中函数的名称,属性是变量的名称。但请记住一点,COM/ATL中的一切都基于函数,属性和方法的一个主要区别是,你可以将属性设为只读(这意味着你只能从属性中获取数据,而不能写入)。现在,让我们回到我们的应用程序。
  10. 现在,在我们的接口中放入三个方法,以及Get和Put方法。你现在会问如何以及在哪里放入它们。右键点击你的接口。你将看到添加方法和属性的选项。为了清晰起见,让我们看这些图片。
    图 6: 显示弹出菜单 图 7: 显示属性向导
    figure 6 figure7
  11. 图 6显示了你可以从何处将属性和方法添加到你的接口。现在,如图 7所示,你可以添加三个属性:NameATLMarksComMarks,以及一个方法Calculate。至于触发,我将在最后告诉你。添加之后,你的接口看起来是这样的
    interface ISimpleObj : IDispatch
    // Our base interface
    {
    
      [propget, id(1), helpstring("property Name")] 
           HRESULT Name([out, retval] BSTR *pVal);
    
      [propput, id(1), helpstring("property Name")] 
           HRESULT Name([in] BSTR newVal);
    
      [propget, id(2), helpstring("property ATLMarks")] 
           HRESULT ATLMarks([out, retval] short *pVal);
    
      [propput, id(2), helpstring("property ATLMarks")] 
           HRESULT ATLMarks([in] short newVal);
    
      [propget, id(3), helpstring("property COMMarks")] 
           HRESULT COMMarks([out, retval] short *pVal);
    
      [propput, id(3), helpstring("property COMMarks")] 
           HRESULT COMMarks([in] short newVal);
    
      [id(4), helpstring("method Calculate")] 
          HRESULT Calculate();
    
    };
  12. 现在你将在你的类CSimpleObj中看到每个属性和方法的函数。在实际编码之前,我想你应该了解上面接口中的propgetpropput method。
    • Propget – 代表用于从组件获取属性值的属性。
    • PropPut–代表用于将属性写入组件的属性。这可以是可选的,如果你删除它,那么你的属性将变成只读。
    • Method—一个简单的用于执行某些计算的函数。
    • [in] --- 表示数据正在传入,或者你正在将某个值赋给组件。
    • [out,retval] --- 这个表示法指出使用此参数的参数将返回数据。
    • HRESULT --- 标准错误报告变量。
  13. 现在,在类中添加一些有用的变量来处理上述属性。在你的CSimpleObj类中添加char Name[100]short AtlMarksshort COMMarks,其余的简单类代码我已为你写好。让我们看看并解释我包含的每个函数。还有一个属性viz total,它将返回总数。我们仍然没有实现Events

    这是我们的SimpleObj类代码。

    STDMETHODIMP CSimpleObj::get_Name(BSTR *pVal)
    {
         // return Name of Student
         CComBSTR bstStr(this->Name);
         *pVal=bstStr.Detach();
         return S_OK;
    }
    
    STDMETHODIMP CSimpleObj::put_Name(BSTR newVal)
    {
         // put Name of Student
    
         ::wcstombs(this->Name,newVal,99);
         return S_OK;
    }
    
    STDMETHODIMP CSimpleObj::get_ATLMarks(short *pVal)
    {
         //return ATL marks
    
        *pVal=this->ATLMarks;
    
        return S_OK;
    }
    
    STDMETHODIMP CSimpleObj::put_ATLMarks(short newVal)
    {
        // return Put of marks of atl
    
        this->ATLMarks=newVal;
    
        return S_OK;
    }
    
    STDMETHODIMP CSimpleObj::get_COMMarks(short *pVal)
    {
        // get the marks for COM
    
        *pVal=this->COMMarks;
        return S_OK;
    }
    
    STDMETHODIMP CSimpleObj::put_COMMarks(short newVal)
    {
        //put marks for COM
    
        this->COMMarks=newVal;
        return S_OK;
    }
    
    STDMETHODIMP CSimpleObj::get_Total(short *pVal)
    {
        //return total number of marks
        *pVal=this->m_iTotalMarks;
        return S_OK;
    }
    
    STDMETHODIMP CSimpleObj::Calculate()
    {
        // Calculate total number of marks and store it total number variable
        this->m_iTotalMarks=this->ATLMarks+this->COMMarks;
        return S_OK;
    }
    
  14. 现在使用BUILD|BUILD SimpleATLCom.dll编译和构建SimpleAtlCom.dll,我想你已经成功地得到了一个SimpleATLCom.dll
  15. 现在为它开发一个简单的Visual Basic项目。我已经为上面的组件创建了一个示例UI,让我们看一看。
    图 8: 上面COM DLL的Visual Basic界面
    figure8
  16. 现在让我们进入编码部分,首先添加引用我们的com DLL到项目中。你可以在PROJECT|REFRENCES of Visual Basic IDE中找到选项。点击后,你会看到一个类似的对话框,在其中找到我们的SimpleAtlCom库并勾选它旁边的复选框。这将引用该DLL到你的项目中(这与在C++项目中包含头文件类似),然后按OK。对话框应用程序看起来是这样的。
    图 9 : Visual Basic IDE的Reference对话框
    figure9
  17. 现在,让我们看看VB应用程序的后端代码。
    'Our Component Object
    Private Obj As SIMPLEATLCOMLib.SimpleObj
    
    Private Sub cmdPutValue_Click()
    
    'give memory to Com object
    Set Obj = New SIMPLEATLCOMLib.SimpleObj
    
    'put atl marks in component
    Obj.ATLMarks = txtPutATL
    
    'put com marks
    Obj.COMMarks = Me.txtPutCom
    
    'put Name
    Obj.Name = Me.txtPutName
    
    End Sub
    
    Private Sub cmdGetValue_Click()
     'calculate the marks
      Obj.Calculate
    
     'put atl marks in component
      Me.txtGetAtl = Obj.ATLMarks
    
     'put com marks
      Me.txtGetCom = Obj.COMMarks
    
     'put Name
      Me.txtGetName = Obj.Name
    
     'get total marks and display it on the Component
      Me.txtTotalMarks = Obj.Total
    
    End Sub
  18. 现在,在处理事件之前,让我们先看看应用程序的测试运行。这里是
    图 10: 我们的COM DLL的第一次运行
    figure 10
  19. 我想,到目前为止没有人遇到任何问题。我上面显示的结果。现在,让我们看看事件以及如何创建和触发它们。你可以看到一个名为_ISimpleObjEvents的接口。这是APP Wizard创建的,它提供了对事件的支持。你可以看到一个前缀‘_’(下划线)。这个下划线通知MIDL(Microsoft IDL compiler)这个接口是disinterface,不要将其包含在TLB文件中。现在右键点击这个接口,添加一个方法,例如,Void TOTAL([in]short marks),如下图所示
    图 11: 向DisInterface添加方法
    figure 11
  20. 现在右键点击你的CSimpleObj类,然后点击Implement Connection Point选项。你会看到这个图,勾选_ISimpleObjEvents复选框,然后点击OK。瞧!CProxy_ISimpleObjEvents类已添加到你的项目中。这是为你触发事件的代理类。你可以看到,它包含函数VOID Fire_TotalMarks(SHORT TotalMarks),这是一个代理函数,用于触发事件。哦!我忘了,看看Connection Point实现图。
    图 12: 添加Connection Point
    figure 12
  21. 现在事件已添加到你的类中。让我们修改CSimpleObj::Calculate()

    现在新的CSimpleObj::Calculate()看起来是这样的

    STDMETHODIMP CSimpleObj::Calculate()
    {
    
         //add Marks
        this->m_iTotalMarks=this->ATLMarks + this->COMMarks;
    
        // when you use this pointer ,Fire_Totalmarks support 
        //is added to your class
        // after you implement the Connection points 
        // This event will fire the Total though event to Client
        
        this->Fire_TotalMarks(this->m_iTotalMarks);
    
        return S_OK;
    }
  22. 现在让我们修改Visual Basic应用程序来处理事件。

    更改

    Private Obj As SIMPLEATLCOMLib.SimpleObj

    to

    Private withevents Obj As SIMPLEATLCOMLib.SimpleObj

    这有助于我们实现事件,看看这个图以获得更清晰的理解。

    图 14: Visual Basic,向项目中添加事件
    figure 14
  23. 现在看看事件实现的代码,因为TotalMarks来自事件,所以只需添加一行来显示总分数。因此,我们的事件代码看起来是这样的
    Private Sub Obj_TotalMarks(ByVal TotalMarks As Integer)
        'Display the MessageBox displaying Total Marks
        MsgBox "total marks " & TotalMarks
    
    End Sub
    
  24. 现在测试并运行应用程序。这里是显示事件的消息框。
    图 15: 我们功能齐全的DLL和应用程序
    figure 15

下载包含的代码

源代码包括

  1. SimpleAtlCom.dll (含源代码)。
  2. Visual Basic中的测试项目。

测试项目包含

  1. 已编译的测试应用程序。
  2. 已编译的SimpleAtlCom.dll

演示应用程序的使用

如果你愿意,请先使用COM DLL和测试应用程序。不要忘记在你的计算机上注册COM DLL,即SimpleAtlCom.dll。你可以使用此命令行来注册该组件。

Drive:> %sys%regsvr32 SimpleAtlCom.dll

作者评论

我已尽我最大的努力来讲述COM DLL的每一个简单方面。如果遗漏了什么,请随时与我联系。

特别感谢

  • 致我的父母。
  • CodeProject.com,它为程序员互动提供了平台。
© . All rights reserved.