1

I am trying to get ANGLE working in C# using P/Invoke. Basically, I am creating a simple 2D surface, and then passing that off to skia (using SkiaSharp). Everything is working and all that, but I am having a problem marshalling PropertySet to the unmanaged code.

This bit works fine:

// the properties
var props = new PropertySet();
props.Add("EGLNativeWindowTypeProperty", swapChainPanel);
// the surface attributes
int[] surfaceAttrs = {
    EGL_ANGLE_SURFACE_RENDER_TO_BACK_BUFFER, EGL_TRUE,
    EGL_NONE
};

// create the surface
surface = eglCreateWindowSurface(eglDisplay, eglConfig, props, surfaceAttrs);

My import looks like this:

[DllImport("libEGL.dll")]
public static extern IntPtr eglCreateWindowSurface(
    IntPtr dpy, 
    IntPtr config, 
    [MarshalAs(UnmanagedType.IInspectable)] object win, 
    int[] attrib_list);

The problem comes in when I am trying to set my scaling for high resolution screens. This "should" work:

var scale = PropertyValue.CreateSingle(2);
props.Add("EGLRenderResolutionScaleProperty", scale);

It works when using C++, but not in C#. I think I have got it down to the fact that the actual value is not being marshalled correctly. This is because when debugging in ANGLE code, it dies over here:

ComPtr<ABI::Windows::Foundation::IPropertyValue> propertyValue;
ABI::Windows::Foundation::PropertyType propertyType;
// ... 
result = propertyValue->get_Type(&propertyType);

https://github.com/Microsoft/angle/blob/54b1fd01f7fddcd7011d5a04e9259edace8a13da/src/libANGLE/renderer/d3d/d3d11/winrt/InspectableNativeWindow.cpp#L242

The runtime exception is:

Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.

Are there any tips, or solutions?

Here is all my code for reference: https://gist.github.com/mattleibow/eacb9c9e87f306b218d99c713c532a82

Original ANGLE issue: https://github.com/Microsoft/angle/issues/89

EDIT

After further investigation, I found that for some reason PropertyValue in C# is different to the same type in C++ (via Windows Runtime Component).

I tried this:

static PropertySet^ CreateSurface(SwapChainPanel^ panel, float scale)
{
    PropertySet^ surfaceCreationProperties = ref new PropertySet();
    surfaceCreationProperties->Insert(L"EGLNativeWindowTypeProperty", panel);
    Object^ scaleVal = PropertyValue::CreateSingle(scale);
    surfaceCreationProperties->Insert(L"EGLRenderResolutionScaleProperty", scaleVal);
    return surfaceCreationProperties;
}

This method created a PropertySet in C++ and then returns that to C#. This works fine and my ANGLE session is all good.

Now, if I change the code to return the PropertyValue::CreateSingle(scale) directly, this fails again. If I pass the PropertyValue.CreateSingle(scale) from C# into this C++ method, I find that the objects are not similar.

In the debugger locals I get this for the object constructed in C#:

0x09f10ba4 <Information not available, no symbols loaded for coreclr.dll>
Platform::Object ^

If the object is constructed in C++, I get this:

0x019162e8 2.00000000
Platform::Object ^ {WinTypes.dll!Windows::Foundation::Value<Windows::Foundation::ValueScalar<float> >}

Shouldn't the object be the same, since they are the same type? What is more scary is that if I pass this value across the boundary, the type changes.

Removed edit 2 and set it as an answer.

Matthew
  • 4,832
  • 2
  • 29
  • 55
  • Getting a stack imbalance is quite hard to explain. You are not supposed to use PropertySet in a C# program since it is a language projection helper that's specific to C++. The C# projection is Dictionary. Some more about this in [this post](http://stackoverflow.com/a/39154410/17034), including the "don't do this!" explanation. Also pretty hard to guess why you think skia will be used, it angle work properly then you'll get DirectX calls. So you might as well skip all the horrid glue. – Hans Passant Sep 11 '16 at 22:05
  • Thanks for the reply, but I am having an issue with marshalling `PropertySet`/`PropertyValue`. After further investigation, there seems to be a difference between the managed C++ and C# versions of the same type. I created a Runtime Component and things still fail - see my edits – Matthew Sep 12 '16 at 10:44
  • I see there is a close request :( this is not quite the same as the other issue. This has to do with marshalling `PropertyValue`, and not directly ANGLE anymore. Everything else seems to be working fine. – Matthew Sep 12 '16 at 11:04

2 Answers2

1

I'm not answering your question directly.

But a proven-working alternative way is to create a C++ WindowsRuntime Component wrapper project, aka, use C++/CLI(or CXX, should I say?)to do the interop.

You can expose methods like below, expose APIs depending on your needs.

void InitializeEGL(Windows::UI::Core::CoreWindow^ window);
void InitializeEGL(Windows::UI::Xaml::Controls::SwapChainPanel ^ window);

or, if you want to have finer granularity, declare something like below in the C++ WinRT component,

void EglCreateWindowSurface(Platform::IntPtr display, Platform::IntPtr config, Windows::Foundation::Collections::PropertySet^ propertySet);

This little trick will enable you to get around the C# marshaling issue, and I have verfied it to work.

And I'll continue to investigate the C# interop approach.

Jackie
  • 2,032
  • 1
  • 12
  • 14
  • Thanks for this. I eventually ad to make a WinRT Component. I created a C API so I could P/Invoke (which I need). I tried to use COM directly, but ran into an issue since `IPropertyValue` already exists in the managed area. However, this does not work the same. When marshalling the result from `PropertyValue::CreateSingle`, it converts it into a `float`, and thus looses the actual identity and several members, such as the `get_Type` method which ANGLE requires. – Matthew Sep 14 '16 at 07:11
  • Basically, in C++ the result of `CreateSingle` is `ValueScalar`, but in C# it is just a plain `float` for some reason. – Matthew Sep 14 '16 at 07:12
  • I am marking this as answered since this is what I had to do. If ever there is a better way, then we can always come back. Thanks. – Matthew Sep 19 '16 at 16:25
0

After doing a bit of searching, I found the best (and only) way to do this was to create a Windows Runtime Component.

As I could not have a "hard" reference to the component - this library must work without ANGLE present. I opted for a small component with a C API so that I could just P/Invoke it when I needed it. This was my method in C++:

void PropertySetInterop_AddSingle(ABI::Windows::Foundation::Collections::IPropertySet* propertySet, HSTRING key, float scale)
{
    using namespace Microsoft::WRL;
    using namespace Microsoft::WRL::Wrappers;
    using namespace ABI::Windows::Foundation;
    using namespace ABI::Windows::Foundation::Collections;

    ComPtr<IPropertySet> propSet = propertySet;
    ComPtr<IMap<HSTRING, IInspectable*>> map;
    propSet.As(&map);

    ComPtr<IPropertyValueStatics> propValueFactory;
    GetActivationFactory(HStringReference(RuntimeClass_Windows_Foundation_PropertyValue).Get(), &propValueFactory);

    ComPtr<IInspectable> valueInspectable;
    propValueFactory->CreateSingle(scale, &valueInspectable);

    boolean replaced;
    map->Insert(key, valueInspectable.Get(), &replaced);
}

I am using COM here so that I can use this in C/C++ code. My P/Invoke code is this:

internal static class PropertySetInterop
{
    public static void AddSingle(PropertySet properties, string key, float value)
    {
        PropertySetInterop_AddSingle(properties, key, value);
    }

    public static void AddSize(PropertySet properties, string key, float width, float height)
    {
        PropertySetInterop_AddSize(properties, key, width, height);
    }

    private const string libInterop = "SkiaSharp.Views.Interop.UWP.dll";

    [DllImport(libInterop)]
    private static extern void PropertySetInterop_AddSingle(
        [MarshalAs(UnmanagedType.IInspectable)] object properties,
        [MarshalAs(UnmanagedType.HString)] string key,
        float value);

    [DllImport(libInterop)]
    private static extern void PropertySetInterop_AddSize(
        [MarshalAs(UnmanagedType.IInspectable)] object properties,
        [MarshalAs(UnmanagedType.HString)] string key,
        float width, float height);
}
Matthew
  • 4,832
  • 2
  • 29
  • 55