0

I have this tutorial working: https://www.red-gate.com/simple-talk/dotnet/net-development/creating-ccli-wrapper/

That tutorial uses 3 Visual Studio projects in one Solution. The "Core" project is the native C++ side. The "Wrapper" project is the C++/CLI "bridge". And the "Sandbox" project is the C# side.

Now I am trying to modify this to work with a C++ function that I added to the Core, but my new Wrapper methods and properties are not showing up in C#. My end-goal is for a C# application send text to a C++ program, the C++ program then query a database, and return the first 20 records that match the text. For now, I just want to send the C++ class a string and an integer, and for it to return a vector of the string repeated integer number of times.

I expected that I would be able to create a new property in the Wrapper, and it would show up in C#. I have the property pointed to a function in Core, and the only significant difference between the working properties/functions and the failing one is the types being used. In the Wrapper project header file, I added my function like this:

void TypeAhead( std::string words, int count );

In the Wrapper .cpp file, I added this:

void Entity::TypeAhead( std::string words, int count )
{
    Console::WriteLine( "The Wrapper is trying to call TypeAhead()!" );
    m_Instance->TypeAhead( words, count );
}

I have matching functions in the Core project. In Program.cs, the Entity class object is able to use the properties and functions from the tutorial, but not the ones I have added. What do I need to change to get properties and functions from the Wrapper project to be usable in the Sandbox project?

My repo can be found here: https://github.com/AdamJHowell/CLIExample

Adam Howell
  • 415
  • 1
  • 11
  • 24
  • 1
    There is no interop story for native C++ objects, that's why you are writing a wrapper. All C++ objects, an std::string as well. You don't have to wrap it yourself, the built-in System::String can do it. But you have to add the glue to convert from String^ to std::string, marshal.h makes it a one-liner. Beware that it is a lossy conversion. – Hans Passant Mar 17 '19 at 21:57

2 Answers2

1

That function signature isn't compatible with C#, because it passes a C++ native type by value.

The signature you're looking for is

void TypeAhead( System::String^ words, int count );

and you will need to convert from the .NET String to a C++ std::string before calling the core function.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
1

The problem is that std::string is not a valid type when attempting to expose to .NET. It is a pure c++ beast.

Change:

void Entity::TypeAhead( std::string words, int count )
{
    Console::WriteLine( "The Wrapper is trying to call TypeAhead()!" );
    m_Instance->TypeAhead( words, count );
}

...to:

void Entity::TypeAhead( String^ words, int count )
{
    Console::WriteLine( "The Wrapper is trying to call TypeAhead()!" );

    // use your favourite technique to convert to std:string, this 
    // will be a lossy conversion.  Consider using std::wstring.
    std::string converted = // ...
    m_Instance->TypeAhead(converted, count );
}

Use std::wstring internally instead

As indicated by Tom’s fine comments below, you might want to consider using wstring due to possible fidelity loss in the conversion from .NET strings to std::string. To convert see the link below.

  • 2
    There is another significant issue. `std::string` can be text using any character encoding. .NET's `String` uses UTF-16. Any marshaling is going to convert between character encoding. You might need to specify which character encoding to convert to/from. And, if from UTF-16 to anything other than UTF-8, it's going to be lossy. You might need to consider using std::wstring instead. – Tom Blodget Mar 17 '19 at 15:30
  • @TomBlodget whilst true, the essence of the problem is _what type to use to expose to .NET_. What type the OP uses on the c++ side afterwards is an exercise for the reader. Also, the link provided includes a `wstring` conversion –  Mar 17 '19 at 16:22
  • 1
    Do NOT use `Marshal::StringToHGlobalAnsi` and `Marshal::FreeHGlobal` for this. They are not exception safe. (And the names are just atrocious, since they do not work on `HGLOBAL` handles at all). C++/CLI provides a perfectly good exception-safe conversion named `marshal_as` which I linked to. This question is not about different string conversion techniques -- it would be a duplicate if it were. In conclusion, the only things present in your answer not already covered in my earlier one are strictly negative. – Ben Voigt Mar 17 '19 at 17:13
  • @MickyD: If you'll actually read the linked question, you'll find that both methods are found on MSDN (yours is listed at the linked question too, and downvoted for good reason). It is really bad to have this "how to convert strings" discussion separately on a hundred different questions because then the bad answers have to be cleaned up a hundred times. Trust the answers on the dedicated questions that specifically compared different conversion techniques, where the experts showed up and voted. – Ben Voigt Mar 17 '19 at 20:25