Website of Markus Griesser


Connecting C++ and C# - Part 3



Ay you have seen in Part 1 and Part 2 connecting managed and unmanaged code is not this complecated. But - yeah there is a but - we have done nothing with data. As you might want to pass some variables to c# and also want to get something back, you have to take care of a lot of things. I have slightly ripped the topic of marshalling in Part 1. But nothing compared to the whole thing.

We now extend the interface from Part 1 with several new functions.

      public interface IClass1 {
        // Gets a string and displays it
        void DisplayString(
          [MarshalAs(System.Runtime.InteropServices.UnmanagedType.BStr)]string s);
        // Gives back a string array
        string[] GetStringArray();
        // Gets a struct, change its content, and give it back
        void DisplayStruct([In, Out]MFCStruct s);
      }
    

Let's have a look at the above code. Function DisplayString is very straight forward. It gets a UNICODE string and displays it in a MessageBox.
GetStringArray() is intended to return several strings as an array.
The most interresting function is DisplayStruct which gets a struct from the c++ function, changes all of its content and gives it back. A complete round.
To make things more difficult, we will add a string member to our MFCStruct. It now looks like this:

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
      public class MFCStruct {
        public MFCStruct() {
            nI = 0;
            dL = 0.0;
        }
        [MarshalAs(System.Runtime.InteropServices.UnmanagedType.BStr)]
        public string s;
        public int nI;
        public double dL;
      }
    

Now the implementation of the above declared functions in our super class Class1:

      [ClassInterface(ClassInterfaceType.AutoDual)]
      public class Class1 : IClass1{
        public void DisplayString(
        [MarshalAs(
            System.Runtime.InteropServices.UnmanagedType.BStr)]string s) {
            MessageBox.Show(s);
        }
        
        public string[] GetStringArray() {
            string[] arr = new string[]{
                "From C# 1",
                "From C# 2",
                "From C# 3"
            };
            return arr;
        }
        
        public void DisplayStruct([In, Out]MFCStruct s) {
            MessageBox.Show(
                string.Format("String: {0}, Integer: {1}, Double: {2}",
                s.s, s.nI, s.dL));
            s.s = s.s.ToLower();
            s.nI *= 2;
            s.dL *= 3;
        }
      }
    

Looks very simple, isn't it?
Now the client side. How to call these functions:

      void CMFCSharpeningDlg::OnBnClickedButton2()
      {
        CoInitialize(NULL);

        IClass1Ptr pClass1(__uuidof(Class1));

        CString csTest = _T("Hello World");
        BSTR str = SysAllocString((BSTR)csTest.GetBuffer(0));
        pClass1->DisplayString(str);

        CoUninitialize();
      }
    

As you can see, I prefer to use BSTR. But it is not problem to use LPTWSTR or its ANSI brother LPTSTR. You only have to change the MarshalAs attribute (the same is true for the struct). Now the second function, which returns an array of strings:

      void CMFCSharpeningDlg::OnBnClickedButton6()
      {
        CoInitialize(NULL);

        IClass1Ptr pClass1(__uuidof(Class1));

        SAFEARRAY *arr = new SAFEARRAY;
        pClass1->GetStringArray(&arr);
        long length = 0;
        HRESULT hr = SafeArrayGetUBound(arr, 1, &length);
        for(long j=0;j<=length;j++){
            BSTR bstr;
            SafeArrayGetElement(arr, &j, &bstr);
            MessageBox(bstr);
        }

        CoUninitialize();
      }
    

A bit more complicated, because arrays are exported as COM-type SAFEARRAY. So you have to write some code to strip it back. Finally our struct:

      void CMFCSharpeningDlg::OnBnClickedButton7()
      {
        CoInitialize(NULL);

        IClass1Ptr pClass1(__uuidof(Class1));

        Sharp::MFCStruct newStruct;
        newStruct.s = SysAllocString((BSTR)_T("Hallo"));
        newStruct.nI = 12;
        newStruct.dL = 13.45;
        pClass1->DisplayStruct(&newStruct);

        MessageBox(newStruct.s);

        delete newStruct;

        CoUninitialize();
      }
    

I hope you got an impression on how the connection works. I have currently not solved every problem related to this topic for me, but every big achievment will be posted here.