CppSharp icon indicating copy to clipboard operation
CppSharp copied to clipboard

How to implement delegates with std::string return type?

Open phraemer opened this issue 4 years ago • 7 comments

Delegates for types like int just work out of the box. E.g. __declspec(dllexport) int __cdecl SetCallback(int (*callback)(int arg));

Then in C# I can do

private static int MyCallback(int arg)
{
    return arg + 1;
}

SetCallback(MyCallback);

Now let's say we want strings instead of ints

__declspec(dllexport) void __cdecl SetCallback(std::string (*callback)(std::string arg));

Things get difficult when trying to return

private static unsafe __Internalc__N_std_S_basic_string__C___N_std_S_char_traits__C___N_std_S_allocator__C MyCallback(__Internalc__N_std_S_basic_string__C___N_std_S_char_traits__C___N_std_S_allocator__C arg)
{
    var argBasicString = global::Std.BasicString<sbyte, global::Std.CharTraits<sbyte>, global::Std.Allocator<sbyte>>.__CreateInstance(new global::System.IntPtr(&arg));
    var platformString = global::Std.BasicStringExtensions.Data(argBasicString);
    argBasicString.Dispose();

    var result = DoSomethingWithTheArg(platformString);

    // How to convert the System.string to the type needed or is this possible at all?
    return ...
}

Any hints how to proceed here would be very welcome.

phraemer avatar Sep 09 '20 15:09 phraemer

The only thing I could think of was to add a native function, void* AllocString(std::string newString), which takes a string, allocates a native string, returns a pointer to C#, and then return that pointer in the callback delegate instead of a string. On the native side I can delete it when finished.

phraemer avatar Sep 10 '20 08:09 phraemer

Ideally when we bind those kind of callbacks, the C# code would be dealing strictly with C# string, and not with the bound version of std::string. We have an issue related to it: https://github.com/mono/CppSharp/issues/511

But assuming we just want to work with the bound std::string manually, I think you could use the BasicStringExtensions.Assign method to create a new std::string from the managed side.

Also to note that the signatures of these more complex callback methods may vary between platforms/targets, for instance, the return type could be transformed to an instance return parameter, so that code may be tricky to get right manually if you need it to work across multiple targets (you may need to write multiple versions using compile time #ifs).

If the above issue was implemented, then that interop code (this callback function you are trying to implement) would be generated automatically, and it would be transparent to the C# code.

tritao avatar Sep 10 '20 11:09 tritao

Thanks for the feedback!

I think you could use the BasicStringExtensions.Assign method to create a new std::string from the managed side.

If I understand your suggestion correctly then the issue then becomes how to convert the BasicString to the std::string

e.g.

With callback in C++ defined as std::string (*callback)() we need this in C#

private static __Internalc__N_std_S_basic_string__C___N_std_S_char_traits__C___N_std_S_allocator__C MyCallback()
{
    var ret = new BasicString<sbyte, CharTraits<sbyte>, Allocator<sbyte>>();
    ret.Assign("hello");

    // Error: ret is a BasicString and not __Internalc__N_std_S_basic_string__C___N_std_S_char_traits__C___N_std_S_allocator__C
    return ret; 
}

phraemer avatar Sep 10 '20 11:09 phraemer

That's a good question, I have not really used the std::string support much, @ddobrev is the brain behind it.

But if I had to guess, maybe you can do something like:

var ptr = (__Internalc__N_std_S_basic_string__C___N_std_S_char_traits__C___N_std_S_allocator__C*) ret.__Instance.ToPointer();
return *ptr;

tritao avatar Sep 10 '20 12:09 tritao

Did you get it to work?

tritao avatar Sep 21 '20 10:09 tritao

Unfortunately not. I can have another attempt again today.

phraemer avatar Sep 21 '20 12:09 phraemer

That seems to do the trick!

phraemer avatar Sep 28 '20 06:09 phraemer