|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionWorking as a developer prior to the advent of the .NET Framework really makes one appreciate the rich number of classes the Framework supports right out of the box. Many times however one may need to be working in the unmanaged world and would like to invoke one of those readily available classes provided in the managed world. Since I have been playing around with .NET for a while, I have learned many of the great classes available within the .NET Framework. SimonS recently posted a question in the C# forum that made me finish a little research and I wanted to share my finding with everyone here. I had actually been tinkering with this idea for a while but never actually had enough time to finish. Here it is in all its glory. The problem is, suppose that I have written a nice library, set of utility functions, etc.. running under the .NET Framework, however I want to use this under pre .NET development environments. For SimonS he would like to use VB6 specifically. Enter the COM Callable Wrapper (CCW) to create a proxy that will provide access to our functions through interface pointers. This can be accomplished through the use of those fun little attribute tags (I keep finding them more and more useful everyday) and with an interface of course. To begin, you will need to include the C# Source Codeusing System; using System.Runtime.InteropServices; namespace Tester { [Guid("D6F88E95-8A27-4ae6-B6DE-0542A0FC7039")] [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface _Numbers { [DispId(1)] int GetDay(); [DispId(2)] int GetMonth(); [DispId(3)] int GetYear(); [DispId(4)] int DayOfYear(); } [Guid("13FE32AD-4BF8-495f-AB4D-6C61BD463EA4")] [ClassInterface(ClassInterfaceType.None)] [ProgId("Tester.Numbers")] public class Numbers : _Numbers { public Numbers(){} public int GetDay() { return(DateTime.Today.Day); } public int GetMonth() { return(DateTime.Today.Month); } public int GetYear() { return(DateTime.Today.Year); } public int DayOfYear() { return(DateTime.Now.DayOfYear); } } } VB.NET Source CodeImports System
Imports System.Runtime.InteropServices
Namespace Tester
<Guid("89439AD1-756F-4f9c-BFB4-18236F63251E"), _
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> _
Public Interface _Tester
<DispId(1)> Function GetMonth() As Integer
<DispId(2)> Function GetDay() As Integer
<DispId(3)> Function GetYear() As Integer
<DispId(4)> Function DayOfYear() As Integer
End Interface
<Guid("1376DE24-CC2D-46cb-8BF0-887A9CAF3014"), _
ClassInterface(ClassInterfaceType.None), _
ProgId("Tester.Numbers")> Public Class Tester
Implements _Tester
Public Tester()
Public Function GetMonth() As Integer Implements _Tester.GetMonth
GetMonth = DateTime.Now.Month
End Function
Public Function GetDay() As Integer Implements _Tester.GetDay
GetDay = DateTime.Now.Day
End Function
Public Function GetYear() As Integer Implements _Tester.GetYear
GetYear = DateTime.Now.Year
End Function
Public Function DayOfYear() As Integer Implements _Tester.DayOfYear
DayOfYear = DateTime.Now.DayOfYear
End Function
End Class
End Namespace
More on Attributes - AutomationYou may be wondering what all those different attributes are being used for. The short answer is simply Automation, more exactly an Automation Server. Visual Basic is an Automation client, which simply means it consumes COM components that expose their functionality through an Before Compiling
Doing It By Hand Or Not...You may find it useful to set the Register for COM interop option to True. This will create a type library and make the correct registry entries for you. You don't have to do this, and in fact you can use the Assembly Registration Tool (Regasm.exe)[^] that comes with the .NET SDK, however it is much simplier to just check it in the property window. One caveat, if you are doing this by hand, without the IDE to register for COM Interop, prior to running the reasm.exe tool to create a type library you will need to use the Strong Name tool (sn.exe)[^] to sign your assembly and thus allowing the assembly to be placed in the Global Assembly Cache (GAC)[^]. The following screen shot shows the creation of a strong named key file via the command line.
The choice to install the assembly to another location other than the GAC is ultimately up to you. If you wish to place your assembly in another location other than the GAC you should include the regasm Tester.dll /tlb:Tester.tlb
To copy your assembly over to the GAC you can use the Global Assembly Cache Tool (Gacutil.exe)[^] with the following command: gacutil /i tester.dll Here Comes VBOpen VB, create a new Standard EXE, select Project ---> References and your class should be listed. Place a check mark on your class and add a button to your form. Click on the button and add the following "code". Private Sub Command1_Click()
Dim i As Tester.Numbers
Set i = New Tester.Numbers
MsgBox "The date is: " & i.GetMonth & "/" & i.GetDay & "/" & i.GetYear
End Sub
Throw in a little MFC tooOk, I know most everyone here, or at least a lot of you use MFC on a regular basis. Here we can see the implementation in MFC is rather simple. I removed the code for the about box on initialization just to shorten it up. You will need to include the BOOL CNickDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CString strMessage;
Tester::_Numbers *com_ptr;
CoInitialize(NULL);
Tester::_NumbersPtr p(__uuidof(Tester::Numbers));
com_ptr = p;
int m_iDay = com_ptr->GetDay();
int m_iMonth = com_ptr->GetMonth();
int m_iYear = com_ptr->GetYear();
strMessage.Format("Today is: %d/%d/%d", m_iMonth, m_iDay, m_iYear);
MessageBox(strMessage, "Today's Date", MB_ICONASTERISK |
MB_ICONINFORMATION);
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
return TRUE;
}
Final ThoughtsThere have been many questions posted regarding the concept that is being applied in this article. Again, the purpose of creating a COM Callable Wrapper (CCW) is strictly to provide a bridging mechanism between .NET and COM. The .NET Framework is still required to be installed on the client machine or server to work. We are identifying an interface in .NET that is exposed as an Update History
| ||||||||||||||||||||