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 take ComInterfaceType
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 use InterfaceIsDual
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 take ClassInterfaceType
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 use None
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.DLL
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
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 ComVisibleLib::IClassComVisible
LibPtr
declared as private class variable .
- 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.