Working with native code easily in C# with the help of C++/CLI

.NET comes with pretty good interoperability options which would enable unmanaged code to be used from a managed environment. In C#, to call a function which is available in a DLL, PInvoke (Platform invocation service) can be used. Here is what MSDN says.

Platform Invocation Services (PInvoke) allows managed code to call unmanaged functions that are implemented in a DLL.

This is very helpful when you need to call some system functions from your C# application. Now let us assume that you have lot of code which is written in C or C++. You are writing a brand new application in C# which internally uses the C/C++ code available. Since C/C++ code is categorised as unmanaged code, you can’t directly use them in C#. At C# side, wrapper classes and functions has to be written to interop properly with the native code. As I said earlier, if you just need to use a single function, it is easy to do directly from C# using PInvoke. But when you need almost all the functionalities/API to be available as a managed object, PInvoke would be a pain to use. Let us take an example from MSDN

typedef struct tagLOGFONT
{
   LONG lfHeight;
   LONG lfWidth;
   LONG lfEscapement;
   LONG lfOrientation;
   LONG lfWeight;
   BYTE lfItalic;
   BYTE lfUnderline;
   BYTE lfStrikeOut;
   BYTE lfCharSet;
   BYTE lfOutPrecision;
   BYTE lfClipPrecision;
   BYTE lfQuality;
   BYTE lfPitchAndFamily;
   TCHAR lfFaceName[LF_FACESIZE];
} LOGFONT;

In C#, this can be represented as

// logfont.cs
// compile with: /target:module
using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]
public class LOGFONT
{
    public const int LF_FACESIZE = 32;
    public int lfHeight;
    public int lfWidth;
    public int lfEscapement;
    public int lfOrientation;
    public int lfWeight;
    public byte lfItalic;
    public byte lfUnderline;
    public byte lfStrikeOut;
    public byte lfCharSet;
    public byte lfOutPrecision;
    public byte lfClipPrecision;
    public byte lfQuality;
    public byte lfPitchAndFamily;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=LF_FACESIZE)]
    public string lfFaceName;
}

Look at the C# example. It is not as clean as the structure definition at the C side. It contains attributes which specify the structure alignment, then marshaling etc. When the structure contains references to other structures, things gets complicated.

For C or C++ programmers, it is always convenient to declare the struct as they normally do. That’s the beauty of C++/CLI which allows you to combine managed and unmanaged code together and makes interoperability very straightforward.

In this post, we will take SQLite as an example and write a managed class which can be consumed from C#. We will wrap SQLite code using C++/CLI and consume from C#. SQLite has a huge API and we won’t be covering all of them. We will cover APIs which will allow to open a DB connection and execute a simple query.

Note: SQLite is chosen as an example. The correct way to wrap SQLite is adhering to ADO.NET standards and providing a API by extending IDBConnection. But for this post, we will ignore this and provide a custom simple API.

SQLite API

We will wrap the following functions

int sqlite3_open(const char *filename, sqlite3 **ppDb);
int sqlite3_exec(
  sqlite3*,                                  /* An open database */
  const char *sql,                           /* SQL to be evaluated */
  int (*callback)(void*,int,char**,char**),  /* Callback function */
  void *,                                    /* 1st argument to callback */
  char **errmsg                              /* Error msg written here */
);

Our managed API will look like,

SQLite db = new SQLite(filePath);
db.Execute(sql, callback);

callback can be assigned to a function which takes 2 parameters of type List<String>. It will contain column names and values respectively.

Wrapper class in C++/CLI

We will have a Visual studio solution with 3 projects.

Solution explorer

In C++/CLI, we start off by creating the following class.

// SQLiteWrapper.h

#pragma once

#include "sqlite3.h"

using namespace System;
using namespace System::Collections::Generic;

namespace SQLiteWrapper {

    // Main class that wraps the native API
    public ref class SQLite : IDisposable
    {
    public:
        SQLite(String^ filePath);
        void Execute(String^ sql, Action<List<String^>^, List<String^>^>^ callback);
        ~SQLite();

    private:
        sqlite3* db;
    };

    public ref class SQLiteError : Exception
    {
    public:
        SQLiteError(String^ errorMessage);
    };

    private ref class SQLiteDataCarrier
    {
    public:
        SQLiteDataCarrier(List<String^>^ columnNames, List<String^>^ columnValues)
        {
            this->columnNames = columnNames;
            this->columnValues = columnValues;
        }

        property List<String^>^ ColumnNames
        {
            List<String^>^ get()
            {
                return columnNames;
            }
        }

        property List<String^>^ ColumnValues
        {
            List<String^>^ get()
            {
                return columnValues;
            }
        }

    private:
        List<String^>^ columnNames;
        List<String^>^ columnValues;
    };
}

This class holds an instance to sqlite3 structure which is the core of SQLite library. Even though the class is a managed class, C++/CLI allows you to hold a pointer which points to sqlite3.

Our constructor can be implemented like,

// Constructor for managed class SQLite
SQLite::SQLite(String^ filePath)
{
    IntPtr p = Marshal::StringToHGlobalAnsi(filePath);
    const char* _filePath = static_cast<const char*>(p.ToPointer());

    try
    {
        sqlite3* d;
        int status = sqlite3_open(_filePath, &d);
        if (status)
            throw gcnew SQLiteError("Unable to open database");


        // Holding the DB pointer as a field
        // This is a pointer to native struct, but it can be stored in managed class!
        db = d;
    }
    finally
    {
        Marshal::FreeHGlobal(p);

    }
}

This code shows how well C++/CLI allows you to mix types from both managed and unmanaged world. It is beautiful!

All this constructor does is to initialize sqlite3 instance and update the db pointer with correct reference.

Here is how Execute() method is implemented.

// Callback function called by SQLite.
// This function shows how native and managed objects can work together!
static int _callback(void *userData, int argc, char **argv, char **azColName)
{
    IntPtr pointer(userData);
    GCHandle handle = GCHandle::FromIntPtr(pointer);
    SQLiteDataCarrier^ carrier = (SQLiteDataCarrier^) handle.Target;

    for(int i = 0; i < argc; i++)
    {
        const char* columnName = azColName[i];
        const char* columnValue = argv[i];

        carrier->ColumnNames->Add(gcnew String(columnName));
        carrier->ColumnValues->Add(gcnew String(columnValue));
    }

    return 0;
}


void SQLite::Execute(String^ sql, Action<List<String^>^, List<String^>^>^ callback)
{
    IntPtr p = Marshal::StringToHGlobalAnsi(sql);
    const char* _sql = static_cast<const char*>(p.ToPointer());

    // Output will be stored in these lists
    List<String^>^ columnNames = gcnew List<String^>();
    List<String^>^ columnValues = gcnew List<String^>();

    // A simple class to aggregate columnNames and columnValues so that it can be passed to SQLite and SQLite will give it back in the callback function
    SQLiteDataCarrier^ carrier = gcnew SQLiteDataCarrier(columnNames, columnValues);

    // Converting carrier to void*
    GCHandle handle = GCHandle::Alloc(carrier);
    IntPtr pointer = GCHandle::ToIntPtr(handle);

    char *zErrMsg = 0;
    int status = sqlite3_exec(db, _sql, _callback, pointer.ToPointer(), &zErrMsg);
    if (status != SQLITE_OK)
    {
        String^ message = gcnew String(sqlite3_errmsg(db));
        throw gcnew SQLiteError(message);
    }

    callback->Invoke(columnNames, columnValues);

    handle.Free();
    Marshal::FreeHGlobal(p);
}

To add results into the list, we are passing both lists to the sqlite3_exec() function so that SQLite will pass that back to the _callback function. This is very powerful because we just passed a managed object into a native function call. Then in a native function, we obtained the managed instance and modified properties on it.

Once the querying is done, database has to be closed. We do that in the destructor.

// In C++/CLI destructor will be implemented using Dispose() pattern
SQLite::~SQLite()
{
    sqlite3_close(db);
}

Using from C#

Usage from C# now becomes pretty straightforward.

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            SQLite db = new SQLite("C:\\Users\\navaneeth\\Desktop\\test\\example.db");
            string sql = "create table if not exists foos (name text);\n" +
                "insert into foos values ('sample1');" +
                "insert into foos values ('sample2');" +
                "select * from foos;";
            db.Execute(sql, (columnNames, columnValues) => {
                foreach (var name in columnNames)
                {
                    Console.WriteLine(name);
                }
                foreach (var value in columnValues)
                {
                    Console.WriteLine(value);
                }
            });
            db.Dispose();
            Console.Read();
        }
    }
}

You can download the code here. Happy programming!