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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (30投票s)

2012年12月8日

CPOL

9分钟阅读

viewsIcon

169327

downloadIcon

5534

文章涉及属性、方法、事件等简单编程工具,以及如何从 ATL/COM 组件调用 ATL 对话框。

引言

来自上一篇 文章在论坛的讨论中,我遇到很多人想写 COM/ATL DLL,但不知道如何从组件创建属性、方法或引发事件,或者缺乏创建它们的根本知识。对于他们,我写了这篇文章,使用的是Visual C++ 6.0。由于 Visual Studio 6 已不再支持,许多初学者要求我用Visual Studio 2005/2008重写这篇文章,因为两者在 GUI 上有很大差异。

因此,我使用 Visual Studio 2005 呈现了相同的文章,并且这是一篇新文章,我必须为读者提供一些新的内容,否则他们会拒绝这篇文章。.NET 应用程序正在慢慢蚕食 Visual C++ 的应用程序份额。为了顺应广大读者的要求,我将展示如何在 .NET 应用程序中使用 COM 组件,而不是非托管应用程序。由于这是一篇入门文章,我在文章中包含了一些额外的截图。请不要因此而指责我使用相同的示例。”

现在,这篇文章是关于在Visual Studio 2012(感谢微软!)中创建 COM\ATL 组件的。与之前的版本相比,UI 发生了巨大变化,并且增加了许多新功能。尽管我还在研究其功能。我很期待添加一个关于其功能的技巧。最初我安装 VS2012 是为了查看 MS 对 C++11 标准的实现,因为我希望能亲眼看到一些新标准的工作,但这并不是今天的议题。

随着新版 Visual Studio 的推出,微软已决定停止支持托管 C++ 中的新窗口窗体,并且 C++/CLI 仅用于互操作(这意味着什么?非托管 C++ 还有未来吗?)。此外,随着 .NET 的发展,COM 技术正日益失去光彩,但我们将把灰暗的图景放在一边,专注于文章的主题。既然 MS 仍在支持它,那么隧道另一边仍有光明。

目录 

  • 创建 ATL 组件
  • 在 Visual C# 应用程序(.NET 2012)中使用它
  • 创建 ATL 对话框并在 C#.Net 2012 中使用它new 

创建 ATL 组件 

让我们一步一步地在 Visual Studio 2012 中创建一个 ATL 组件

  1. 打开 Visual Studio 2012,然后点击文件 | 新建 | 项目菜单,这将显示如下文件-保存对话框,您可以在其中为新项目命名。我在这里将名称设置为 SimpleATLCom,建议选择要创建项目的目标文件夹,否则它会弄乱您的文档文件夹。按OK确认更改。 这将创建一个空白的 ATL 组件。注意:由于我使用的是 Windows 7 操作系统,您必须以管理员模式启动 VS2012,可以通过右键单击 VS2012 来完成  

    图 1:文件打开对话框

    如果您还记得早期版本,曾经有三四个项目模板,现在似乎合并成了一个。接下来,在(ATL 项目向导)对话框中,选择默认设置,然后按“完成”。如果您需要 MFC 支持,请勾选“支持 MFC”复选框。我在这里看到了“安全开发生命周期 (SDL)”复选框,作为 CSSLP 的学生,我可以说是添加得很好。

    图 2:ATL 项目向导

    现在在项目中插入 ATL 对象,我将在下一步中展示。

  2. 选择项目 | 添加类。将出现以下对话框,正如预期的那样,它也添加了一些新模板。

    图 3:添加简单 ATL 对象

    选择ATL | ATL 简单对象,然后按添加按钮

  3. ATL 简单对象向导中输入新的接口名称。当您开始输入接口名称时,您会看到其余的详细信息由 Visual Studio 开发环境自动填充。另外,在“选项”选项卡中勾选“连接点”复选框,这使组件能够触发事件。另外,为了错误消息支持,您可以选择勾选“ISupportErrorInfo”复选框。

    图 4:ATL 简单对象

    图 5:选择连接点复选框
  4. 现在,如果您检查以下 IDL(接口定义语言),它将被生成并包含在您的项目中。

       // SimpleATLCom.idl : IDL source for SimpleATLCom
    //
    
    // 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";
    
    [
    	object,
    	uuid(06598046-EBCA-424A-9F6A-4C01DBE17C8A), ------(1)
    	dual,
    	nonextensible,
    	pointer_default(unique)
    ]
    interface ISimpleCom : IDispatch{
    };
    [
    	uuid(84E5C77E-58FA-4D2D-A00F-73E319EF601E), -------(2)
    	version(1.0),
    ]
    library SimpleATLComLib
    {
    	importlib("stdole2.tlb");
    	[
    		uuid(67652A2B-278A-4B84-9F61-65F899A1004F)		
    	]
    	dispinterface _ISimpleComEvents  -------(3)
    	{
    		properties:
    		methods:
    	};
    	[
    		uuid(435356F9-F33F-403D-B475-1E4AB512FF95)		
    	]
    	coclass SimpleCom
    	{
    		[default] interface ISimpleCom;
    		[default, source] dispinterface _ISimpleComEvents;
    	};
    };
    • (1):这是开发环境生成的默认 UUID(唯一 ID),这使得组件具有唯一的可识别性。
    • (2):这就是我们的组件名称和 UUID,它的内容可用于唯一标识组件,您可以使用此 ID 来创建组件。
    • (3):我们的 dis-interface,即事件函数。
  5. 现在使用“添加属性”和“添加方法”向导添加属性和方法。这可以在类视图中找到,右键单击ISimpleCom接口,然后选择添加->添加方法或添加属性:

    图 6:添加方法/添加属性

    像这样添加一个名为 Calculate 的方法,其中 BOOL a_bFireEvent 作为IN,Long* a_lTotalMarks 作为OUT, RETVAL参数。

    图 7:添加方法

    像示例中所示,添加一个类型为 Long 的属性 ComMark。

    图 8:添加属性

    同样,请自己添加类型为 long 的属性AtlMarks和类型为BSTRStudentName。抱歉,我忘记提到参数中的 IN、OUT 和 RETVAL 类型。

    • Propget – 代表用于从组件获取属性值的属性
    • PropPut – 代表用于将属性值放入组件的属性。这可以是可选的,如果您删除它,则可以将属性设置为只读
    • Method — 一个简单的函数,用于执行某些计算
    • [in] - 表示数据正在输入,或者您正在将某些值放入组件
    • [out,retval] - 符号表示使用此参数的参数将返回数据
    • HRESULT - 标准错误报告变量
  6. 现在,以几乎相同的方式添加事件,我们向接口添加方法。现在右键单击_ISimpleComEvents,然后选择“添加 | 添加方法”。

    图 9:添加事件
  7. 一切完成后,修改后的 IDL 文件将如下所示:

     
                 
    import "oaidl.idl";
    import "ocidl.idl";
    
    [
    	object,
    	uuid(06598046-EBCA-424A-9F6A-4C01DBE17C8A),
    	dual,
    	nonextensible,
    	pointer_default(unique)
    ]
    interface ISimpleCom : IDispatch{
    	[id(1)] HRESULT Calculate([in] VARIANT_BOOL a_bFireEvent, [out,retval] LONG* a_lTotalMarks);
    	[propget, id(2)] HRESULT ComMarks([out, retval] LONG* pVal);
    	[propput, id(2)] HRESULT ComMarks([in] LONG newVal);
    	[propget, id(3)] HRESULT AtlMarks([out, retval] LONG* pVal);
    	[propput, id(3)] HRESULT AtlMarks([in] LONG newVal);
    	[propget, id(4)] HRESULT StudentName([out, retval] BSTR* pVal);
    	[propput, id(4)] HRESULT StudentName([in] BSTR newVal);
    };
    [
    	uuid(84E5C77E-58FA-4D2D-A00F-73E319EF601E),
    	version(1.0),
    ]
    library SimpleATLComLib
    {
    	importlib("stdole2.tlb");
    	[
    		uuid(67652A2B-278A-4B84-9F61-65F899A1004F)		
    	]
    	dispinterface _ISimpleComEvents
    	{
    		properties:
    		methods:
    			[id(1)] HRESULT TotalMarks(LONG a_lTotalMarks);
    	};
    	[
    		uuid(435356F9-F33F-403D-B475-1E4AB512FF95)		
    	]
    	coclass SimpleCom
    	{
    		[default] interface ISimpleCom;
    		[default, source] dispinterface _ISimpleComEvents;
    	};
    };
  8. 现在,在执行上述操作之前,请先编译。要在主接口中添加连接点,请右键单击 CSimpleCom,然后点击添加 | 添加连接点。将出现以下截图。

    图 10:选择添加连接点

    选择“>”按钮将 _ISimpleComEvents 源接口移至实现连接点,然后点击“完成”。一个名为 Fire_TotalMarks 的新函数将被添加到 CSimpleCom 类中,可用于引发事件。 请记住在“可用类型库”中选择 SimpleATLcomLib,在我电脑上我需要重复此步骤两次,我不知道原因。

    图 11:添加连接向导
  9. 现在,为CSimpleCom类中的所有属性和函数编写代码,即为ComMarksATLMarksStudentName添加类变量以存储来自外部的信息。编码完成后,我们的类将如下所示:

               STDMETHODIMP CSimpleCom::InterfaceSupportsErrorInfo(REFIID riid)
    {
    	static const IID* const arr[] = 
    	{
    		&IID_ISimpleCom
    	};
    
    	for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)
    	{
    		if (InlineIsEqualGUID(*arr[i],riid))
    			return S_OK;
    	}
    	return S_FALSE;
    }
    
    STDMETHODIMP CSimpleCom::Calculate(VARIANT_BOOL a_bFireEvent, LONG* a_lTotalMarks)
    {
    	*a_lTotalMarks = m_lATLMarks + m_lComMarks;
    	if(a_bFireEvent == VARIANT_TRUE)
    		Fire_TotalMarks(*a_lTotalMarks);
    
    	return S_OK;
    }
    
    STDMETHODIMP CSimpleCom::get_ComMarks(LONG* pVal)
    {
    	*pVal= m_lComMarks;
    	return S_OK;
    }
    
    STDMETHODIMP CSimpleCom::put_ComMarks(LONG newVal)
    {
    	m_lComMarks = newVal;
    	return S_OK;
    }
    
    STDMETHODIMP CSimpleCom::get_AtlMarks(LONG* pVal)
    {
    	*pVal= m_lATLMarks;
    	return S_OK;
    }
    
    STDMETHODIMP CSimpleCom::put_AtlMarks(LONG newVal)
    {
    	m_lATLMarks = newVal;
    	return S_OK;
    }
    
    STDMETHODIMP CSimpleCom::get_StudentName(BSTR* pVal)
    {
    	*pVal = m_bstStudName.Copy();
    	return S_OK;
    }
    
    STDMETHODIMP CSimpleCom::put_StudentName(BSTR newVal)
    {
    	m_bstStudName = newVal;
    	return S_OK;
    }
    

    现在生成解决方案,构建过程完成后,您的 DLL 将自动注册。

在 Visual C# 应用程序中使用它

现在,让我们一步一步地创建一个 C#.NET 项目,并在项目中包含 COM 组件的支持。

  1. 在此解决方案中添加一个新的 C# 窗口应用程序项目(SimpleATLcomTest1),然后单击以接受默认配置来创建测试项目。
    图 12:添加新的 C# 项目
  2. 设计界面,如下面的截图所示,添加三个Textbox控件以将值输入到组件中,两个框以检索所有值,包括计算后的值,以及一个复选框以通过事件而非同步获取数据。

    图 13:测试应用程序设计
  3. 现在,通过右键单击项目名称并选择“添加引用”菜单项,为您的项目添加对 SimpleATLCom.dll 的引用。

    图 14:添加引用菜单

    点击“COM”选项卡项,选择SimpleATLCom.dll,将组件的引用添加到项目中。

    图 15:添加引用
  4. 现在,编写以下代码以在您的项目中 Ya使用该组件。

    using SimpleATLComLib;
    
    namespace SimpleATLComTest1
    {
        public partial class FrmSimpleATLComTest1 : Form
        {
            SimpleATLComLib.SimpleCom objSimpleObj = null;
            int _totalMarks = 0;
    
            public FrmSimpleATLComTest1()
            {
                InitializeComponent();
                objSimpleObj = new SimpleCom();
                objSimpleObj.TotalMarks += objSimpleObj_TotalMarks;
            }
    
            void objSimpleObj_TotalMarks(int a_lTotalMarks)
            {
                string txtMsg = string.Format(" {0} got  {1} Marks", txtName.Text, _totalMarks);
                MessageBox.Show(txtMsg);
            }
    
            private void btnCalculate_Click(object sender, EventArgs e)
            {
                objSimpleObj.AtlMarks = int.Parse(txtATLMarks.Text);
                objSimpleObj.ComMarks = int.Parse(txtComMarks.Text);
                
                bool fireEvent = chkFireEvent.Checked;
                _totalMarks = objSimpleObj.Calculate(fireEvent);
    
                txtResultName.Text = txtName.Text;
                txtMarks.Text = _totalMarks.ToString();
            }
        }
    } 

    VS2012 中的代码生成有了很大的改进,使用 COM 组件现在非常容易。作为参考,使用using关键字添加import SimpleATLComLib,并为类对象的 TotalMarks 事件添加事件处理程序。

  5. 运行测试应用程序,您将看到结果。

     
    未触发事件 触发事件

    图 16:运行应用程序

    选中“FireEvent”复选框后,“true”参数被传递到 ATL Dll,然后从 COM DLL 调用 TotalMarks 事件。我们在委托中捕获它。

创建 ATL 对话框并在 C#.Net 2012 中使用它 new 

本文中编写 ATL 对话框支持的灵感来自于编程论坛,最近我正在讨论通过 ATL-COM DLL 调用 ATL 对话框的前景。所以,这是我们自己的 ATLDialog 实现,它将接收输入并计算所有分数。

这里我演示的是不带事件接收器的情况,因为实现事件本身是一个大话题,我不想把事情搞得太复杂。

  1. 在我们的 SimpleATLCom dll 中添加一个新类,类型为 ATLDialog,选择“项目->添加类”,
    图 17:添加 ATL 对话框类

    并将其命名为 SimpleATLDialog。

    图 18:添加 ATL 对话框类
  2. 现在,设计一个类似于 C# 应用程序的界面,设计完成后,对话框看起来像这样:-
    图 19:ATL 对话框界面设计
  3. 添加以下代码来处理对话框中的事件。
    #include "SimpleCom.h"
    // CSimpleATLDialog
    
    LRESULT CSimpleATLDialog::OnBnClickedButtonCalculate(WORD /*wNotifyCode*/,
     WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
    	
    	int lATLMarks = this->GetDlgItemInt(IDC_EDIT_ATLMARKS);
    	int lCOMMarks = this->GetDlgItemInt(IDC_EDIT_COMMARKS);
    	TCHAR strName[100];
    	GetDlgItemText(IDC_EDIT_NAME,strName,250);
    	CComObject<CSimpleCom>* simpleComObj;
    	HRESULT hRes = CComObject<CSimpleCom>::CreateInstance(&simpleComObj);
    
    	if(SUCCEEDED(hRes))
    	{
    		long totalMarks =0;
    		simpleComObj->put_AtlMarks(lATLMarks);
    		simpleComObj->put_ComMarks(lCOMMarks);
    		simpleComObj->Calculate(VARIANT_FALSE,&totalMarks);
    
    		TCHAR strMsg[150];
    		wsprintf(strMsg,_T("%s got following Marks %d"),strName,totalMarks);
    
    		MessageBox(strMsg);
    
    	}
    	else
    	{
    		MessageBox(_T("Initialization Failed"));	
    	}
    
    	return 0;
    }
       
    Here I am retrieving the ATLMarks and COMMarks from dialog text boxes and the using CComObject Creating the SimpleATLCom object (since later is the com class) and passing all the values to created object with event firing equal to false.
       
    
        
  4. 添加一个单独的简单 ATL 对象 SimpleATLDLGController,并向其添加一个 InvokeDialog 方法。完成后,新接口的代码将如下所示。
       #include "SimpleATLDialog.h"
    // CSimpleATLDLGController
    
    STDMETHODIMP CSimpleATLDLGController::InvokeDialog(void)
    {
    	CSimpleATLDialog atlDLg;
    	atlDLg.DoModal();
    	return S_OK;
    }
    
       
  5. 重新设计我们之前的 C# 项目,包含一个新按钮,我们将通过该按钮处理 SimpleATLDLGController 的 InvokeDialog 方法。
    图 20:C# 窗体界面设计
    按钮点击的代码如下:-
          private void btnInvokeDLG_Click(object sender, EventArgs e)
            {
                SimpleATLComLib.SimpleATLDLGController simpleCTRLDLG = new SimpleATLDLGController();
                simpleCTRLDLG.InvokeDialog();
    
            }
        
  6. 简单地运行您的应用程序。
    图 20:应用程序演示 --- 2

关于下载代码

源代码包括

  • SimpleATLCom Test1 项目:包含基本项目
  • SimpleATLCom Test2 项目:包含基本项目以及 ATL 对话框项目

演示应用程序的使用

如果您喜欢,请先使用Com DLL和测试应用程序。别忘了注册 Com DLL,即SimpleAtlCom.dll,到您的计算机上。您可以使用此命令行来注册组件。对于 Windows Vista 及更高版本,您必须以管理员模式启动 cmdline。

Drive:> %sys%regsvr32 path_to_dll\SimpleAtlCom.dll  

作者评论

我已尽我所能讲解 COM DLL 的每一个简单方面。如果有什么遗漏,请随时联系我或在下面的讨论论坛中留下您宝贵的评论。

特别鸣谢 

  • 感谢我的母亲(已故)和父亲,当然还有我的妻子 
  • 感谢CodeProject.com,它为程序员提供了互动平台。

本系列的其他文章

历史 

  • 2012 年 12 月 9 日:初次发布 
© . All rights reserved.