Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C

Writing a Native C++ Application to Consume a .NET Assembly

Rate me:
Please Sign up or sign in to vote.
3.91/5 (6 votes)
22 Jul 2009CPOL5 min read 46.3K   497   35   7
An article about the boundary of unmanged native and managed code
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.

f07c8z1c_ccw_en-us_VS_71_.gif

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
    {
        /// <summary />
        /// Required designer variable.
        /// </summary />
        private System.ComponentModel.IContainer components = null;

        /// <summary />
        /// Clean up any resources being used.
        /// </summary />
        /// <param name="disposing" />true if managed resources should be disposed; otherwise, false.</param />
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary />
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary />
        private void InitializeComponent()
        {
            this.dateTimePicker1 = new System.Windows.Forms.DateTimePicker();
            this.button1 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // dateTimePicker1
            // 
            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;
            // 
            // button1
            // 
            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);
            // 
            // Form1
            // 
            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:

1.JPG

2.JPG

Now, recall the event handler used the Close() method for the button control, which is what it will do:

3.JPG

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Monroe Community
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralInteresting Pin
Robin2-Aug-09 2:43
Robin2-Aug-09 2:43 
Questionquestion [modified] Pin
flyingxu29-Jul-09 1:42
flyingxu29-Jul-09 1:42 
GeneralBad HTML Esacpes Pin
Jeffrey Walton5-Apr-09 14:43
Jeffrey Walton5-Apr-09 14:43 
QuestionWhat about C++/CLI Pin
Ernest Laurentin5-Apr-09 9:53
Ernest Laurentin5-Apr-09 9:53 
Sir,
I was a little bit confused by your article maybe I had different expectations because of the title.
My concern is that you can do more with C++/CLI, what advantages do you see in doing this the way you did?
Also, I was expecting that you would used CorBindToRuntimeEx, ICorRuntimeHost and other MSCORLIB interfaces but that wasn't the case.
What are the benefits over C++/CLI (managed C++).
Thank you


Try not to become a man of success but rather to become a man of value. - Albert Einstein
Ernest Laurentin

AnswerRe: What about C++/CLI Pin
N a v a n e e t h22-Jul-09 16:48
N a v a n e e t h22-Jul-09 16:48 
GeneralRe: What about C++/CLI Pin
logicchild23-Jul-09 9:51
professionallogicchild23-Jul-09 9:51 
GeneralIt Report Error While Debugging under DatePickerNative Pin
littlewater5-Apr-09 5:50
littlewater5-Apr-09 5:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.