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