migration C#, C++ (day 14), call C# from C++ (advanced), call C++ from C# (hardcore)

logo

Hardcore today.The second example is C# even though it looks like C++. So don’t get confused. Day 15 is nigh.

C++ is about speed and not comfort. Nevertheless, sometimes you want comfort. And it can be done. The C++ programmer can in fact call C# code from C++ to access the entire .Net Framework indirectly.
Usually you would use a TCP connection to achieve this. Unfortunately it does require work. And you sometimes do not have the source code of a C# library to quickly build in a network connection. And remoting is not a solution as well; the protocol needs to be the same. So you could end up in a lot of unnecessary programming. If you can, access available C# libraries directly. And today I show you how it can be done.

In case you need to write the library, make sure to amend the com-visibility in your AssemblyInfo.cs to [assembly: ComVisible(true)]. You also have to check the “Register for COM Interop” setting.

ComVisible

Oh, and before I forget this important detail. Start your Visual Studio in admin mode by right clicking it and then selecting “Run as administrator”. You won’t have enough user rights to register the library for com otherwise. In case you are not using Visual Studio, or you have to roll out the library on a different PC, then use RegAsm to register the library on that system. You may have a 32 bit and a 64 bit version of RegAsm on your PC. Use the right one to avoid nasty error messages.

Calling C# code from C++

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace TestLib {

  // struct is a value type
  [StructLayout(LayoutKind.Sequential)]
  public struct Friend {
    public int friendSince; // value type
    [MarshalAs(UnmanagedType.BStr)]
    public string name;  // reference type
  } // struct

  public interface IFriends {
    Friend[] GetFriends { get; }
    string ShowErrorMessage(string xMessage);
  } // interface

  public class Friends : IFriends {

    public string ShowErrorMessage(string xMessage) {
      Console.WriteLine("Do you see the nice .Net MessgeBox?");
      DialogResult lButtons = MessageBox.Show("Therefore let's use Windows Forms and do the stuff that would take too much time in C++.", xMessage, MessageBoxButtons.YesNoCancel);
      switch (lButtons) {
        case DialogResult.Cancel:
          return "Cancel? Any other decision would have been better than that one!";
        case DialogResult.No:
          return "No? What a pessimist you are!";
        case DialogResult.Yes:
          return "Yes? PS:I lov ya!";
        default:
          return "impossible";
      }
    } //


    public Friend[] GetFriends {
      get {
        Friend[] lFriends = new Friend[6];
        lFriends[0].name = "Michael Sylvester Gardenzio Stallone";
        lFriends[0].friendSince = 1946;

        lFriends[1].name = "Rocky Balboa";
        lFriends[1].friendSince = 1978;

        lFriends[2].name = "John Rambo";
        lFriends[2].friendSince = 1982;

        lFriends[3].name = "Ray Breslin";
        lFriends[3].friendSince = 2013;

        lFriends[4].name = "Gabe Walker";
        lFriends[4].friendSince = 1993;

        lFriends[5].name = "Ray Tango";
        lFriends[5].friendSince = 1989;

        return lFriends;
      }
    } //

  } // class
} // namespace
#include <windows.h>
#include <stdio.h>
#include <iostream>
#pragma warning (disable: 4278)
#import <mscorlib.tlb> // import the CLR
#import "..\TestLib\bin\Debug\TestLib.tlb"  // path to your C# library

using namespace std;
using namespace TestLib;

void test() {
  HRESULT lHResult;

  lHResult = CoInitialize(NULL); // Initializes the COM library on the current thread and identifies the concurrency model as single-thread apartment (STA).
  if (FAILED(lHResult)) { cout << "Cannot initialize com." << endl; return; }

  IFriends *lFriends = nullptr;
  lHResult = CoCreateInstance(__uuidof(Friends), NULL, CLSCTX_INPROC_SERVER, __uuidof(IFriends), (void**)&lFriends);
  if (FAILED(lHResult)) { cout << "Instance creation failed" << endl; return; }

  Friend HUGEP *lBSTR;
  SAFEARRAY *lSafeArray = lFriends->GetFriends;
  lHResult = SafeArrayAccessData(lSafeArray, (void HUGEP* FAR*)&lBSTR);
  if (FAILED(lHResult)) { cout << "Getting a property failed" << endl; return; }
  for (ULONG i = 0; i < lSafeArray->rgsabound->cElements; i++) {
    wcout << lBSTR[i].name << " is my best buddy since " << lBSTR[i].friendSince << endl;  // 64bit: lBSTR[0].name is wchar_t (Unicode)
  }
  SafeArrayUnaccessData(lSafeArray);

  _bstr_t lMessage("C++ is not happy enough with Rambo!");
  _bstr_t lReply = lFriends->ShowErrorMessage(lMessage);
  wcout << "C# replied: " << lReply << endl; // .GetBSTR()

  CoUninitialize();
  cin.get();
} //

example output:
Michael Sylvester Gardenzio Stallone is my best buddy since 1946
Rocky Balboa is my best buddy since 1978
John Rambo is my best buddy since 1982
Ray Breslin is my best buddy since 2013
Gabe Walker is my best buddy since 1993
Ray Tango is my best buddy since 1989
Do you see the nice .Net MessgeBox?
C# replied: Yes? PS:I lov ya!

MsgBox

Let’s do the opposite now. C# coders sometimes need to access mashine code (C++, Assembler, etc).
I decided to make it a bit more difficult today. As mentioned already: It is day 14. Therefore I thought about returning an array of a structure from C++ to C#. I was checking the internet and after more than an hour I still had not found anything that could be used. C# most likely does not support it.
After a while I got the idea to use pointers in C#. They are very unusual and only for professional people. Well, finally I found a practical example for something seemingly useless. You can really crash your PC if you do it wrong. The critical code is marked with the keyword unsafe:

unsafe {
  Friend* lFriend = (Friend*)lIntPtr;
  for (int i = 0, n = FriendsCount(); i < n; i++) {
    char* lChars = (char*)lFriend->name;
    string lWho = new string(lChars);
    Console.WriteLine("C#: since " + lFriend->friendSince + " my best buddy is " + lWho);
    lFriend++;
  }
}

I have to admit, in hindsight it does look easy. But it took a lot of time to get there. Check the internet. There is no solution out there on how to read returned arrays … at least I did not find it. Many people have failed it seems.
First of all you have to enable unsafe code to use pointers in C#. Go to your Build settings and check the Allow unsafe code option:

Unsafe

The unknown size with arrays of structures can be solved by using pointers in the structure rather than the content itself. This way the size is equidistant. Surely, you cannot solve the problem when you have no source code access to the library and the returned structure size is variable. I see no practical com-example for this. Indeed all programmers of all languages would face the same problem here. C++ does not tell you the size, because there is no meta-data. This is why I implemented a function to get the array size separately. I am sure you have used buffers in the past. You always have to oversize them. This is a similar problem, not the same though.

Calling C++ code from C#

This really is C#. It can use pointers.

using System;
using System.Runtime.InteropServices;

namespace Day14b {
  class Program {

    [StructLayout(LayoutKind.Sequential)]
    public struct Friend {
      public int friendSince;
      public IntPtr name;
    }; // struct

    private const string cLibrary = @"C:\Users\Username\Desktop\Day14b\Debug\TestLib.dll";

    [DllImport(cLibrary, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
    [return: MarshalAs(UnmanagedType.BStr)]
    static extern string ShowErrorMessage(string xMessage);

    [DllImport(cLibrary, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
    static extern IntPtr GetFriend();

    [DllImport(cLibrary)]
    static extern int FriendsCount();

    [DllImport(cLibrary)]
    static extern void ReleaseMemory();

    static void Main(string[] args) {
      string lFeedback = ShowErrorMessage("C#: Hello C++");
      Console.WriteLine("C#: C++ replied -> " + lFeedback + Environment.NewLine);

      Console.WriteLine("C#: Number of friends: " + FriendsCount());
      Console.WriteLine("C#: Fasten your seatbelts! This is hardcore C#");

      IntPtr lIntPtr = GetFriend();
      Console.WriteLine();
      Console.WriteLine("C#: Number of friends: " + FriendsCount());
      Console.WriteLine();
      unsafe {
        Friend* lFriend = (Friend*)lIntPtr;
        for (int i = 0, n = FriendsCount(); i < n; i++) {
          char* lChars = (char*)lFriend->name;
          string lWho = new string(lChars);
          Console.WriteLine("C#: since " + lFriend->friendSince + " my best buddy is " + lWho);
          lFriend++; // increment the pointer, it now points to the next structure in the array
        }
      }

      ReleaseMemory();
      Console.ReadKey();
    } //

  } // class
} // namespace
#include <stdio.h>
#include <iostream>
#include <string>
#include <vector>
#include <windows.h>

using namespace std;

extern "C" {
  struct Friend {
    int friendSince;
    const wchar_t *name;
  }; // struct

  vector<wchar_t *> _BStrings;
  Friend *_Friends = nullptr;

  const wchar_t *Alloc(const wchar_t *xText){
    wchar_t * lBSTR = SysAllocString(xText);
    _BStrings.push_back(lBSTR);
    return lBSTR;
  } //

  __declspec(dllexport) const wchar_t* ShowErrorMessage(BSTR xMessage) {
    cout << "C++: I am too lazy for MessageBoxes." << endl;
    wcout << "C++: Your message was: " << xMessage << endl;
    return Alloc(L"C++: Good night!");
  } //

  __declspec(dllexport) int FriendsCount() {
    if (_Friends == nullptr) return 0;
    return 6; // which is a constant value in our example
  } //

  __declspec(dllexport) Friend *GetFriend(int xIndexer) {
    if (_Friends != nullptr) return _Friends;

    _Friends = new Friend[6];
    _Friends[0].name = Alloc(L"Michael Sylvester Gardenzio Stallone");
    _Friends[0].friendSince = 1946;

    _Friends[1].name = Alloc(L"Rocky Balboa");
    _Friends[1].friendSince = 1978;

    _Friends[2].name = Alloc(L"John Rambo");
    _Friends[2].friendSince = 1982;

    _Friends[3].name = Alloc(L"Ray Breslin");
    _Friends[3].friendSince = 2013;

    _Friends[4].name = Alloc(L"Gabe Walker");
    _Friends[4].friendSince = 1993;

    _Friends[5].name = Alloc(L"Ray Tango");
    _Friends[5].friendSince = 1989;

    return _Friends;  // be aware of the possible memory leak, you have to free lFriend
  } //

  __declspec(dllexport) void ReleaseMemory()  {
    for (auto &lBSTR : _BStrings) SysFreeString(lBSTR);
    if (_Friends != nullptr) delete[] _Friends;
    cout << endl << "C++: Memory released." << endl;
  } //

} // extern

example output:
C++: I am too lazy for MessageBoxes.
C++: Your message was: C#: Hello C++
C#: C++ replied -> C++: Good night!

C#: Number of friends: 0
C#: Fasten your seatbelts! This is hardcore C#

C#: Number of friends: 6

C#: since 1946 my best buddy is Michael Sylvester Gardenzio Stallone
C#: since 1978 my best buddy is Rocky Balboa
C#: since 1982 my best buddy is John Rambo
C#: since 2013 my best buddy is Ray Breslin
C#: since 1993 my best buddy is Gabe Walker
C#: since 1989 my best buddy is Ray Tango

C++: Memory released.

Advertisements

About Bastian M.K. Ohta

Happiness only real when shared.

Posted on March 20, 2014, in C#, C++, Java, Professional and tagged , , , , , , , , . Bookmark the permalink. Leave a comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

%d bloggers like this: