On Mon, 31 Jan 2011 16:25:11 -0500, Eelco Hoogendoorn <hoogendoorn.ee...@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;
            }
        }

Reply via email to