1

So I am trying to write my first program using P/Invoke to create a wrapper around a c++ library to allow use in a c# application. One of the features of the library is it takes a callback function (delegate), with the signature void callback_func(string log, int time).

I am trying to determine how I can wrap this so that the C# library can process std::strings handed to this function by the c++ library.

I tried look at this Passing strings from C# to C++ DLL and back -- minimal example, however I couldn't determine how this would apply in the this context, where a delegate is being provided to the library.

The delegate function is as follows:

C# App

public delegate void log_delegate(string log, int time);
static void logFunc(string log, int time)
{
    Console.WriteLine($"{log} {time}");
}

And it is passed to the C++ library as a argument in the constructor of an object Foo, which has member functions that will call this function

C# app

    log_delegate my_del = logFunc;
    IntPtr foo = Foo_Create(6, 6, my_del);

The C++ library currently calls the callback function, handing in relevant logging information as a string in the first parameter.

Currently, I just get garbage values back however, (i.e. " ►>üv⌂☻")

  • Possibly due to C# UTF-16LE character encoding. Have a look at the bytes you get back to C# to know for sure. – Bathsheba Jun 28 '22 at 14:03
  • 1
    `void callback_func(string log, int time)` -- If that is the C++ code, it is a non-starter as to how to make Pinvoke work on this directly. The first argument is a `std::string`, and has absolutely nothing to do with C# `string` type. A `std::string` is only known by the C++ language, and is only internally can be used by the C++ compiler it was designed for. So that callback was designed only for usage *within* the C++ application -- it is not meant to be used externally by any other language. If the first argument were `const char*` or `LPCSTR` or similar, *then* we have a valid case. – PaulMcKenzie Jun 28 '22 at 14:32
  • 1
    Any reasonably competent C++ programmer can know that std::string is not compatible with anything. So bad choice for an api, you shouldn't have any trouble asking for a const char* flavor of the callback. – Hans Passant Jun 28 '22 at 15:43
  • C++ doesn't have `string`, it does have `std::string` and `char*`. So what is the *real* callback definition? – Charlieface Jun 28 '22 at 16:21

1 Answers1

1

Every time you travel from C# to C++ and back, you should remember there are different memory managers. This means you'll copy your strings every time you cross the borders. And you'll need a C++/CLI wrapper lib that can play as a translator between the worlds (add a separate project, it can reference both .NET and C++ classes). Inside the lib, you'll need a code like this:

void logFuncBridge(wchar_t *strCpp, int time) // called by C++
{
    String^ strDotnet = gcnew String(strCpp);
    logFunc(strDotnet, time); // invokes C#
}
Yury Schkatula
  • 5,291
  • 2
  • 18
  • 42
  • C++/CLI definitely makes life better, but it's possible to do this without. The shim layer is not optional though, something is needed to convert C++ `std::string` to one of the simple types supported in the ABI. Your answer skips that step, and starts out directly with `wchar_t*`, which is a type already supported by p/invoke. – Ben Voigt Jun 28 '22 at 14:56
  • So I am trying to do it without C++/CLI just to reduce additional dependencies. So could I convert the C++ string into a char* or something in this function, call the C# delegate handing it the char*? – user19435638 Jun 28 '22 at 16:53
  • @user19435638 -- *could I convert the C++ string into a char* or something in this function* -- You want to call `c_str()`, as that is the buffer. Once you do that, then the C# code doesn't care how that buffer was created by C++ or whether C++ was even used to create the buffer, as long as it is a contiguous array of characters. But the underlying issue is that a `std::string` is *not* a character buffer, but a class that wraps a character buffer, and to get to that buffer, `c_str()` or `data()` is used. – PaulMcKenzie Jun 28 '22 at 17:30
  • @user19435638 avoiding C++/CLI here is "asking for trouble", please don't – Yury Schkatula Jun 29 '22 at 07:40
  • Out of curiosity, why is that? I have it working using the method I described in the previous comment, and I only need to expose about 5 functions to the .NET app. Is there a reason C++/CLI should absolutely be used if it works fine without it? – user19435638 Jun 29 '22 at 14:28
  • @user19435638 imagine you changed any signature on one side only. C++/CLI project is aware of both worlds so can rise a compiler error. Having no such project would hide the error until run-time phase. – Yury Schkatula Jul 01 '22 at 17:34