Thread overview
What exactly gets returned with extern(C) export string func() ?
Jun 13, 2021
cc
Jun 13, 2021
frame
Jun 16, 2021
cc
Jun 16, 2021
frame
June 13, 2021

D under dmd/Win10/64-bit currently seems to store strings (slices) internally like so:

static struct DString {
	size_t length;
	immutable(char)* ptr;
}
static assert(DString.sizeof == string.sizeof);
string s = "abcde";
DString d;
memcpy(&d, &s, s.sizeof);
assert(d.length == s.length);
assert(d.ptr == s.ptr);

If I write a DLL export like:

extern(C) export string someDLLFunc() {
	return "hello";
}

and import it in C# (VS .NET):

struct DString {
	public long length;
	public IntPtr ptr;
}
[DllImport("mydll.dll")]
extern DString someDLLFunc();
...
DString d = someDLLFunc();
System.Console.WriteLine("dstr: {0} : {1}", d.length, d.ptr);

Though C# seems to properly be getting 16 bytes as the return value from someDLLFunc(), the length and ptr parameters are both 0.

If I explicitly pass a struct back from D instead like so:

static struct DString {
	size_t length;
	immutable(char)* ptr;
	this(string str) {
		length = str.length;
		ptr = str.ptr;
	}
}
extern(C) export DString someDLLFunc() {
	return DString("hello");
}

it seems to work as expected with the same C# code. Does D explicitly disallow slices as an extern(C) export parameter type?

June 13, 2021

On Sunday, 13 June 2021 at 10:02:45 UTC, cc wrote:

>

it seems to work as expected with the same C# code. Does D explicitly disallow slices as an extern(C) export parameter type?

The spec says that there is no equivalent to type[]. You get a type* instead.

June 16, 2021

On Sunday, 13 June 2021 at 21:13:33 UTC, frame wrote:

>

On Sunday, 13 June 2021 at 10:02:45 UTC, cc wrote:

>

it seems to work as expected with the same C# code. Does D explicitly disallow slices as an extern(C) export parameter type?

The spec says that there is no equivalent to type[]. You get a type* instead.

I can't seem to get it to work as a return type, but interestingly it does work as an out/pass by ref parameter.

D:

export void D_testString(out string ret) {
	ret = "hello".idup;
}

C#:

public struct DString {
	public ulong length;
	public IntPtr ptr;
	public string str {
		get {
			byte[] b = new byte[length];
			for (int i = 0; i < (int)length; i++) {
				b[i] = Marshal.ReadByte(ptr, i);
			}
			return Encoding.UTF8.GetString(b);
		}
	}
}
[DllImport("test.dll")]
private static extern void D_testString(out DString dd);
public static string testString() {
	DString d;
	D_testString(out d);
	return d.str;
}
June 16, 2021

On Wednesday, 16 June 2021 at 02:46:36 UTC, cc wrote:

>

I can't seem to get it to work as a return type, but interestingly it does work as an out/pass by ref parameter.

Probably for returning the struct it needs some allocation directive in C# but I'm not sure. Maybe C# also tries something to do with the IntPtr. It may work if you use a struct on the DLL side too and convert any specific things like pointers to simple integers instead.

The out keyword however basically is just a pointer to the struct, so reading directly from the memory offset from the DLL works instead.

As long the GC sees the data alive - .(i)dup doesn't protect your memory here. The data could be collected by the GC anytime the function call goes out of scope.