Sunday 21 October 2012

Calling Managed C# code from Native C++

Introduction

Why Do This?

There is a lot of power and flexibility available in the .NET world that is not available in the standard C++ STL & ATL libraries. It would be very useful to be able to tap into these capabilities in an otherwise highly efficient and optimised native C++ program.

What do you mean by native / managed?

Native code is compiled into machine code that runs directly on the host computer. Once compiled, it is very lightweight and fast. Managed code, on the other hand, is compiled into bytecode that needs an interpreter to be able to run on the host computer. This interpreter is provided by the .NET framework and therefore to run managed code you need to have the .NET framework installed. The benefit of this is that there are various programming languages, such as C# and VB.NET, that compile to the same bytecode, so you can pick your favourite. Also, managed code needs to be compiled for the operating system it is running on. A managed program can run on any operating system that has .NET framework installed.

Why is this difficult?

You cannot call a C# function directly from a native C++ program. You have to have a marshalling function in the middle, written in managed C++. This is because .NET hides the complexity of pointers by making all objects reference-types by definition. It then manages the life-cycle of these objects and references through managed pointers. You have to convert objects from managed .NET classes into native classes.

How is it done?

In Visual Studio, you need to have different projects in the same solution if you want to mix languages. For example, if you want to write code in C++ and C#, you need to have a C++ project and a C# project. You can mix managed C++ and native C++ in the same project, but there are some tricks you need to do to get it to work. That is what the main point of this blog is about, how to set up the C++ project to work with mixed native and managed code.

Setting up the project

Solution configuration

The solution has 2 projects, one is a C# project and the other is C++.

Solution Configuration

C# Project

The C# project is set up as a class library, meaning that it does not have an entry point and cannot be run as a program. Instead, the output is a DLL that can be linked to the other project in the solution. I have written a static function that does some formatting using the System.DateTime class, which is not available in native C++.

CLRClass.cs

using System;

namespace CSharpProject
{
    public static class CLRClass
    {
        public static string FormatDateTimeClr(int year, int month, int day, string format)
        {
            DateTime dt = new DateTime(year, month, day);
            return dt.ToString(format);
        }
    }
}

C++ Project

The C++ project is set up as a native console application. This has a main method that does the work of the program and therefore that is the entry point to the application. If this is a fairly big application, it is a good idea to use pre-compiled headers. This means that the standard header, stdafx.h, is compiled once and linked once to all the code files in the program, rather than copying in the contents of this to all the code files. Compilation will be much faster and also the program will be leaner because only the necessary code will be included in the source. However, you cannot include a native pre-compiled header in a managed C++ file. The way round this is to have a native stdafx.h and a managed stdafx.h, and generate 2 precompiled headers.

The mechanism that tells the program to generate a precompiled header is by having a dummy code file called stdafx.cpp that includes stdafx.h and has the setting to generate the precompiled header. For example, the settings for stdafx.cpp in my project is shown here;

Precompiled Header settings for stdafx.cpp
In order to generate a managed pre-compiled header file, I create two new source files, CLRstdafx.h and CLRstdafx.cpp. The source for stdafx.h, CLRstdafx.h and CLRstdafx.cpp are shown here;

stdafx.h

#pragma once

#include <string>
#include <stdio.h>
#include <tchar.h>
#include <iostream>

CLRstdafx.h

#include "stdafx.h"
#include <vcclr.h>

CLRstdafx.cpp

#include "CLRStdAfx.h"

 The CLR'd stdafx.h includes vcclr.h because this is a managed header and therefore cannot go in the native header file. Then, the settings for CLRstdafx.cpp are as follows;

Precompiled Header settings for CLRstdafx.cpp
As you can see, the pre-compiled header is set to CLRStdAfx.h and the pre-compiled header output file is NativeApplicationCLR.pch. Therefore, any managed source file will have to have these same settings in order to use the CLR pre-compiled header.

The managed C++ file that converts the data to and from native and managed code is declared in CLRInteropHeader.h and defined in CLRInteropFile.cpp.

CLRInteropHeader.h

std::wstring FormatDateTime(
    int year,
    int month,
    int day,
    std::wstring& format);


CLRInteropFile.cpp

#include "CLRstdafx.h"
#include "CLRInteropHeader.h"

using namespace CSharpProject;

std::wstring FormatDateTime(int year, int month, int day, std::wstring& format)
{
    System::String^ clrString = gcnew System::String(format.c_str());
    System::String^ formattedString =
        CLRClass::FormatDateTimeClr(year, month, day, clrString);
    pin_ptr<const wchar_t> wch = PtrToStringChars(formattedString);
    return std::wstring(wch);
}

The format specifier for the date-time is provided as a reference to a string, which needs to be converted to a .NET version of a string before passing to the C# function. Then, the results have to be converted back into a native string before passing back to the calling native function. In order for this code to work, a reference from the C++ to the C# project is required.

Adding Reference to CSharpProject
Then the program is finally ready to use the .NET features.

Program.cpp

#include "stdafx.h"
#include "CLRInteropHeader.h"

int _tmain(int argc, _TCHAR* argv[])
{
    int year = 2014;
    int month = 07;
    int day = 12;
    std::wstring format(L"yyyy-MMM-dd");
    std::wstring formattedString =
        FormatDateTime(year, month, day, format);
    std::wcout << formattedString;
    return 0;
}

And the final output;

Result of Running Program