COM 与 ATL 及 C++ 的托管扩展的互操作性
来源:互联网 发布:网络教育考研究生 编辑:程序博客网 时间:2024/05/17 01:10
- 在本演练中,您将使用 ATL 创建一个 COM 组件,使用编译器中的 #import 语句支持写一个 COM 客户端程序来检验该组件的功能,然后使用 C++ 和 C# 的托管扩展向 .NET 客户端程序和 COM 客户端程序公开该组件。
- 本演练旨在说明以下内容:
- 说明一个核心区域,在此区域内需要使用托管扩展,而不使用纯托管语言(如 Visual C#)。
- 说明在使用 /clr 将现有代码编译为托管代码时,现有代码在语义上无任何改变。
- 说明您可以在一个图像中将托管代码和非托管代码链接在一起,然后在托管和非托管代码之间进行调用。
- 将使用 ATL 编写组件 ProdLookup,该组件支持产品说明的简单索引查找。该示例将使用字符串数组(而不使用数据库)查找产品说明。它将用只有一个方法的单个接口进行查找。
- 如果业务逻辑与 COM 逻辑分离,那么编写双模式组件(可直接由 COM 客户端程序和 .NET Framework 客户端程序调用)或将 COM 组件迁移到 .NET Framework 组件将容易得多。编写 Prodlookup 组件时将考虑到这一点。
- COM 客户端 COMProdClient 将使用 Visual C++ 编译器的 #import 支持编写 COM 客户端程序。与直接使用 COM API 所得到的结果相比,这将使引用的组件 ProdLookup 更加以 C++ 为中心。
- 然后,将向 ProdLookup 组件添加对 .NET Framework 客户端程序的直接支持,从而使之成为一个双模式组件。更改项目系统中的预编译头选项所需要的步骤是不可或缺的,这是因为无法在非托管代码和托管代码之间共享预编译头文件。对于更大的项目,您应添加一个新的预编译头供托管源文件使用。因为所有业务逻辑均已完全分离,所以添加此层时不需要改动任何 ATL 代码。
- 此后,您将创建一个 .NET Framework 客户端程序,该客户端程序将调用到 ProdLookup 的托管层中。您将看到,与 #import 为 COM 客户端程序减少编写接口代码需求相比,托管扩展客户端程序对编写接口代码需求的减少更进了一层。这显示了 .NET 主要优点之一:在各种语言之间的互操作性。
- 在最后一步中,您将生成一个 Visual C# .NET Framework 客户端程序。您将会看到它与托管扩展客户端程序非常相似。
- 使用业务对象 (ProdLookup.dll) 创建 ATL COM 服务器
- 在本步骤中,您将创建一个 ATL COM 服务器。
- 打开一个新解决方案
- 在“文件”菜单上,单击“新建”,然后单击“项目”。
- 将出现“新建项目”对话框。
- 单击“项目类型”窗格中的“Visual C++ 项目”,然后单击“模板”窗格中的“ATL 项目”。
- 在“名称”对话框中,输入“ProdLookup”。
- 单击“确定”。
- 出现“ATL 项目向导”。
- 保留“ATL 项目向导”中的所有默认设置并单击“完成”。
- 现在您将得到一个解决方案和一个名为 ProdLookup 的项目。
- 向项目中添加头文件
- 在解决方案资源管理器中,右击“头文件”文件夹。从快捷菜单中单击“添加”,然后单击“添加新项”。
- 将出现“添加新项”对话框。
- 单击“头文件”图标,并在“名称”对话框中输入“Products.h”。
- 单击“打开”添加该项。
- 将下面的代码添加到 Products.h 中:
- #pragma once
- #include <windows.h>
- #include <stdio.h>
- namespace Products
- {
- class ProductInfo
- {
- public:
- static const MAXDESCRSZ = 30;
- BOOL GetItemDescr( unsigned int nItemCode,
- wchar_t wszProductDescr[MAXDESCRSZ] );
- };
- }
- 向项目中添加源文件
- 在解决方案资源管理器中,右击“源文件”文件夹。从快捷菜单中单击“添加”,然后单击“添加新项”。
- 显示“添加新项”对话框。
- 单击“C++ 文件”图标,并在“名称”对话框中输入“Products.cpp”。
- 单击“打开”添加该项。
- 将下面的代码添加到 Products.cpp 中:
- #include "stdafx.h"
- #include "Products.h"
- namespace Products
- {
- BOOL ProductInfo::GetItemDescr(
- unsigned int nItemCode,
- wchar_t wszProductDescr[MAXDESCRSZ] )
- {
- static const wchar_t *wszProductList[] = {L"Mahi mahi",
- L"Ahi", L"Ono"};
- static const unsigned int nMaxProducts = 3;
- if ( ( nItemCode > nMaxProducts ) || (nItemCode == 0) )
- {
- return FALSE;
- }
- wcscpy(wszProductDescr, wszProductList[nItemCode - 1]);
- return TRUE;
- }
- }
- 向项目中添加类
- 在“视图”菜单上,单击“类视图”以切换到类视图。
- 右击“ProdLookup”。在快捷菜单上单击“添加”,然后单击“添加类”。
- 出现“添加类”对话框。
- 单击“ATL 简单对象”,然后单击“打开”。
- 出现“ATL 简单对象向导”。
- 在“简称”框中,输入“FindProductInfo”。
- 其他字段将自动填充。
- 单击“完成”可创建所有必需的 COM 接口并关闭“ATL 简单对象向导”。
- 修改代码并向项目中添加方法
- 在解决方案资源管理器中,双击“FindProductInfo.h”,并在 #include resource.h 行后紧接着添加下面的 #include 语句:
- #include "Products.h"
- 在类的 public 节的紧前面添加下面的私有成员。
- private:
- Products::ProductInfo* pProductInfo;
- 按下面的方法修改 CFindProductInfo 类构造函数以初始化 pProductInfo 指针:
- CFindProductInfo( )
- {
- pProductInfo = new Products::ProductInfo();
- }
- 在类视图中,展开 ProdLookup 节点并右击“IFindProductInfo”。
- 在快捷菜单上单击“添加”,然后单击“添加方法”。
- “添加方法向导”出现。
- “返回类型”框中已自动输入“HRESULT”。在“方法名称”框中,输入“GetItemDescr”。
- 在“参数类型”框中输入 LONG,在“参数名称”框中输入 nItemCode 并单击“添加”。
- 在“参数类型”框中,输入 BSTR*,在“参数名”框中,输入 pbstrProductDescr 并单击“添加”。
- 单击“完成”可关闭“添加方法向导”并生成方法。
- 在解决方案资源管理器中,双击“FindProductInfo.cpp”,并像下面这样实现 GetItemDescr 方法。GetItemDescr 方法是以前添加的;请将现有代码替换为下面的代码:
- STDMETHODIMP CFindProductInfo::GetItemDescr(LONG nItemCode,
- BSTR* pbstrProductDescr)
- {
- using namespace Products;
- wchar_t wcszProductDescr[ProductInfo::MAXDESCRSZ];
- if (pProductInfo->GetItemDescr(nItemCode, wcszProductDescr)) {
- *pbstrProductDescr = SysAllocString(wcszProductDescr);
- return S_OK;
- }
- else {
- return S_FALSE;
- }
- }
- 生成解决方案以验证是否有代码错误。您此时已完成了业务对象的本机部分。
- 创建简单的 Win32 COM 客户端程序 (ComProdClient.exe)
- 在本步骤中,您将生成一个 Win32 客户端程序,以确保能够调用到 ATL COM 服务器中。
- 在现有解决方案中创建新项目
- 在解决方案资源管理器中,右击该解决方案节点并在快捷菜单上单击“添加”。
- 单击“新建项目”以向解决方案添加新项目。
- 出现“添加新项目”对话框。
- 单击“项目类型”窗格中的“Visual C++ 项目”,然后单击“模板”窗格中的“Win32 项目”。
- 在“名称”框中,输入“COMProdClient”并单击“确定”。
- “Win32 应用程序向导”随即出现。
- 单击“应用程序设置”选项卡。
- 对于“应用程序类型”,选择“控制台应用程序”。
- 单击“完成”以接受这些设置并关闭“Win32 应用程序向导”。
- 接下来,开发应用程序。
- 向 COMProdClient.cpp 中添加代码
- 在解决方案资源管理器中,双击“COMProdClient.cpp”。
- 将 COMProdClient.cpp 中的全部现有代码替换为以下代码:
- #include "stdafx.h"
- #include "atlbase.h"
- #import "prodlookup.dll"
- struct StartUpCom
- {
- StartUpCom()
- {
- CoInitialize(NULL);
- }
- ~StartUpCom()
- {
- CoUninitialize();
- }
- } _global_com_inst;
- int main(int argc, char* argv[])
- {
- using namespace ProdLookup;
- IFindProductInfoPtr pFindProductInfo(L"ProdLookup.FindProductInfo");
- CComBSTR bstrItemDescr;
- int nItemCode = 1;
- while(pFindProductInfo->raw_GetItemDescr(nItemCode++,
- &bstrItemDescr) == S_OK)
- {
-
- USES_CONVERSION;
- printf("%s/n", W2A(bstrItemDescr));
- SysFreeString(bstrItemDescr);
- }
- return 0;
- }
- 注意,如果使用 #import,则是在使用 ATL COM 服务器公开的类型库。此时必须添加正确的 #include 路径,以便使编译器能够找到 ProdLookup.dll。
- 使用“属性”对话框添加 #include 路径
- 在解决方案资源管理器中,右击“COMProdClient”项目节点并单击快捷菜单上的“属性”。
- 出现“COMProdClient 属性页”对话框。
- 在“配置”下拉菜单中,单击“所有配置”。
- 这样可确保在所有生成配置中均设置下面的属性更改。
- 在左窗格中,单击“C/C++”文件夹,然后单击“常规”。
- 在“附加包含目录”框中,输入下面一行:
- $(SolutionDir)$(SolutionName)/$(ConfigurationName)
- 单击“确定”接受更改并关闭对话框。
- 在解决方案资源管理器,右击“COMProdClient”并单击快捷菜单上的“设为启动项目”。
- 在“生成”菜单上,单击“生成解决方案”。
- 在“调试”菜单上,单击“开始执行(不调试)”以运行 COMProdClient.exe。
- 您将看见下面的输出:
- Mahi Mahi
- Ahi
- Ono
- 向 ATL 业务对象中添加 .NET Framework 支持
- 在本步骤中,您将编写包装,使托管代码能够使用传统型 COM 对象中的业务逻辑。
- 向 ProdLookup 项目中添加新的头文件
- 在解决方案资源管理器中,右击 ProdLookup 项目的“头文件”文件夹。
- 在快捷菜单上,单击“添加”,然后单击“添加新项”。
- 将出现“添加新项”对话框。
- 在“模板”窗格中,单击“头文件”;并在“名称”框中输入“MgdFindProductInfo.h”。
- 单击“打开”添加头文件。
- 将下面的代码添加到 MgdFindProductInfo.h 中:
- #include "Products.h"
- #using <mscorlib.dll>
- using namespace System;
- public __gc class MgdFindProductInfo
- {
- private:
- Products::ProductInfo* pProductInfo;
- public:
- static const int MAXDESCRSZ = Products::ProductInfo::MAXDESCRSZ;
- MgdFindProductInfo();
- BOOL GetItemDescr(int nItemCode, System::String** ppStrDescr);
- };
- 向 ProdLookup 项目中添加新的 C++ 文件
- 在解决方案资源管理器中,右击“源文件”文件夹。
- 在快捷菜单上,单击“添加”,然后单击“添加新项”。
- 将出现“添加新项”对话框。
- 在“模板”窗格中,单击“C++ 文件”,然后在“名称”框中输入 MgdFindProductInfo.cpp。
- 单击“打开”添加源文件。
- 将下面的代码添加到 MgdFindProductInfo.cpp 中:
- #include "MgdFindProductInfo.h"
- MgdFindProductInfo::MgdFindProductInfo()
- {
- pProductInfo = new Products::ProductInfo();
- }
- BOOL MgdFindProductInfo::GetItemDescr(int nItemCode,
- System::String** ppStr)
- {
- wchar_t wszItemDescr[Products::ProductInfo::MAXDESCRSZ];
- if (pProductInfo->GetItemDescr(nItemCode, wszItemDescr))
- {
- *ppStr = wszItemDescr;
- return TRUE;
- }
- else
- {
- return FALSE;
- }
- }
- 为 MgdFindProductInfo.cpp 指定项目设置
- 在解决方案资源管理器中,右击“MgdFindProductInfo.cpp”源文件。
- 在快捷菜单上单击“属性”。
- 出现“mgdFindProductInfo.cpp 属性页”对话框。
- 在“配置”下拉菜单上,选择“所有配置”。
- 这确保了在所有生成配置上设置下列属性。
- 在左窗格中,单击“C/C++”文件夹,然后单击“常规”属性页。
- 在“编译为托管”字段中,单击“程序集支持 (/clr)”。
- 这样就设置了使用 /clr 编译该文件。
- 在“代码生成”属性页上,将“启用最小重新生成”设置为“否”,并将“基本运行时检查”设置为“默认”。
- 单击“应用”以使这些更改生效。
- 为 ProdLookup 指定项目设置
- 在“mgdFindProductInfo.cpp 属性页”对话框仍处于打开状态时,单击解决方案资源管理器中 ProdLookup 的项目节点。
- 在左窗格中,单击“C/C++”文件夹,然后单击“预编译头”属性页。
- 将“创建/使用预编译头”属性设置为“不使用预编译头”。
- 在左窗格中,仍在 C/C++ 文件夹下单击“常规”属性页。
- 将“调试信息格式”属性设置为“程序数据库 (/Zi)”。
- 单击“应用”以应用您的更改。
- 为 Stdafx.cpp 指定项目设置
- 在“属性页”对话框处于打开状态时,单击解决方案资源管理器中 ProdLookup 下的“Stdafx.cpp”文件。
- 在左窗格中,单击“预编译头”属性页。
- 将“创建/使用预编译头”属性设置为“不使用预编译头”。
- 单击“确定”以应用您的更改并关闭“mgdFindProductInfo.cpp 属性页”对话框。
- 此时您得到了一个混合模式的 DLL,它可以同时支持 COM 客户端程序和 .NET Framework 客户端程序,而 .NET Framework 客户端程序不必检查 COM 互操作性即可工作。为演示 .NET Framework 客户端程序的运行情况,下一步将创建使用 C++ 托管扩展的客户端程序。
- 创建简单的 C++ 托管扩展客户端程序 (MgdProdClient.exe)
- 在本步骤中,您将添加一个使用 C++ 托管扩展的客户端程序。
- 向解决方案中添加 C++ 托管扩展项目
- 在解决方案资源管理器中,右击“ProdLookup”解决方案。
- 在快捷菜单上单击“添加”,然后单击“新建项目”。
- 出现“添加新项目”对话框。
- 在“项目类型”窗格中,单击“Visual C++ 项目”,并在“模板”窗格中,单击“控制台应用程序 (.NET)”。
- 在“名称”框中,输入“MgdProdClient”。
- 单击“确定”以关闭“新建项目”对话框并添加 C++ 托管扩展应用程序。
- 双击“MgdProdClient.cpp”并添加下面的代码,覆盖文件中的任何现有代码:
- #using <mscorlib.dll>
- #using "prodlookup.dll"
- using namespace System;
- int main( )
- {
- MgdFindProductInfo* pProductInfo = new MgdFindProductInfo();
- int i = 1;
- String* pStrDescr;
- while (pProductInfo->GetItemDescr(i++, &pStrDescr))
- {
- Console::WriteLine(pStrDescr);
- }
- }
- 现在修改项目,将 ProdLookup.dll 从生成它的当前位置复制到生成 MgdProdClient.exe 的同一位置。这样,当您运行 MgdProdClient.exe 时,MgdProdClient.exe 就能找到 ProdLook.dll。
- 修改项目生成
- 在解决方案资源管理器中,右击“MgdProdClient”项目节点并单击快捷菜单上的“属性”。
- 出现“MgdProdClient 属性页”对话框。
- 在“配置”下拉列表中,单击“所有配置”。
- 这确保了在所有生成配置上设置下列属性。
- 在左窗格中,单击“C/C++”文件夹,然后单击“预编译头”属性页。
- 将“创建/使用预编译头”属性设置为“不使用预编译头”。
- 在左窗格中,单击“生成事件”文件夹,然后单击“预生成事件”属性页。
- 将“命令行”属性设置为:
- copy "$(SolutionDir)$(SolutionName)/$(ConfigurationName)/ProdLookup.dll" $(ConfigurationName)
- 在左窗格中,单击“C/C++”文件夹,然后单击“常规”属性页。
- 将“解析 #using 引用”属性设置为:
- $(outdir)
- 单击“确定”接受更改并关闭对话框。
- 向托管客户端程序中添加引用
- 在解决方案资源管理器中,右击“引用”节点并单击快捷菜单上的“添加引用”。
- 出现“添加引用”对话框。
- 单击“项目”选项卡,单击“ProdLookup”,然后单击“选择”将此引用添加到托管项目中。
- 单击“确定”以关闭“添加引用”对话框。
- 当您生成解决方案时,它将首先生成 ProdLookup.dll。在 MgdProdClient 项目生成之前,它将把生成的 DLL 复制到项目输出文件夹中,然后继续生成项目。
- 生成并运行解决方案
- 在解决方案资源管理器中,右击“MgdProdClient”项目节点并单击快捷菜单上的“设为启动项目”。
- 在“生成”菜单上,单击“生成解决方案”。
- 在“调试”菜单上,单击“开始执行(不调试)”运行 MgdProdClient.exe。
- 您将看见下面的输出:
- Mahi Mahi
- Ahi
- Ono
- 创建简单的 Visual C# .NET Framework 客户端程序 (CSProdClient.exe)
- 在本步骤中,您将生成一个 Visual C# 控制台应用程序,它将调用到同一个混合模式的业务对象 DLL 中。
- 向解决方案中添加 Visual C# 控制台应用程序
- 在解决方案资源管理器中,右击“ProdLookup”解决方案节点。
- 在快捷菜单上单击“添加”,然后单击“新建项目”。
- 出现“添加新项目”对话框。
- 单击“项目类型”窗格中的“Visual C# 项目”,然后单击“模板”窗格中的“控制台应用程序”图标。
- 在“名称”框中,输入“CSProdClient”。
- 单击“确定”将该控制台应用程序添加到您的解决方案中。
- 向 C# 控制台应用程序添加引用
- 在解决方案资源管理器中,右击“引用”节点并单击快捷菜单上的“添加引用”。
- 出现“添加引用”对话框。
- 单击“项目”选项卡,单击“ProdLookup”,然后单击“选择”将此引用添加到 C# 项目中。
- 单击“确定”以关闭“添加引用”对话框。
- 将 Class1.cs 文件中的 Main 函数替换为以下代码:
- public static int Main(string[] args)
- {
- MgdFindProductInfo prodinfo = new MgdFindProductInfo();
- String ItemDescr = "";
- int i = 1;
- while ( prodinfo.GetItemDescr(i++, ref ItemDescr) != 0)
- {
- Console.WriteLine(ItemDescr);
- }
- return 0;
- }
- Visual C# 项目系统与 C++ 托管扩展项目系统的不同之处在于:默认情况下将引用复制到输出目录中。在 Visual C# 中,您不必额外执行将 ProdLook.dll 复制到输出目录中这一步骤。
- 生成并运行解决方案
- 在解决方案资源管理器中,右击“CSProdClient”并单击快捷菜单上的“设为启动项目”。
- 在“生成”菜单上,单击“生成解决方案”。
- 在“调试”菜单上,单击“开始执行(不调试)”来运行 CSProdClient.exe。
- 您将看见下面的输出:
- Mahi Mahi
- Ahi
- Ono
- 来源:msdn