0

I'm trying to set up a test project to figure out how to correctly wrap a native C++ lib in a managed C++ DLL, to then be called by a C# application.

For my test case I have 3 application:

CPP lib

I compile a library with a class containing four functions.

1) return a double value

2) return a string

3) take a double by reference, modify it.

4) take a string by reference, modify it.

cpp_lib.h

namespace cpp_lib
{
   class CPPLibFunctions{

   public:
      CPPLibFunctions();
   public:
      static double getValue();
      static void incrementValue(double& val);

      static std::string getText();
      static void changeText(std::string& txt);
   };
}

cpp_lib.cpp

#include "cpp_lib.h"
#include <stdexcept>

namespace cpp_lib{
   CPPLibFunctions::CPPLibFunctions(){};

   double CPPLibFunctions::getValue(){
      return 5.5;
   }
   void CPPLibFunctions::incrementValue(double& val){
      val += 1.0;      
   }
   std::string CPPLibFunctions::getText(){
      return "success";
   }
   void CPPLibFunctions::changeText(std::string& txt){
      txt += "_123";
   }
}

Managed CPP lib

This is a managed C++ project compiled to a DLL, which wraps the functions from cpp_lib.

managed_cpp_lib.h

#include "cpp_lib.h"
#pragma once    
#include <msclr\marshal_cppstd.h>

using namespace System;

namespace managed_cpp_lib {

   System::String^ stdToSysString(std::string str){
      return gcnew System::String(str.c_str());
   }

    public ref class ManagedCppFunctions
    {
   private:
      cpp_lib::CPPLibFunctions * cppLibFunctions;
   public:
      ManagedCppFunctions(){
         cppLibFunctions = new cpp_lib::CPPLibFunctions();
      }

      double getValue(){         
         return  cppLibFunctions->getValue();
      }
      void incrementValue(double& val){
         cppLibFunctions->incrementValue(val);
      }
      System::String^ getText(){
         return stdToSysString(cppLibFunctions->getText());
      }
      void changeText(System::String^ txt){
         //this does not work:
         std::string txtstd = msclr::interop::marshal_as<std::string>(txt);
         cppLibFunctions->changeText(txtstd);
         txt = stdToSysString(txtstd);
      }
    };
}

csharp_app

Lastly, cssharp_app is a C# console application, which references managed_cpp_lib, and calls the functions.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using managed_cpp_lib;

namespace csharp_app
{
    class Program
    {
        static void Main(string[] args)
        {
            managed_cpp_lib.ManagedCppFunctions managedCppFunctions = new managed_cpp_lib.ManagedCppFunctions();

            //works:
            double val = managedCppFunctions.getValue();
            Console.WriteLine("\managedCppFunctions.getValue() = {0}", val);

            //works:
            string txt = managedCppFunctions.getText();
            Console.WriteLine("\managedCppFunctions.getText() = {0}", txt);

            //does not work:
            /*managedCppFunctions.incrementValue(val);
            Console.WriteLine("\managedCppFunctions.incrementValue(val) = {0}", val);*/

            //the string is not modified:
            managedCppFunctions.changeText(txt);
            Console.WriteLine("\managedCppFunctions.changeText(txt) = {0}", txt);

            Console.WriteLine("...");
        }
    }
}

The output of csharp_app is:

managedCppFunctions.getValue() = 5.5
managedCppFunctions.getText() = success
managedCppFunctions.changeText(txt) = success

So managedCppFunctions.getValue() and managedCppFunctions.getText() works.

managedCppFunctions.changeText(txt) does not modify the content of the string.

I'm not sure how to implement managedCppFunctions.incrementValue(val).

What is the correct way to pass a double and a string by reference, to then change their values using managed C++ and C#?

remi
  • 937
  • 3
  • 18
  • 45
  • If you want to change a string, pass a `StringBuilder` instead of a `string` - the marshaller will handle this for you. To change a value type, you must declare the parameter as `ref`: `void incrementValue(ref double val)` – Matthew Watson Jul 06 '18 at 07:49
  • I get `ref is undefined` if I do `void incrementValue(ref double val)` in managed_cpp_lib... – remi Jul 06 '18 at 08:01
  • I meant in the C# code. I think you'll need to put `void incrementValue(double& val)` in the C++. – Matthew Watson Jul 06 '18 at 08:03
  • Hmm.. That functions is not defined in the C# code. It's defined in the C++ lib, then wrapped in the managed C++ code, which is called by the C# code. – remi Jul 06 '18 at 08:05
  • I can see a `void incrementValue(double val)` in your managed C++ code. That needs to change to `void incrementValue(double& val)` – Matthew Watson Jul 06 '18 at 08:06
  • That is true. I must have accidentally removed it while testing. I edited the code. The error I get then in managed cpp is : `The best overloaded method match for managed_cpp_lib.ManagedCppFunctions.incrementValue(double*) has some invalid arguments` – remi Jul 06 '18 at 08:10
  • Hmm, I've no idea why you're getting that error - is the header file correct? Anyway, to change a double passed as a parameter it will have to be either `double&` or `double*`. – Matthew Watson Jul 06 '18 at 08:15
  • I managed now to make the double by reference work. In the managed cpp I tried with two functions: `void incrementValue(double& val)` and `void incrementValuePtr(double* val)`. They can both be called with `managedCppFunctions.incrementValue(&val)` and `managedCppFunctions.incrementValuePtr(&val)`, if I put them in `unsafe` context. But I'm still not sure how to work with `String` by reference or `StringBuilder` – remi Jul 06 '18 at 08:49
  • It should work similarly to the Windows API GetWindowText() - [here's the PInvoke.net page for it](http://www.pinvoke.net/default.aspx/user32/GetWindowText.html). – Matthew Watson Jul 06 '18 at 09:47

0 Answers0