COM 与 ATL 及 C++ 的托管扩展的互操作性

来源:互联网 发布:网络教育考研究生 编辑:程序博客网 时间:2024/05/17 01:10
  1. 在本演练中,您将使用 ATL 创建一个 COM 组件,使用编译器中的 #import 语句支持写一个 COM 客户端程序来检验该组件的功能,然后使用 C++ 和 C# 的托管扩展向 .NET 客户端程序和 COM 客户端程序公开该组件。
  2. 本演练旨在说明以下内容: 
  3. 说明一个核心区域,在此区域内需要使用托管扩展,而不使用纯托管语言(如 Visual C#)。 
  4. 说明在使用 /clr 将现有代码编译为托管代码时,现有代码在语义上无任何改变。 
  5. 说明您可以在一个图像中将托管代码和非托管代码链接在一起,然后在托管和非托管代码之间进行调用。 
  6. 将使用 ATL 编写组件 ProdLookup,该组件支持产品说明的简单索引查找。该示例将使用字符串数组(而不使用数据库)查找产品说明。它将用只有一个方法的单个接口进行查找。
  7. 如果业务逻辑与 COM 逻辑分离,那么编写双模式组件(可直接由 COM 客户端程序和 .NET Framework 客户端程序调用)或将 COM 组件迁移到 .NET Framework 组件将容易得多。编写 Prodlookup 组件时将考虑到这一点。
  8. COM 客户端 COMProdClient 将使用 Visual C++ 编译器的 #import 支持编写 COM 客户端程序。与直接使用 COM API 所得到的结果相比,这将使引用的组件 ProdLookup 更加以 C++ 为中心。
  9. 然后,将向 ProdLookup 组件添加对 .NET Framework 客户端程序的直接支持,从而使之成为一个双模式组件。更改项目系统中的预编译头选项所需要的步骤是不可或缺的,这是因为无法在非托管代码和托管代码之间共享预编译头文件。对于更大的项目,您应添加一个新的预编译头供托管源文件使用。因为所有业务逻辑均已完全分离,所以添加此层时不需要改动任何 ATL 代码。
  10. 此后,您将创建一个 .NET Framework 客户端程序,该客户端程序将调用到 ProdLookup 的托管层中。您将看到,与 #import 为 COM 客户端程序减少编写接口代码需求相比,托管扩展客户端程序对编写接口代码需求的减少更进了一层。这显示了 .NET 主要优点之一:在各种语言之间的互操作性。
  11. 在最后一步中,您将生成一个 Visual C# .NET Framework 客户端程序。您将会看到它与托管扩展客户端程序非常相似。
  12. 使用业务对象 (ProdLookup.dll) 创建 ATL COM 服务器
  13. 在本步骤中,您将创建一个 ATL COM 服务器。
  14. 打开一个新解决方案 
  15. 在“文件”菜单上,单击“新建”,然后单击“项目”。 
  16. 将出现“新建项目”对话框。 
  17. 单击“项目类型”窗格中的“Visual C++ 项目”,然后单击“模板”窗格中的“ATL 项目”。 
  18. 在“名称”对话框中,输入“ProdLookup”。 
  19. 单击“确定”。 
  20. 出现“ATL 项目向导”。 
  21. 保留“ATL 项目向导”中的所有默认设置并单击“完成”。 
  22. 现在您将得到一个解决方案和一个名为 ProdLookup 的项目。
  23. 向项目中添加头文件 
  24. 在解决方案资源管理器中,右击“头文件”文件夹。从快捷菜单中单击“添加”,然后单击“添加新项”。 
  25. 将出现“添加新项”对话框。 
  26. 单击“头文件”图标,并在“名称”对话框中输入“Products.h”。 
  27. 单击“打开”添加该项。 
  28. 将下面的代码添加到 Products.h 中: 
  29. // Business logic class for product lookups
  30. #pragma once
  31. #include <windows.h>
  32. #include <stdio.h>
  33. namespace Products
  34. {
  35.    class ProductInfo
  36.    {
  37.    public:
  38.       static const MAXDESCRSZ = 30;
  39.       BOOL GetItemDescr( unsigned int nItemCode, 
  40.                          wchar_t wszProductDescr[MAXDESCRSZ] );
  41.    };
  42. }
  43. 向项目中添加源文件 
  44. 在解决方案资源管理器中,右击“源文件”文件夹。从快捷菜单中单击“添加”,然后单击“添加新项”。 
  45. 显示“添加新项”对话框。 
  46. 单击“C++ 文件”图标,并在“名称”对话框中输入“Products.cpp”。 
  47. 单击“打开”添加该项。 
  48. 将下面的代码添加到 Products.cpp 中: 
  49. #include "stdafx.h"
  50. #include "Products.h"
  51. namespace Products
  52. {
  53.    BOOL ProductInfo::GetItemDescr( 
  54.       unsigned int nItemCode, 
  55.       wchar_t wszProductDescr[MAXDESCRSZ] )
  56.    {
  57.       static const wchar_t *wszProductList[] = {L"Mahi mahi"
  58.                                                 L"Ahi", L"Ono"};
  59.       static const unsigned int nMaxProducts = 3;
  60.       if ( ( nItemCode > nMaxProducts ) || (nItemCode == 0) )
  61.       {
  62.          return FALSE;
  63.       }
  64.       wcscpy(wszProductDescr, wszProductList[nItemCode - 1]);
  65.       return TRUE;
  66.    }
  67. }
  68. 向项目中添加类 
  69. 在“视图”菜单上,单击“类视图”以切换到类视图。 
  70. 右击“ProdLookup”。在快捷菜单上单击“添加”,然后单击“添加类”。 
  71. 出现“添加类”对话框。 
  72. 单击“ATL 简单对象”,然后单击“打开”。 
  73. 出现“ATL 简单对象向导”。 
  74. 在“简称”框中,输入“FindProductInfo”。 
  75. 其他字段将自动填充。 
  76. 单击“完成”可创建所有必需的 COM 接口并关闭“ATL 简单对象向导”。 
  77. 修改代码并向项目中添加方法 
  78. 在解决方案资源管理器中,双击“FindProductInfo.h”,并在 #include resource.h 行后紧接着添加下面的 #include 语句: 
  79. #include "Products.h"
  80. 在类的 public 节的紧前面添加下面的私有成员。 
  81. private:
  82.    Products::ProductInfo* pProductInfo;
  83. 按下面的方法修改 CFindProductInfo 类构造函数以初始化 pProductInfo 指针: 
  84. CFindProductInfo( )
  85. {
  86.    pProductInfo = new Products::ProductInfo();
  87. }
  88. 在类视图中,展开 ProdLookup 节点并右击“IFindProductInfo”。 
  89. 在快捷菜单上单击“添加”,然后单击“添加方法”。 
  90. “添加方法向导”出现。 
  91. “返回类型”框中已自动输入“HRESULT”。在“方法名称”框中,输入“GetItemDescr”。 
  92. 在“参数类型”框中输入 LONG,在“参数名称”框中输入 nItemCode 并单击“添加”。 
  93. 在“参数类型”框中,输入 BSTR*,在“参数名”框中,输入 pbstrProductDescr 并单击“添加”。 
  94. 单击“完成”可关闭“添加方法向导”并生成方法。 
  95. 在解决方案资源管理器中,双击“FindProductInfo.cpp”,并像下面这样实现 GetItemDescr 方法。GetItemDescr 方法是以前添加的;请将现有代码替换为下面的代码: 
  96. STDMETHODIMP CFindProductInfo::GetItemDescr(LONG nItemCode, 
  97.                                             BSTR* pbstrProductDescr)
  98. {
  99.    using namespace Products;
  100.    wchar_t wcszProductDescr[ProductInfo::MAXDESCRSZ];
  101.    if (pProductInfo->GetItemDescr(nItemCode, wcszProductDescr)) {
  102.       *pbstrProductDescr = SysAllocString(wcszProductDescr);
  103.       return S_OK;
  104.    }
  105.    else {
  106.       return S_FALSE;
  107.    }
  108. }
  109. 生成解决方案以验证是否有代码错误。您此时已完成了业务对象的本机部分。
  110. 创建简单的 Win32 COM 客户端程序 (ComProdClient.exe)
  111. 在本步骤中,您将生成一个 Win32 客户端程序,以确保能够调用到 ATL COM 服务器中。
  112. 在现有解决方案中创建新项目 
  113. 在解决方案资源管理器中,右击该解决方案节点并在快捷菜单上单击“添加”。 
  114. 单击“新建项目”以向解决方案添加新项目。 
  115. 出现“添加新项目”对话框。 
  116. 单击“项目类型”窗格中的“Visual C++ 项目”,然后单击“模板”窗格中的“Win32 项目”。 
  117. 在“名称”框中,输入“COMProdClient”并单击“确定”。 
  118. “Win32 应用程序向导”随即出现。 
  119. 单击“应用程序设置”选项卡。 
  120. 对于“应用程序类型”,选择“控制台应用程序”。 
  121. 单击“完成”以接受这些设置并关闭“Win32 应用程序向导”。 
  122. 接下来,开发应用程序。
  123. 向 COMProdClient.cpp 中添加代码 
  124. 在解决方案资源管理器中,双击“COMProdClient.cpp”。 
  125. 将 COMProdClient.cpp 中的全部现有代码替换为以下代码: 
  126. // COMProdClient.cpp : Connect to the ProdLookup through COM
  127. #include "stdafx.h"
  128. #include "atlbase.h"
  129. #import "prodlookup.dll"
  130. // Start up COM when globals are constructed, shut down COM 
  131. // when globals are destructed
  132. struct StartUpCom
  133. {
  134.    StartUpCom()
  135.    {
  136.       CoInitialize(NULL); 
  137.    }
  138.    ~StartUpCom()
  139.    {
  140.       CoUninitialize();
  141.    }
  142. } _global_com_inst;
  143. int main(int argc, char* argv[])
  144. {
  145.    using namespace ProdLookup;
  146.    IFindProductInfoPtr pFindProductInfo(L"ProdLookup.FindProductInfo");
  147.    CComBSTR bstrItemDescr;
  148.    int nItemCode = 1;   // initialize the item code
  149.    while(pFindProductInfo->raw_GetItemDescr(nItemCode++, 
  150.                                             &bstrItemDescr) == S_OK)
  151.    {
  152.       // bstrItemDescr;
  153.       USES_CONVERSION;
  154.       printf("%s/n", W2A(bstrItemDescr));
  155.         SysFreeString(bstrItemDescr);
  156.    }
  157.    return 0;
  158. }
  159. 注意,如果使用 #import,则是在使用 ATL COM 服务器公开的类型库。此时必须添加正确的 #include 路径,以便使编译器能够找到 ProdLookup.dll。
  160. 使用“属性”对话框添加 #include 路径 
  161. 在解决方案资源管理器中,右击“COMProdClient”项目节点并单击快捷菜单上的“属性”。 
  162. 出现“COMProdClient 属性页”对话框。 
  163. 在“配置”下拉菜单中,单击“所有配置”。 
  164. 这样可确保在所有生成配置中均设置下面的属性更改。 
  165. 在左窗格中,单击“C/C++”文件夹,然后单击“常规”。 
  166. 在“附加包含目录”框中,输入下面一行: 
  167. $(SolutionDir)$(SolutionName)/$(ConfigurationName)
  168. 单击“确定”接受更改并关闭对话框。 
  169. 在解决方案资源管理器,右击“COMProdClient”并单击快捷菜单上的“设为启动项目”。 
  170. 在“生成”菜单上,单击“生成解决方案”。 
  171. 在“调试”菜单上,单击“开始执行(不调试)”以运行 COMProdClient.exe。 
  172. 您将看见下面的输出: 
  173. Mahi Mahi
  174. Ahi
  175. Ono
  176. 向 ATL 业务对象中添加 .NET Framework 支持
  177. 在本步骤中,您将编写包装,使托管代码能够使用传统型 COM 对象中的业务逻辑。
  178. 向 ProdLookup 项目中添加新的头文件 
  179. 在解决方案资源管理器中,右击 ProdLookup 项目的“头文件”文件夹。 
  180. 在快捷菜单上,单击“添加”,然后单击“添加新项”。 
  181. 将出现“添加新项”对话框。 
  182. 在“模板”窗格中,单击“头文件”;并在“名称”框中输入“MgdFindProductInfo.h”。 
  183. 单击“打开”添加头文件。 
  184. 将下面的代码添加到 MgdFindProductInfo.h 中: 
  185. #include "Products.h"
  186. #using <mscorlib.dll>
  187. using namespace System;
  188. public __gc class MgdFindProductInfo
  189. {
  190. private:
  191.    Products::ProductInfo* pProductInfo;
  192. public:
  193.    static const int MAXDESCRSZ = Products::ProductInfo::MAXDESCRSZ;
  194.    MgdFindProductInfo();   // ctor
  195.    BOOL GetItemDescr(int nItemCode, System::String** ppStrDescr);
  196. };
  197. 向 ProdLookup 项目中添加新的 C++ 文件 
  198. 在解决方案资源管理器中,右击“源文件”文件夹。 
  199. 在快捷菜单上,单击“添加”,然后单击“添加新项”。 
  200. 将出现“添加新项”对话框。 
  201. 在“模板”窗格中,单击“C++ 文件”,然后在“名称”框中输入 MgdFindProductInfo.cpp。 
  202. 单击“打开”添加源文件。 
  203. 将下面的代码添加到 MgdFindProductInfo.cpp 中: 
  204. #include "MgdFindProductInfo.h"
  205. MgdFindProductInfo::MgdFindProductInfo()
  206. {
  207.    pProductInfo = new Products::ProductInfo();
  208. }
  209. BOOL MgdFindProductInfo::GetItemDescr(int nItemCode, 
  210.                                       System::String** ppStr)
  211. {
  212.    wchar_t wszItemDescr[Products::ProductInfo::MAXDESCRSZ];
  213.    if (pProductInfo->GetItemDescr(nItemCode, wszItemDescr))
  214.    {
  215.       *ppStr = wszItemDescr;
  216.       return TRUE;
  217.    }
  218.    else
  219.    {
  220.       return FALSE;
  221.    }
  222. }
  223. 为 MgdFindProductInfo.cpp 指定项目设置 
  224. 在解决方案资源管理器中,右击“MgdFindProductInfo.cpp”源文件。 
  225. 在快捷菜单上单击“属性”。 
  226. 出现“mgdFindProductInfo.cpp 属性页”对话框。 
  227. 在“配置”下拉菜单上,选择“所有配置”。 
  228. 这确保了在所有生成配置上设置下列属性。 
  229. 在左窗格中,单击“C/C++”文件夹,然后单击“常规”属性页。 
  230. 在“编译为托管”字段中,单击“程序集支持 (/clr)”。 
  231. 这样就设置了使用 /clr 编译该文件。 
  232. 在“代码生成”属性页上,将“启用最小重新生成”设置为“否”,并将“基本运行时检查”设置为“默认”。 
  233. 单击“应用”以使这些更改生效。 
  234. 为 ProdLookup 指定项目设置 
  235. 在“mgdFindProductInfo.cpp 属性页”对话框仍处于打开状态时,单击解决方案资源管理器中 ProdLookup 的项目节点。 
  236. 在左窗格中,单击“C/C++”文件夹,然后单击“预编译头”属性页。 
  237. 将“创建/使用预编译头”属性设置为“不使用预编译头”。 
  238. 在左窗格中,仍在 C/C++ 文件夹下单击“常规”属性页。 
  239. 将“调试信息格式”属性设置为“程序数据库 (/Zi)”。 
  240. 单击“应用”以应用您的更改。 
  241. 为 Stdafx.cpp 指定项目设置 
  242. 在“属性页”对话框处于打开状态时,单击解决方案资源管理器中 ProdLookup 下的“Stdafx.cpp”文件。 
  243. 在左窗格中,单击“预编译头”属性页。 
  244. 将“创建/使用预编译头”属性设置为“不使用预编译头”。 
  245. 单击“确定”以应用您的更改并关闭“mgdFindProductInfo.cpp 属性页”对话框。 
  246. 此时您得到了一个混合模式的 DLL,它可以同时支持 COM 客户端程序和 .NET Framework 客户端程序,而 .NET Framework 客户端程序不必检查 COM 互操作性即可工作。为演示 .NET Framework 客户端程序的运行情况,下一步将创建使用 C++ 托管扩展的客户端程序。
  247. 创建简单的 C++ 托管扩展客户端程序 (MgdProdClient.exe)
  248. 在本步骤中,您将添加一个使用 C++ 托管扩展的客户端程序。
  249. 向解决方案中添加 C++ 托管扩展项目 
  250. 在解决方案资源管理器中,右击“ProdLookup”解决方案。 
  251. 在快捷菜单上单击“添加”,然后单击“新建项目”。 
  252. 出现“添加新项目”对话框。 
  253. 在“项目类型”窗格中,单击“Visual C++ 项目”,并在“模板”窗格中,单击“控制台应用程序 (.NET)”。 
  254. 在“名称”框中,输入“MgdProdClient”。 
  255. 单击“确定”以关闭“新建项目”对话框并添加 C++ 托管扩展应用程序。 
  256. 双击“MgdProdClient.cpp”并添加下面的代码,覆盖文件中的任何现有代码: 
  257. #using <mscorlib.dll>
  258. #using "prodlookup.dll"
  259. using namespace System;
  260. int main( )
  261. {
  262.    MgdFindProductInfo* pProductInfo = new MgdFindProductInfo();
  263.    int i = 1;
  264.    String* pStrDescr;
  265.    while (pProductInfo->GetItemDescr(i++, &pStrDescr))
  266.    {
  267.       Console::WriteLine(pStrDescr);
  268.    }
  269. }
  270. 现在修改项目,将 ProdLookup.dll 从生成它的当前位置复制到生成 MgdProdClient.exe 的同一位置。这样,当您运行 MgdProdClient.exe 时,MgdProdClient.exe 就能找到 ProdLook.dll。
  271. 修改项目生成 
  272. 在解决方案资源管理器中,右击“MgdProdClient”项目节点并单击快捷菜单上的“属性”。 
  273. 出现“MgdProdClient 属性页”对话框。 
  274. 在“配置”下拉列表中,单击“所有配置”。 
  275. 这确保了在所有生成配置上设置下列属性。 
  276. 在左窗格中,单击“C/C++”文件夹,然后单击“预编译头”属性页。 
  277. 将“创建/使用预编译头”属性设置为“不使用预编译头”。 
  278. 在左窗格中,单击“生成事件”文件夹,然后单击“预生成事件”属性页。 
  279. 将“命令行”属性设置为: 
  280. copy "$(SolutionDir)$(SolutionName)/$(ConfigurationName)/ProdLookup.dll" $(ConfigurationName)
  281. 在左窗格中,单击“C/C++”文件夹,然后单击“常规”属性页。 
  282. 将“解析 #using 引用”属性设置为: 
  283. $(outdir)
  284. 单击“确定”接受更改并关闭对话框。 
  285. 向托管客户端程序中添加引用 
  286. 在解决方案资源管理器中,右击“引用”节点并单击快捷菜单上的“添加引用”。 
  287. 出现“添加引用”对话框。 
  288. 单击“项目”选项卡,单击“ProdLookup”,然后单击“选择”将此引用添加到托管项目中。 
  289. 单击“确定”以关闭“添加引用”对话框。 
  290. 当您生成解决方案时,它将首先生成 ProdLookup.dll。在 MgdProdClient 项目生成之前,它将把生成的 DLL 复制到项目输出文件夹中,然后继续生成项目。
  291. 生成并运行解决方案 
  292. 在解决方案资源管理器中,右击“MgdProdClient”项目节点并单击快捷菜单上的“设为启动项目”。 
  293. 在“生成”菜单上,单击“生成解决方案”。 
  294. 在“调试”菜单上,单击“开始执行(不调试)”运行 MgdProdClient.exe。 
  295. 您将看见下面的输出: 
  296. Mahi Mahi
  297. Ahi
  298. Ono
  299. 创建简单的 Visual C# .NET Framework 客户端程序 (CSProdClient.exe)
  300. 在本步骤中,您将生成一个 Visual C# 控制台应用程序,它将调用到同一个混合模式的业务对象 DLL 中。
  301. 向解决方案中添加 Visual C# 控制台应用程序 
  302. 在解决方案资源管理器中,右击“ProdLookup”解决方案节点。 
  303. 在快捷菜单上单击“添加”,然后单击“新建项目”。 
  304. 出现“添加新项目”对话框。 
  305. 单击“项目类型”窗格中的“Visual C# 项目”,然后单击“模板”窗格中的“控制台应用程序”图标。 
  306. 在“名称”框中,输入“CSProdClient”。 
  307. 单击“确定”将该控制台应用程序添加到您的解决方案中。 
  308. 向 C# 控制台应用程序添加引用 
  309. 在解决方案资源管理器中,右击“引用”节点并单击快捷菜单上的“添加引用”。 
  310. 出现“添加引用”对话框。 
  311. 单击“项目”选项卡,单击“ProdLookup”,然后单击“选择”将此引用添加到 C# 项目中。 
  312. 单击“确定”以关闭“添加引用”对话框。 
  313. 将 Class1.cs 文件中的 Main 函数替换为以下代码: 
  314. public static int Main(string[] args)
  315. {
  316.       MgdFindProductInfo prodinfo = new MgdFindProductInfo();
  317.       String ItemDescr = "";
  318.       int i = 1;
  319.       while ( prodinfo.GetItemDescr(i++, ref ItemDescr) != 0)
  320.             {
  321.          Console.WriteLine(ItemDescr);
  322.       }
  323.         return 0;
  324. }
  325. Visual C# 项目系统与 C++ 托管扩展项目系统的不同之处在于:默认情况下将引用复制到输出目录中。在 Visual C# 中,您不必额外执行将 ProdLook.dll 复制到输出目录中这一步骤。
  326. 生成并运行解决方案 
  327. 在解决方案资源管理器中,右击“CSProdClient”并单击快捷菜单上的“设为启动项目”。 
  328. 在“生成”菜单上,单击“生成解决方案”。 
  329. 在“调试”菜单上,单击“开始执行(不调试)”来运行 CSProdClient.exe。 
  330. 您将看见下面的输出: 
  331. Mahi Mahi
  332. Ahi
  333. Ono
  334. 来源:msdn