Using COM aware DotNet Library in MFC Application
Article about using C# DLL in unmanaged VC++ application
Introduction
Recently (i.e. yesterday) I have published article for creating COM/ATL based component and using it in Dot-Net applications. There a thought strike my mind, why not used DotNet based library in unmanaged VisualCpp application. As usual, I start by searching over internet (Developer Mind, Huh! J), there I found some useful tips to get started in above topic.
Though I am not a seasoned dot-net developer, but I am able to create dot-net library quite quickly, but when it’s come for integration it unmanaged application, really I chewed iron nuts to integrate it, sometime com object is not created, some time exported files doesn’t contain information for exposed function. Here, in this article I tried to share everything, whatever I learned from above experience.
Table of Content
- Creating com aware C#.Net class library
- Using it in Visual C++ MFC Application
- Quick Revision
Creating C#.Net class library
Let’s proceed step by step for creating C# class library- Open Development Environment (Visual Studio 2005), Click on File| New | Project, create C# class library project as shown in below example :
Figure1: Selecting project - Now include support of
System.Runtime.InteropServices
namespace, which provide classes and attribute to make custom class COM aware.using System.Runtime.InteropServices;
- Now add interface and it should be publicly accessible and also add class which implement the interface like this :-
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. }
- Now open
AssemblyInfo.cs
, it’s under Properties folder of the project. Pass true as parameter of[assembly: ComVisible(true)],
which is false earlier, this is first step toward making assembly com visible, same attribute is already very well commented in file. - On interface and class, add attribute
[ComVisible(true)]
and[Guid("GUID_string generated from guidgen")]
. ComVisible attribute make your interface or class com visible (I know, I have repeated Com visible many times, please bear with me for same, as you going to read it some more times, hehehe). Guid string can be generated using GuidGen utility which generally available at following path :-C:\Program Files\Microsoft Visual Studio 8\Common7\Tools
Figure2: GuidGen Utility
Copy the Guid string from utility and paste it as argument for Guid attribute. Interface and Class should have unique Guid. - Add Attribute
InterfaceType
on Interface, which tell compiler how we exposing our interface to COM , which takeComInterfaceType
Enumeration as a argument. Following are enumeration and there meaning from MSDN.
Member name Description InterfaceIsDual Indicates the interface is exposed to COM as a dual interface, which enables both early and late binding. InterfaceIsDual is the default value. InterfaceIsIUnknown Indicates an interface is exposed to COM as an IUnknown -derived interface, which enables only early binding. InterfaceIsIDispatch Indicates an interface is exposed to COM as a dispinterface, which enables late binding only.
We will useInterfaceIsDual
as it will enable both early and late binding. - Same way add attribute
ClassInterface
on class. It’s identifies the type of class interface that is generated for a class. It’s takeClassInterfaceType
Enumeration as argument, following are enumeration and there meaning from MSDN
Member name Description None 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 Indicates that the class only supports late binding for COM clients. A dispinterface for the class is automatically exposed to COM clients on request. The type library produced by the type Type Library Exporter (Tlbexp.exe) does not contain type information for the dispinterface in order to prevent clients from caching the DISPIDs of the interface. The dispinterface does not exhibit the versioning problems described in ClassInterfaceAttribute because clients can only late bind to the interface. This is the default setting for ClassInterfaceAttribute. AutoDual Indicates that a dual class interface is automatically generated for the class and exposed to COM. Type information is produced for the class interface and published in the type library. Using AutoDual is strongly discouraged because of the versioning limitations described in ClassInterfaceAttribute.
We will useNone
as argument for ClassInterface
. - After all above addition, Our class library looks like to be:-
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. }
- Compile and Build above code to make ComVisibleLib.dll, since this DLL is .net based you have to use RegAsm utility supplied with Dev Studio to generate ComVisibleLib.tlb. following is syntax and result returned by
RegAsm
utility when we run it on ComVisibleLib.DLLCommand_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
Using it in Visual C++ MFC Application
Now your .Net based COM aware DLL is ready, you will now create MFC based application, let follow step by step for this- Add a new MFC project in above solution, by Clicking File | New | Project and selecting MFC based Dialog project.
Figure3: Select MFC Project - Design the user interface like shown in the figure displayed below :-
Figure4: User interface - Now import TLB in your project, by using following syntax :-
#import "C:\Projects\COM\ComVisibleLib\ComVisibleLib\bin\Debug\ComVisibleLib.tlb" raw_interfaces_only
- Add code for communication with com component within your code
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
is of type
declared as private class variable .ComVisibleLib::IClassComVisible
LibPtr - Now, when you compile and run your application, it will throw "Class Not Registered" error at time of creation of object. I spent almost 5-6 hr to solve this problem, during searching I found this comment from Ivan Towlson, which seems to be word send by god himself :-
“Have a look in the registry to see if the CLSID mentioned in the TLH is>registered. If not, you may need to make the class ComVisible and>re-regasm.> Another possible issue is whether COM can actually find your assembly.>Your .NET assembly needs to be either in your app (EXE) directory or GAC. (Even though your class is registered as a COM component, it's>*not* globally accessible a la COM unless you put it in the GAC. The>registered COM handler for all .NET classes is mscoree.dll, not the.NET>assembly itself.)”
Now you can solve this problem by either copying dll file to location where executable is present or create a strong key, associate with class library and register it with GAC.
So, I have written post build step to run regasm on new created dll and then copy it to location where executable is present.C:\Windows\Microsoft.NET\Framework\v2.0.50727\regasm.exe $(TargetPath) /tlb copy $(TargetPath) $(SolutionDir)debug\ComVisibleLib.dll
- Now run the application to see the result.
Figure5: Result
Quick Reference for creating COM aware DotNet DLL
- Create Class Library project
- Add Interface and corresponding class to the project
- Make ComVisible attribute in AssemblyInfo.cs to TRUE
- Add ComVisible, Guid and InterfaceType on Interface
- Add ComVisible, Guid and ClassInterface on class, ProgId attribute is optional
- Compile and Build to create DotNet Aware DLL
- Run RegAsm utility on DLL to create TLB file
Special Thanks
- To My Mother (Late) and Father and off course My Wife
- To CodeProject.com, for providing platform for Programmer Interaction.