Calling .NET from Unmanaged C++

My coworker Julie Nelson and I work on a legacy app written in C++/MFC. We wanted to take advantage of some managed devices such as .NET controls and web services, but the app is too big to rewrite in C#, so we needed a way to call these devices from the existing code. This article describes what we came up with: a fairly general method for calling managed code from unmanaged. The technique is demonstrated by a little dialog app that can communicate with the ADO.NET data source of your choice.

The idea is simple, and the work involved is mostly editing. For any .NET class or web service you want to use, you develop a set of wrapper functions exposing the methods and properties you need. The wrappers are in three parts: public interface, private implementation, and translation layer between the two. These parts are distributed in a particular way between .cpp and .h files, so that the latter can be included in unmanaged code while the former can call managed classes. There is a systematic naming convention, so you can look at a call and know which system function it wraps.

The client app for demonstrating the technique takes an ADO.NET connect string, connects to a data source, and retrieves schema data or query results into a grid. It uses a few System.Data.Oledb classes—Command, Connection, DataAdapter, and DataReader—wrapped and callable from the standard MFC dialog app.


  • Public interfaces (.h file). These specify the routines your app is allowed to call. The interfaces are in terms of friendly types (like CString), and the .h file can be included in your normal MFC app. Class names all start with "NL_" (for NetLib), followed by the name of the system class being wrapped. Each NL class has a member pointer to its implementation.
  • Private implementations (.cpp file). Each NL class has a corresponding "NLI_" (NetLib Implementation) class in the .cpp file. These classes convert arguments as necessary and call the managed system routines. Each NLI class has a garbage-collected pointer to a system class object.
  • Public implementations (.cpp file). The NL interfaces in the .h file just pass their arguments to the equivalent NLI functions.


One of the classes you want to use is System::Data::OleDb::OleDBConnection. You need capabilities to establish a connection, obtain a command object, and access some schema information.

The public interface in NETLib.h exposes these functions in the form of an NL wrapper class:

class NL_OleDbConnection
   NL_OleDbConnection(NLI_OleDbConnection *p);
   NL_OleDbConnection(const CString& conn);
   virtual ~NL_OleDbConnection();

   CString             DataSource();
   CString             ConnectionString();
   void                Open();
   void                Close();
   NL_OleDbCommand*    CreateCommand();
   NL_DataTable*       GetSchema();

   friend class NLI_OleDbCommand;
   NLI_OleDbConnection *m_pi;

Each NL class has a default constructor, plus one that takes a pointer to the implementation, plus possible others for convenience (in this case, one for constructing from a string). This example has two read-only properties returning strings, Open/Close methods, plus two properties that return other objects as NL pointers.

Forward declarations are necessary for the compiler. There are many, so we collected them in a separate header file (class_defs.h), which has two lines per class:

class NL_OleDbConnection;
class NLI_OleDbConnection;

The private implementation in NETLib.cpp looks like this:

class NLI_OleDbConnection
   NLI_OleDbConnection() : m_OleDbConnection(gcnew OleDbConnection) { }
   NLI_OleDbConnection(OleDbConnection^ g) : m_OleDbConnection(g) { }
   NLI_OleDbConnection(const CString& conn)
   : m_OleDbConnection(gcnew OleDbConnection(gcnew String(conn))) { }

   CString             DataSource()
      { return m_OleDbConnection->DataSource; }
   CString             ConnectionString()
      { return m_OleDbConnection->ConnectionString; }
   void                Open()
      { m_OleDbConnection->Open(); }
   void                Close()
      { m_OleDbConnection->Close(); }
   NL_OleDbCommand*    CreateCommand();
   NL_DataTable*       GetSchema();

   gcroot<OleDbConnection^> m_OleDbConnection;

The actual system object is the member m_OleDbConnection. Most NLI functions are simply inline calls to members or properties of that object. More complicated functions are implemented elsewhere in the .cpp file.

Connecting the two parts is a series of translation calls in NETLib.cpp:

   : m_pi(new NLI_OleDbConnection) { }
NL_OleDbConnection::NL_OleDbConnection(NLI_OleDbConnection *p)
   : m_pi(p) { }
NL_OleDbConnection::NL_OleDbConnection(const CString& s)
   : m_pi(new NLI_OleDbConnection(s)) { }

   { delete m_pi; }
CString NL_OleDbConnection::DataSource()
   { return m_pi->DataSource(); }
CString NL_OleDbConnection::ConnectionString()
   { return m_pi->ConnectionString(); }
void NL_OleDbConnection::Open()
   { m_pi->Open(); }
void NL_OleDbConnection::Close()
   { m_pi->Close(); }
NL_OleDbCommand* NL_OleDbConnection::CreateCommand()
   { return m_pi->CreateCommand(); }
NL_DataTable* NL_OleDbConnection::GetSchema()
   { return m_pi->GetSchema(); }

where each NL routine calls the corresponding routine of the implementation object, m_pi.


The included project is for VS2005, and requires the .NET Framework 2.0. Assuming it builds and works, what you get is a little data source browser, which is fairly handy but not intended to serve any real purpose. It demonstrates a few wrappers for a few classes, and should have enough code to show how you can go on to develop your own.

-- Jim Dill

About the Author

Jim Dill

Developer at CambridgeSoft Corp. Hobby: TrainPlayer Software.



  • Make the paths relative

    Posted by jonp71 on 03/20/2007 07:35am

    You'll want to set WSClient's "Additional Include Directories" setting to "$(ProjectDir)NETLib";"$(ProjectDir)JLib" instead of the hard-coded ones that are in the project now. Other than that, this is a really nice hack!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Download the Information Governance Survey Benchmark Report to gain insights that can help you further establish business value in your Records and Information Management (RIM) program and across your entire organization. Discover how your peers in the industry are dealing with this evolving information lifecycle management environment and uncover key insights such as: 87% of organizations surveyed have a RIM program in place 8% measure compliance 64% cannot get employees to "let go" of information for …

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds