在 MFC 应用程序中使用 COM 感知的 DotNet 库
关于在非托管 VC++ 应用程序中使用 C# DLL 的文章。
引言
最近(即昨天),我发表了一篇关于创建 **COM/ATL** 组件并在 **.NET 应用程序**中使用它的文章。这时我脑海里闪过一个念头,为什么不能在非托管的 VisualCpp 应用程序中使用 .NET 库呢?像往常一样,我开始在互联网上搜索(开发者思维,哼!J),在那里我找到了一些关于开始这个话题的有用技巧。
虽然我不是一个经验丰富的 .NET 开发者,但我能够很快地创建 .NET 库,但当涉及到与非托管应用程序的集成时,我真的费尽心思来集成它,有时 COM 对象无法创建,有时导出的文件不包含暴露函数的信息。在这里,这篇文章中我将分享我从以上经验中学到的一切。
目录
- 创建 COM 感知的 C#.Net 类库
- 在 Visual C++ MFC 应用程序中使用它
- 快速回顾
创建 C#.Net 类库
让我们一步一步来创建 C# 类库- 打开开发环境(Visual Studio 2005),点击 *文件 | 新建 | 项目*,创建一个 *C# 类库* 项目,如下面的示例所示:
图 1:选择项目 - 现在包含 `
System.Runtime.InteropServices
` 命名空间的支持,它提供了使自定义类 COM 感知的类和属性。using System.Runtime.InteropServices;
- 现在添加一个接口,它应该是公开可访问的,并添加一个实现该接口的类,如下所示:
1. public interface IClassComVisibleLib 2. { 3. int CSharpMarks 4. { 5. get; 6. set; 7. } 8. int VCppMarks 9. { 10. get; 11. set; 12. } 13. int Calculate(); 14. } 15. public class ClassComVisibleLib : IClassComVisibleLib 16. { 17. private int m_iVCPPMarks = 0, m_iCSharpMarks = 0; 18. private string m_sStudentName = string.Empty; 19. public ClassComVisibleLib() { } 20. #region IClassComVisibleLib Members 21. public int CSharpMarks 22. { 23. get 24. { 25. return m_iCSharpMarks; 26. } 27. set 28. { 29. m_iCSharpMarks= value; 30. } 31. } 32. public int VCppMarks 33. { 34. get 35. { 36. return m_iVCPPMarks; 37. } 38. set 39. { 40. m_iVCPPMarks = value; 41. } 42. } 43. public int Calculate() 44. { 45. return m_iCSharpMarks + m_iVCPPMarks; 46. } 47. #endregion 48. }
- 现在打开 `AssemblyInfo.cs`,它位于项目的属性文件夹下。将 `[assembly: ComVisible(true)]` 的参数设置为 true,而之前是 false。这是使程序集 COM 可视化的第一步,同一个属性已经在文件中很好地注释过了。
- 在接口和类上,添加属性 `[ComVisible(true)]` 和 `[Guid("GUID_string generated from guidgen")]`。ComVisible 属性使你的接口或类 COM 可视(我知道,我多次重复了“COM 可视”,请忍受我,因为你还要读很多次,呵呵)。Guid 字符串可以使用 GuidGen 工具生成,该工具通常位于以下路径:
C:\Program Files\Microsoft Visual Studio 8\Common7\Tools
图 2:GuidGen 工具
从工具中复制 Guid 字符串并将其作为 Guid 属性的参数粘贴。接口和类应该有唯一的 Guid。 - 在接口上添加 `InterfaceType` 属性,它告诉编译器我们如何将接口暴露给 COM,它接受 `ComInterfaceType` 枚举作为参数。以下是来自 MSDN 的枚举及其含义:
成员名称 描述 InterfaceIsDual 表示接口被暴露给 COM 作为双重接口,支持早期绑定和晚期绑定。InterfaceIsDual 是默认值。 InterfaceIsIUnknown 表示接口被暴露给 COM 作为 IUnknown 派生接口,仅支持早期绑定。 InterfaceIsIDispatch 表示接口被暴露给 COM 作为 dispinterface,仅支持晚期绑定。
我们将使用 `InterfaceIsDual
`,因为它支持早期绑定和晚期绑定。 - 以同样的方式在类上添加 `ClassInterface` 属性。它标识为类生成的类接口的类型。它接受 `ClassInterfaceType` 枚举作为参数,以下是来自 MSDN 的枚举及其含义:
成员名称 描述 无 Indicates that no class interface is generated for the class. If no interfaces are implemented explicitly, the class can only provide late bound access through the IDispatch interface. This is the recommended setting for ClassInterfaceAttribute. Using ClassInterfaceType.None is the only way to expose functionality through interfaces implemented explicitly by the class. AutoDispatch 表示类仅支持 COM 客户端的晚期绑定。当请求时,会自动为类公开一个 dispinterface 给 COM 客户端。类型库导出器 (Tlbexp.exe) 生成的类型库不包含 dispinterface 的类型信息,以防止客户端缓存接口的 DISPID。dispinterface 不会表现出 ClassInterfaceAttribute 中描述的版本问题,因为客户端只能晚期绑定到接口。这是 ClassInterfaceAttribute 的默认设置。 AutoDual 表示会自动为类生成一个双重类接口并将其暴露给 COM。类型信息会为类接口生成并发布在类型库中。强烈不建议使用 AutoDual,因为它存在 ClassInterfaceAttribute 中描述的版本限制。
我们将使用 `None` 作为 `ClassInterface` 的参数。
. - 添加以上所有内容后,我们的类库看起来是这样的:
1. [ComVisible(true)] 2. [Guid("5DB724F2-763A-4eb2-A886-1DB3794585F6")] 3. [InterfaceType( ComInterfaceType.InterfaceIsDual)] 4. public interface IClassComVisibleLib 5. { 6. int CSharpMarks 7. { 8. get; 9. set; 10. } 11. int VCppMarks 12. { 13. get; 14. set; 15. } 16. int Calculate(); 17. } 18. [ComVisible(true)] 19. [Guid("DCD9F4D2-A529-446b-A8CD-7AE28F544EAC")] 20. [ClassInterface( ClassInterfaceType.None)] 21. [ProgId("progid_ClassComVisibleLib")] 22. public class ClassComVisibleLib : IClassComVisibleLib 23. { 24. private int m_iVCPPMarks = 0, m_iCSharpMarks = 0; 25. private string m_sStudentName = string.Empty; 26. public ClassComVisibleLib() { } 27. #region IClassComVisibleLib Members 28. public int CSharpMarks 29. { 30. get 31. { 32. return m_iCSharpMarks; 33. } 34. set 35. { 36. m_iCSharpMarks= value; 37. } 38. } 39. public int VCppMarks 40. { 41. get 42. { 43. return m_iVCPPMarks; 44. } 45. set 46. { a. m_iVCPPMarks = value; 47. } 48. } 49. public int Calculate() 50. { 51. return m_iCSharpMarks + m_iVCPPMarks; 52. } 53. #endregion 54. }
- 编译并构建上述代码以生成 ComVisibleLib.dll。由于此 DLL 是基于 .NET 的,您必须使用 Dev Studio 随附的 RegAsm 工具来生成 ComVisibleLib.tlb。以下是我们在 ComVisibleLib.DLL 上运行 `
RegAsm
` 工具时返回的语法和结果:Command_Prompt> regasm comvisiblelib.dll /tlb Microsoft (R) .NET Framework Assembly Registration Utility 2.0.50727.4016 Copyright (C) Microsoft Corporation 1998-2004. All rights reserved. Types registered successfully Assembly exported to 'C:\Projects\COM\ComVisibleLib\ComVisibleLib\bin\Debug\comvisiblelib.tlb', and the type library was registered successfully
在 Visual C++ MFC 应用程序中使用它
现在您的基于 .NET 的 COM 感知 DLL 已准备就绪。您现在将创建基于 MFC 的应用程序,让我们一步一步地进行:- 在上述解决方案中添加一个新的 MFC 项目,方法是单击 *文件 | 新建 | 项目* 并选择基于 MFC 的对话框项目。
图 3:选择 MFC 项目 - 设计用户界面,如下图所示:
图 4:用户界面 - 现在使用以下语法在您的项目中导入 TLB:
#import "C:\Projects\COM\ComVisibleLib\ComVisibleLib\bin\Debug\ComVisibleLib.tlb" raw_interfaces_only
- 在代码中添加与 COM 组件通信的代码
1. void CComVisibleLibTestDlg::OnBnClickedButtonSetvalues() 2. { 3. CoInitialize(NULL); 4. CLSID rclsid; 5. CLSIDFromProgID(L"progid_ClassComVisibleLib",&rclsid); 6. m_pToClass.CreateInstance(rclsid); 7. CString sName; 8. GetDlgItemText(IDC_EDIT_PNAME,sName); 9. HRESULT hr =m_pToClass->put_CSharpMarks(GetDlgItemInt(IDC_EDIT_PCSHARP)); 10. hr =m_pToClass->put_VCppMarks(GetDlgItemInt(IDC_EDIT_PVCMARKS)); 11. } 12. 13. void CComVisibleLibTestDlg::OnBnClickedButtonGetvalues() 14. { 15. long i = 0; 16. HRESULT hr=m_pToClass->get_CSharpMarks(&i); 17. SetDlgItemInt(IDC_EDIT_GCSHARP,i); 18. hr=m_pToClass->get_VCppMarks(&i); 19. SetDlgItemInt(IDC_EDIT_GVCMARK,i); 20. hr =m_pToClass->Calculate(&i); 21. SetDlgItemInt(IDC_EDIT_GTOTAL,i); 22. }
`m_pToClass` 是 `
` 类型,声明为私有的类变量。ComVisibleLib::IClassComVisible
LibPtr - 现在,当您编译并运行应用程序时,在创建对象时会抛出“Class Not Registered”(类未注册)错误。我花了将近 5-6 小时来解决这个问题,在搜索过程中,我发现了 Ivan Towlson 的这条评论,这简直是神来之语:
“检查注册表,看看 TLH 中提到的 CLSID 是否已注册。如果未注册,您可能需要使类 ComVisible 并重新运行 regasm。另一个可能的问题是 COM 是否能实际找到您的程序集。您的 .NET 程序集需要位于您的应用程序(EXE)目录或 GAC 中。(尽管您的类已注册为 COM 组件,但除非您将其放入 GAC,否则它*不*是全局可访问的,就像 COM 一样。所有 .NET 类的已注册 COM 处理程序是 mscoree.dll,而不是 .NET 程序集本身。)”
现在您可以通过将 DLL 文件复制到可执行文件所在的位置,或者创建与类库关联的强密钥并将其注册到 GAC 来解决此问题。
因此,我编写了构建后步骤来在新建的 DLL 上运行 regasm,然后将其复制到可执行文件所在的位置。C:\Windows\Microsoft.NET\Framework\v2.0.50727\regasm.exe $(TargetPath) /tlb copy $(TargetPath) $(SolutionDir)debug\ComVisibleLib.dll
- 现在运行应用程序以查看结果。
图 5:结果
创建 COM 感知 .NET DLL 的快速参考
- 创建类库项目
- 向项目添加接口和相应的类
- 将 AssemblyInfo.cs 中的 ComVisible 属性设置为 TRUE
- 在接口上添加 ComVisible、Guid 和 InterfaceType
- 在类上添加 ComVisible、Guid 和 ClassInterface,ProgId 属性是可选的
- 编译和构建以创建 .NET 感知 DLL
- 在 DLL 上运行 RegAsm 工具以创建 TLB 文件
特别感谢
- 献给我已故的母亲和父亲,当然还有我的妻子
- 献给 *CodeProject.com*,感谢它为程序员互动提供了平台。