Try the following. First the D code:
module main;
import std.stdio;
import std.conv;
extern (C++) ulong receiveMe(ulong i);
extern (C++) ulong freeMe(ulong i);
void main() {
ulong l = receiveMe(0);
char* p = cast(char*)l;
char[] s = to!(char[])(p);
byte[] b = cast(byte[])(s.dup);
writeln("The answer is " ~ to!string(s));
ulong m = freeMe(0);
}
Then a C++/CLI shim:
#include "stdafx.h"
#using "...\CS-Interop\bin\x64\Debug\netstandard2.0\CS-Interop.dll"
using namespace System;
UInt64 sendMe(UInt64 arg) {
return CS_Interop::Test::receiveMe(42);
}
UInt64 freeMe(UInt64 arg) {
return CS_Interop::Test::freeMe(42);
}
Lastly the C#:
using System.Runtime.InteropServices;
using System.Text;
namespace CS_Interop {
public class Test {
public static byte[] buffer;
public static GCHandle gcbuf;
public static ulong receiveMe(ulong arg) {
string s = "I was a string " + arg;
s = (s.Length + 2) + s;
buffer = Encoding.ASCII.GetBytes(s);
gcbuf = GCHandle.Alloc(buffer, GCHandleType.Pinned);
ulong l = (ulong)gcbuf.AddrOfPinnedObject();
return l;
}
public static ulong freeMe(ulong arg) {
gcbuf.Free();
return 42;
}
}
}
I'm still looking at ways to get rid of that C++/CLI shim.
This code is written in such a way that you can poke around with the VS debugger.
This is very simple to set up and test in Visual Studio. With Visual D installed, first set up a C++/CLI project (NOT a Visual D project) and park the D and C++ code there. Then setup a C# DLL project under the D project.
It is one thing to call C# code from D, but another thing to get data back unless you are using only simple scalar types like int. The key lines of C# are
gcbuf = GCHandle.Alloc(buffer, GCHandleType.Pinned);
ulong l = (ulong)gcbuf.AddrOfPinnedObject();
where you first need to pin the thing you are sending back then send the address back to D. There is no tedious mucking about with marshaling in the C++ part, your D code just needs to be able to deal with whatever sits behind the pointer.
Be sure also to free the pinned pointer once you're done with it. Comment out the freeMe line in the D code and watch the memory usage grow (and grow) in VS.
Personally, I find the pin process a bit fickle as GCHandle.Alloc
will only work when its first argument, be it a byte array or structure, contains blittable items.
See also https://learn.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types