2

I'm writing a C++ program that needs to call a DLL written in C#. I followed these instructions for creating my C# DLL and linking in to it from my C++.

https://support.microsoft.com/en-us/kb/828736

I have a C# function that takes a string as an argument. How do I pass a string out of C++ into my C#?

stranger
  • 390
  • 4
  • 17

1 Answers1

2

I couldn't find a concise answer to this question, so I'm putting my solution here in the hope that it helps someone in the future.

TL;DR: You need to use a BSTR to pass strings back and forth between C# and C++.

Here is how I did it.

C# Code

Here is a sample of my C# code. A few things to note:

  • Any function you want to be able to call from the C++ Must be delcared in the interface section.
  • Notice the way I declare the stringToPrint argument both in the interface and the function definition. Prefacing string with [MarshalAs(UnmanagedType.BStr)] is crucial.
  • Once in the function you can use the string argument as if it were a normal string. You don't need to convert from BSTR in C# the way you do in C++. More on that below.

.CS file

//Reference where I got all this:
//https://support.microsoft.com/en-us/kb/828736

// Class1.cs
// A simple managed DLL that contains a method to add two numbers.
using System;
using System.Runtime.InteropServices;


namespace ManagedDLL
{
    // Interface declaration.
    public interface ICalculator
    {
        //Test functions
        int Add(int Number1, int Number2);
        int ReturnAge();
        string StringTest();
        void PrintAString([MarshalAs(UnmanagedType.BStr)] string stringToPrint);
    };

    // Interface implementation.
    public class ManagedClass : ICalculator
    {
        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        //Test functions
        public int Add(int Number1, int Number2)
        {
            return Number1 + Number2;
        }

        public int ReturnAge()
        {
            return 35;
        }

        public string StringTest()
        {
            return "Can you hear me now?";
        }

        public void PrintAString([MarshalAs(UnmanagedType.BStr)] string stringToPrint)
        {
            Console.WriteLine("Trying to print a BSTR in C#");

            Console.WriteLine(stringToPrint);
            Console.WriteLine("Done printing");
        }
    }
}

C++ Code

A few things to notice in the C++:

  • The #import call in the header. This is where you tell the C++ how to find your C# library. This is mentioned in the tutorial I link to in the question.
  • If you have a return value in one of your C# functions, it won't come through to the C++ as a return. Instead you'll need to include a pointer to a C++ variable as a parameter when you make the call. See the AddTest() function for an example of this.
  • Strings must be passed to the C# as BSTR type variables. It's fairly easy to convert a std::string to a BSTR, I have functions for doing the conversion in either direction.

Header File

//csLink.h

#include <windows.h>
#include <iostream>
#include <string>

#import "path/to/C#/dll.tlb" raw_interfaces_only

using namespace std;

namespace Sample{

    class CSLink {
    public:
        CSLink();
        //Test functions
        int AddTest(int i1, int i2);
        int AgeTest();
        string StringTestCall();
        void stringArgTest(string s);

    private:
        ICalculatorPtr pCalc;
        long lResult;

        string convertBSTR(BSTR *s);
        BSTR convertBSTR(string s);

    };
}

Source file

//csLink.cpp

#include "stdafx.h"
#include "csLink.h"

using namespace std;

namespace Sample{
    //Constructor
    CSLink::CSLink(){
        cout << "You have created a CS Link" << endl;

        //https://support.microsoft.com/en-us/kb/828736
        HRESULT hr = CoInitialize(NULL);

        pCalc = ICalculatorPtr(__uuidof(ManagedClass));

        lResult = 0;
    }

    //Test functions
    int CSLink::AddTest(int i1, int i2){
        cout << "you are adding " << i1 << " and " << i2 << endl;

        pCalc->Add(i1, i2, &lResult);

        cout << "The result should have been " << i1 + i2 << " and it was " << lResult << endl;

        return 0;
    }

    int CSLink::AgeTest(){
        cout << "Trying to get my age" << endl;

        pCalc->ReturnAge(&lResult);

        return lResult;
    }

    string CSLink::StringTestCall(){
        BSTR s;
        pCalc->StringTest(&s);

        return convertBSTR(&s);
    }

    void CSLink::stringArgTest(string s)
    {
        //References I used figuring this all out:
        //http://stackoverflow.com/questions/28061637/how-to-pass-string-parameters-between-c-and-c
        //https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshalasattribute(v=vs.110).aspx
        //http://forums.codeguru.com/showthread.php?193852-How-to-convert-string-to-wstring
        //http://stackoverflow.com/questions/6284524/bstr-to-stdstring-stdwstring-and-vice-versa

        BSTR bSTR = convertBSTR(s);

        cout << "~~~~~~~~~~~~~~~~~~~~~~~" << endl;
        cout << "Testing conversion: " << convertBSTR(&bSTR) << "|end test" << endl;
        pCalc->PrintAString(bSTR);
        cout << "~~~~~~~~~~~~~~~~~~~~~~~" << endl;
    }

    //Utility functions
    string CSLink::convertBSTR(BSTR *s){
        if (*s == nullptr){
            return "NULL STRING";
        }
        else{
            wstring ws(*s, SysStringLen(*s));
            string ss(ws.begin(), ws.end());
            return ss;
        }
    }

    BSTR CSLink::convertBSTR(string s){
        wstring wStr = wstring(s.length(), L' ');
        copy(s.begin(), s.end(), wStr.begin());

        return SysAllocStringLen(wStr.data(), wStr.size());
    }
}

That's about it. Comment with any questions, I'll do my best to answer.

stranger
  • 390
  • 4
  • 17
  • thanks very much for posting this amazing code... I have been searching for this for a very long time. It works great. – windchaser Sep 17 '18 at 00:40
  • I'm glad it was helpful! I searched high and low trying to figure this out, so I'm glad I've been able to help you save at least a little time :) – stranger Sep 18 '18 at 01:15
  • Actually, I just ran into an issue when trying to deploy the exe and ComInterOp dll. The code runs well in the Dev machine but not in the client machine. Seems to be a registration issue. Not sure what I need to do. Any ideas? – windchaser Sep 19 '18 at 02:05
  • I remember something like that, and I found this in my old code: – stranger Sep 20 '18 at 14:25
  • //https://support.microsoft.com/en-us/kb/828736 //NB: Make sure to follow the directions in this article to REGISTER THE MANAGED DLL FOR USE WITH COM OR NATIVE C++!!!!!!!!!!!!! // RegAsm.exe mercuryLink.dll /tlb:mercuryLink.tlb /codebase // After doing this, copy the registered DLL into the executable folder – stranger Sep 20 '18 at 14:27
  • Sorry, it's deleting newlines. You can split that up by the //'s. I think that link talks you through it, but the short version is that you need to call that RegAsm.exe line. I don't remember what directory you call it from. And I think you need to do it in the VS terminal? – stranger Sep 20 '18 at 14:27