Thread overview
Ethan: About your wpf/C# and D integration
Aug 12, 2019
Bert
Aug 12, 2019
Ethan
Aug 12, 2019
a11e99z
August 12, 2019
One of the biggest issues I have with D is properly gui development. It's just a real pain in the ass compared to wpf and C#.

I'm curious about what you have done and if it would let me get the best of both worlds.

What I'd like to do is write the business end of apps in D and use C# for the gui(with possibly wpf hosting a D gui window in some cases for performance of graphics). I want to leverage D's meta programming to write efficient oop structures.

For it to work well the amount of boilerplate has to be minimized and the interfacing between D and C# also has to be minimal.

I'd have to be able to access the D types in C# somehow to access the data so it can be visualized. I'm thinking that D could emit some type of interface that allows referencing the objects in C# as if they were C#, more or less(doesn't have to be full blown, but at least fields and basic methods and properties).

I'm just not sure how much you've actually achieved in it or how well it works. If it's just as much trouble as using D directly(I use gtk and it works well for the most part but it's a little bit of pain in some ways).



August 12, 2019
On Monday, 12 August 2019 at 13:08:17 UTC, Bert wrote:
> What I'd like to do is write the business end of apps in D and use C# for the gui(with possibly wpf hosting a D gui window in some cases for performance of graphics). I want to leverage D's meta programming to write efficient oop structures.

Exactly what I'm doing. Client and server backends are written in D. Binderoo generates the C# <==> D interfaces.

There's a few traps I haven't worked out fully yet. Structs are a sticking point. I have them byte mapped exactly, but. Well. Here's an example: std.uuid.UUID

[ Serializable ]
[ StructLayout( LayoutKind.Explicit, Pack = 8 ) ]
public struct UUID
{
    public enum Version : int
    {
        unknown = -1,
        timeBased = 1,
        dceSecurity = 2,
        nameBasedMD5 = 3,
        randomNumberBased = 4,
        nameBasedSHA1 = 5,
    }
    //----------------------------------------------------------------

    public enum Variant : int
    {
        ncs = 0,
        rfc4122 = 1,
        microsoft = 2,
        future = 3,
    }
    //----------------------------------------------------------------

    //----------------------------------------------------------------

    // Methods
    //----------------------------------------------------------------

    public void swap( ref std.uuid.UUID rhs )
    {
        binderoointernal.FP.std_uuid_UUID_swap3( ref this, ref rhs );
    }
    //----------------------------------------------------------------

    public ulong toHash( )
    {
        return binderoointernal.FP.std_uuid_UUID_toHash4( ref this );
    }
    //----------------------------------------------------------------

    public string toString( )
    {
        return new SliceString( binderoointernal.FP.std_uuid_UUID_toString5( ref this ) ).Data;
    }
    //----------------------------------------------------------------

    //----------------------------------------------------------------

    // Properties
    //----------------------------------------------------------------

    public std.uuid.UUID.Variant variant
    {
        get { return binderoointernal.FP.std_uuid_UUID_variant1( ref this ); }
    }
    //----------------------------------------------------------------

    public bool empty
    {
        get { return binderoointernal.FP.std_uuid_UUID_empty0( ref this ); }
    }
    //----------------------------------------------------------------

    public std.uuid.UUID.Version uuidVersion
    {
        get { return binderoointernal.FP.std_uuid_UUID_uuidVersion2( ref this ); }
    }
    //----------------------------------------------------------------

    //Ridiculous fixed array preservation code for data of length 16
    [ FieldOffset( 0 ) ] private byte var_data_elem0;
    [ FieldOffset( 1 ) ] private byte var_data_elem1;
    [ FieldOffset( 2 ) ] private byte var_data_elem2;
    [ FieldOffset( 3 ) ] private byte var_data_elem3;
    [ FieldOffset( 4 ) ] private byte var_data_elem4;
    [ FieldOffset( 5 ) ] private byte var_data_elem5;
    [ FieldOffset( 6 ) ] private byte var_data_elem6;
    [ FieldOffset( 7 ) ] private byte var_data_elem7;
    [ FieldOffset( 8 ) ] private byte var_data_elem8;
    [ FieldOffset( 9 ) ] private byte var_data_elem9;
    [ FieldOffset( 10 ) ] private byte var_data_elem10;
    [ FieldOffset( 11 ) ] private byte var_data_elem11;
    [ FieldOffset( 12 ) ] private byte var_data_elem12;
    [ FieldOffset( 13 ) ] private byte var_data_elem13;
    [ FieldOffset( 14 ) ] private byte var_data_elem14;
    [ FieldOffset( 15 ) ] private byte var_data_elem15;
    //Ridiculous fixed array preservation code for ulongs of length 2
    [ FieldOffset( 0 ) ] private ulong var_ulongs_elem0;
    [ FieldOffset( 8 ) ] private ulong var_ulongs_elem1;
    //----------------------------------------------------------------

}

C#'s types and D's don't exactly match (ESPECIALLY if you run on Windows vs any other platform). If I want to match the C ABI that D adheres to, I have to go full paranoid and define an explicit pack.

Then there's things like strings. I've still not come up with a way that I'm satisfied with to deal with allocated data being passed between both languages and not being garbage collected. You could take the "GC pin" route. But I'm not satisfied with that for parameter passing. There's no guarantees a user will or won't keep a string on either side of the language divide.

I'm still pondering on that one. I'm taking a convention of array.dup in my D code whenever assigning a slice in a C# exposed function. Full solution to be determined.

> For it to work well the amount of boilerplate has to be minimized and the interfacing between D and C# also has to be minimal.

Absolutely. The original goal of Binderoo was to make C++ and D interoperation and hot reloading seamless. Since I left Remedy, that goal has now become to make language barriers irrelevant for any supported language.

But for my use cases, I've mostly focused on exposing D to C#. I have not worked on the obverse yet. But I do have my D code calling C# code through delegates and function pointers. That's led to some hilarity in C#. Did you know C# will collect locally defined delegates and marshalled pointers to that delegate during a thread sleep? Thanks C#. Even D knows how to scan the stack for managed pointers.

> I'd have to be able to access the D types in C# somehow to access the data so it can be visualized. I'm thinking that D could emit some type of interface that allows referencing the objects in C# as if they were C#, more or less(doesn't have to be full blown, but at least fields and basic methods and properties).

Classes are actually in a reasonably good state. For my use case, at least.

In both C# and D, they're reference types where storage is not defined by the user. Trying to keep binary compatibility between the two is going to be a nightmare. Solution? The D code generates properties for publicly accessible variables, and just binds methods otherwise.

I support inheritance, but as I don't use interfaces myself (metaprogramming and static branching all the way) there's zero support for them.

The implementation details is that Binderoo allocates a class in native code and the pointer gets stored in C#. I don't let the D GC manage that memory at all, Binderoo allocs/emplaces/destroys everything itself. All the properties and methods call delegates I store elsewhere, basically exactly like that code you see in the UUID example above.

> I'm just not sure how much you've actually achieved in it or how well it works. If it's just as much trouble as using D directly(I use gtk and it works well for the most part but it's a little bit of pain in some ways).

One implementation detail I need to talk about.

I don't bind XAML directly to my D representations.

Binding properties in XAML is weird. You can do it with nullable properties (ie append a ? to your value type property, now you've got a nullable property) except for when they don't work. So you can implement the INotifyPropertyChanged interface and force XAML bindings to refresh whenever your property changes, except for when they don't work. So you go brute force and use dependency properties, which are a ton of boilerplate and copy the value off somewhere outside of your class instance - but they work 100% every time.

My D code has a separate function that generates object wrappers for XAML binding that stores an internal instance of the D class. Every property gets its own dependency property.

This actually turned out to be an advantage as far as using WPF is concerned. Because you'll only get the value changed callback for your dependency property *if* the value actually changes when your XAML form sets a value. If it's the same value, you don't get the callback. And since my WPF UI is a frontend for a client, that means that WPF automatically does the job of working out when I should send new data to the server for me. The value changed callbacks invokes the writing API, I set the new values on my D object, and then the client kicks off the changes to the server. Server rejects those changes? I force the XAML binding object to refresh its values and they all get flushed out to the UI.

The extra object is pretty specific to my use case. It should be quite alright to generate dependency properties on a D object and do it that way.
August 12, 2019
On Monday, 12 August 2019 at 13:08:17 UTC, Bert wrote:
> One of the biggest issues I have with D is properly gui development. It's just a real pain in the ass compared to wpf and C#.

maybe it is better to use WinRT instead of C#/WPF?
- can be used for desktop apps too
- same XAML
- WinRT based on COM-model so it can be simpler interop
  (not sure how to transfer POD types. maybe streaming?)
- no need pinning from CLR side
- definitions can be generated from WinMD metadata files