C# Interop
Robert Jacques
sandford at jhu.edu
Mon Jan 31 19:05:08 PST 2011
On Mon, 31 Jan 2011 16:25:11 -0500, Eelco Hoogendoorn
<hoogendoorn.eelco at gmail.com> wrote:
> Hi all,
>
> At work I currently develop in C++ with a C# front-end, using CLI
> interop.
>
> Ive been using D for quite a while for hobby projects, and needless to
> say, it
> makes C++ feel like masochism. Id like to convince my coworkers that
> adding D
> in the mix is more than just another complication.
>
> But im not quite sure what would be the best way to do C# and D interop.
> CLI
> will no longer do me any good I fear.
>
> Do I just create a D DLL with a bunch of free extern(C) function for
> communication?
Basically, yes. I've been doing this for a project and it works reasonably
well. I have one module based on From D's/bugzilla's public domain example
code to properly enable the dll and then declare the interop functions as
export extern(C) {}. There's also some general helper functions you'll
want to write. Although exceptions can propagate to .NET, they just turn
into a System.Runtime.InteropServices.SEHException exception, so you'll
want to wrap all your export function in a try-catch block and save the
error message to a global variable. Then you can call a lastError function
to get the actual error string. Oh, and remember .NET defaults to
wstrings, not strings. The other helper function you'll want is a way to
pin and unpin objects as D's GC can't see C#'s memory.
> What about marshalling?
C# marshaling, though I'm glad it's there, involves pulling teeth to do
anything other then calling basic C system calls. Lucky, you really only
need it for arrays and structs. Objects can be be treated as handles
inside a proxy C# object. And then you can handle methods as free
functions whose first argument is the object's handle. But you'll also
have to write a C# proxy object + pin/unpin the D object. If you're doing
a lot of this, I'd recommend writing a mixin to generate all the free
functions on the D side, and looking into the dynamic language features of
C# to write an auto-wrapping proxy object. (I haven't needed to do this
yet)
> Is using unsafe pointers back and forth
> the best I can do? C# can read from a pointer allocated by D in unsafe
> mode,
> and D can read from a pinned C# pointer, right?
I don't know if it's the best you can do, but it does work.
> (no, im not looking for D.NET; what I miss in C# is to-the-metal /
> compilation.)
Lastly, D DLLs will only work on Vista/Windows 7/later. They will not work
on XP. This is due to a long known bug with DLLs and thread local storage
in general on XP. Also, you'll have to use 32-bit C# currently, as DMD
isn't 64-bit compatible yet. (Walter is hard at work on a 64-bit version
of DMD, but it will be Linux only at first, with Windows following
sometime later)
I've listed some example code from my project below:
// Written in the D Programming Language (www.digitalmars.com/d)
///Basic DLL setup and teardown code. From D's/bugzilla's public domain
example code.
module dll;
import std.c.windows.windows;
import std.c.stdlib;
import core.runtime;
import core.memory;
extern (Windows)
BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved) {
switch (ulReason) {
case DLL_PROCESS_ATTACH:
Runtime.initialize();
break;
case DLL_PROCESS_DETACH:
Runtime.terminate();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
return false;
}
return true;
}
D code:
private {
wstring last_error_msg = "No error"; /// The last
encountered by the dose server
__gshared Object[Object] pinned; /// Hash of
pinned objects
void pin(Object value) { pinned[ value ] = value; } /// Pin an
object
/// Stores a string as the last error
void lastError(string str) {
wstring err;
auto app = appender(&err);
foreach (dchar c; str) app.put(c);
last_error_msg = err;
enforce(false);
}
}
export extern(C) {
ref wstring lastError() { return last_error_msg; } /// returns:
the last error message, used for exception marshaling
}
C# code:
[DllImport(gpu_dll)]
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef =
typeof(DString))]
private static extern string lastError();
/// <summary>
/// A custom marshaller for D's strings (char[]) passed by ref
/// </summary>
private class DString : ICustomMarshaler
{
//[ThreadStatic]
//private static ICustomMarshaler _instance = new DString();
private IntPtr ptr = IntPtr.Zero;
/// <summary>
/// Factory method
/// </summary>
/// <param name="pstrCookie"></param>
/// <returns></returns>
static ICustomMarshaler GetInstance(String pstrCookie)
{
return new DString();
}
/// <summary>
/// Convert a pointer to a D array to a managed T[]
/// </summary>
/// <param name="pNativeData"></param>
/// <returns></returns>
public Object MarshalNativeToManaged(IntPtr pNativeData)
{
Int32 length = Marshal.ReadInt32(pNativeData, 0);
IntPtr data = Marshal.ReadIntPtr(pNativeData, 4);
char[] result = new char[length];
Marshal.Copy(data, result, 0, length);
return new string(result);
}
/// <summary>
/// Convert a managed T[] to a pointer to a D array
/// </summary>
/// <param name="ManagedObj"></param>
/// <returns></returns>
public IntPtr MarshalManagedToNative(Object ManagedObj)
{
char[] managed = ((string)ManagedObj).ToCharArray(); ;
ptr = Marshal.AllocHGlobal(8 + managed.Length * 2); //
unicode = 16 bytes, sigh
IntPtr data = (IntPtr)((UInt32)ptr + 8);
Marshal.Copy(managed, 0, data, managed.Length);
Marshal.WriteInt32(ptr, 0, managed.Length);
Marshal.WriteIntPtr(ptr, 4, data);
return ptr;
}
/// <summary>
/// Delete the marshaled D array
/// </summary>
/// <param name="pNativeData"></param>
public void CleanUpNativeData(IntPtr pNativeData)
{
if (ptr == pNativeData)
Marshal.FreeHGlobal(pNativeData);
}
/// <summary>
/// Clean up managed data (i.e. do nothing)
/// </summary>
/// <param name="ManagedObj"></param>
public void CleanUpManagedData(Object ManagedObj) { }
/// <returns>The size of a D array struct (8)</returns>
public int GetNativeDataSize()
{
return 8;
}
}
More information about the Digitalmars-d
mailing list