Download DatePicker.zip - 454.53 KB
Introduction
There are several articles about the topic of COM and .NET Interop and this article is not intended as a substitute for one of them. This paper is intended for the intermediate user to gain a stronger grasp of COM and.NET Interop by showing a simple method of implementing Interop. If we want to use a managed assembly in a native C++ application, there are essentially three things that we need to prioritize. The first is to determine how to allocate the object, the second is to determine the object’s life cycle in order to enable the garbage collection, and thirdly, we need to determine how we are going to marshal the object across the boundary between the unmanaged world and the managed world. The .NET Framework’s tool, regasm.exe, is able to create registry entries for a .NET component that is meant to appear as a COM component. Regasm.exe also examines the metadata tables of a .NET assembly and creates a type library that is used by the C++ compiler to create its own headers and so forth. A COM Callable Wrapper, CCW, lies between the world of the managed and the unmanaged. Knowing that a COM client is mostly dealing with pointers while a .NET assembly is mostly dealing with references to objects, the CCW is able to both map those pointers to those references to managed objects. Moreover, the CCW is able to keep track of reference counting, so when a COM client’s reference count is zero, the CCW can then pass it to the object reference where it will soon be garbage collected.
At the same time, the CLR keeps close track of a .NET application’s actions and always knows when references go out of scope and are candidates for collection. But the CLR cannot possibly know what native code is doing. So the only way that the garbage collection can happen safely is if we mark the period in which this object may not be collected and mark it when it can be collected. You make use of a class called GCHandle
, and what you do is that you pass it a reference to a managed object, and you call the "alloc" member of that class- you pass a handle to the object and as long as that handle is open, the collector will avoid collecting that object for disposal. When you're done with the object, you call free, and that tells the collector that you're done with the object. We use C++ Interop (Implicit PInvoke) to get ourselves in managed code to allocate an object. With C++ interop, you can call a managed method as though it were a native method. The C++ compiler makes the transition.
The way we are going to examine Interop is to build a C# Windows Form control and then build a native C++ application that makes use of that control. The application and the managed assembly, or control (class) that we will use are built using the 2008 Express Editions of Visual C# and Visual C++. We start by firing up Visual C# to build a new project that is a class library. After we change the name to DatePicker
, we add a Windows form, in which we drag and drop a DateTimePicker
control and a Button
control (whose text property is changed to Select). Because we built the project as a class library, the project compiles into a DLL that the native C++ application will reference. Below is the code for DataPicker.cs:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace DatePicker
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class DatePicker
{
public DatePicker()
{
}
public string PickDate()
{
Form1 form = new Form1();
String date;
Application.Run(form);
date = form.Date;
form.Dispose();
return date;
}
}
}
Here is the code for the Form1.cs
designer that contains the DateTimePicker
and the Button
:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace DatePicker
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Close();
}
public String Date
{
get
{
return dateTimePicker1.Value.ToString();
}
}
}
}
And here is the code-behind for Form1.Designer.cs:
namespace DatePicker
{
partial class Form1
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.dateTimePicker1 = new System.Windows.Forms.DateTimePicker();
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
this.dateTimePicker1.Location = new System.Drawing.Point(44, 76);
this.dateTimePicker1.Name = "dateTimePicker1";
this.dateTimePicker1.Size = new System.Drawing.Size(204, 20);
this.dateTimePicker1.TabIndex = 0;
this.button1.Location = new System.Drawing.Point(109, 137);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 1;
this.button1.Text = "Select";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.button1);
this.Controls.Add(this.dateTimePicker1);
this.Name = "Form1";
this.Text = "Pick a date, any date";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.DateTimePicker dateTimePicker1;
private System.Windows.Forms.Button button1;
}
}
The above project is a Visual C# class library application purposed to build a managed code component. Because there is no "main" point of entry", it compiles into a DLL. This means that we only build the solution. Now this is sort of a naive method, but copy and paste that built DLL into your .NET Framework 2.0 directory, and run the regasm.exe tool on it to create a registry entry. Using this command lne on the command line:
C:\Windows\Microsoft.NET\Framework\v2.0.50727>regasm /tlb:DatePickerLib.tlb DatePicker.dll
you will have a successfully registered type library that will enable that gap between the managed and unmanaged world od COM. Now we must build a relatively simple COM application that will consume this .NET assembly:
The native C++ project (using Visual C++ Express 2008) is called DatePickerNative
, and it is an empty, Win32 console application that has the precompiled headers check box unchecked in its application settings. The first property to configure is the CLR Support option. Next, we one item, a source code file named DatePickerNative.cpp:
#include <windows.h> <windows.h />
#include <iostream><iostream />
#import "..\\..\\DatePicker\\DatePicker\\DatePickerLib.tlb" raw_interfaces_only
int main()
{
CoInitialize(0);
{
BSTR bstr;
HRESULT hr;
DatePicker::_DatePickerPtr picker(__uuidof(DatePicker::DatePicker));
hr = picker->PickDate(&bstr);
std::wcout << bstr << std::endl;
}
CoUninitialize();
return 0;
}
You can copy and paste that DatePickerLib.tlb into this project's directory, which means that you might not have to use the path to the right of the import statement. In other words, if the code doesn't compile, get rid of the path lines and just place the DatePickerLib.tlb in quotes after the import statement.
When we build this project, try to copy and paste the DatePicker.dll from the Visual C# project files release folder into the Visual C++ project folder to avoid using the < > signs and using a path to this referenced DLL. Just keep it in quotes. Now we build the native C++ application to get as an output the DatePicker
control and a blank DOS prompt:
Now, recall the event handler used the Close()
method for the button
control, which is what it will do: