4

I am trying to call a function in C from my .Net Core application. To dive right in, the C function comes from libmpv render.h and the head of the functions looks like this:

int mpv_render_context_create(mpv_render_context **res, mpv_handle *mpv, mpv_render_param *params);

The problem is I have no clue how to call the function from C#. So I've tried the following:

[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int mpv_render_context_create([Out] out IntPtr renderContext, IntPtr mpvHandle,
        MpvRenderParam[] parameters);

Since the **res parameter should be updated by the function I thought this would make sense. This I tried to call with the following:

var ptr = IntPtr.Zero;
var apiTypePtr = Marshal.StringToHGlobalAuto("opengl");
var i = mpv_render_context_create(out ptr, _mpvHandle, new []
{
    new MpvRenderParam(MpvRenderParamType.ApiType, apiTypePtr),
    new MpvRenderParam(MpvRenderParamType.Invalid, IntPtr.Zero
});

This threw me an AccessViolationException, In fact every time I called the mpv_render_context_create Method I got the same Exception, so when I say "It didn't work" I mean the call threw that exception.

So I then thought maybe I would have to make it a ref parameter:

[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int mpv_render_context_create(ref IntPtr renderContext, IntPtr mpvHandle,
        MpvRenderParam[] parameters);

Which caused the same error when calling it.

I then read in another stackoverflow question, that I should pass the bare IntPtr like the following:

[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int mpv_render_context_create(IntPtr renderContext, IntPtr mpvHandle,
        MpvRenderParam[] parameters);

But first, it didn't work again, and second how would I even get the result from this?

So how should I create and call the function so I can create the render context?

The MpvRenderParam is declared as followed:

[StructLayout(LayoutKind.Sequential)]
public struct MpvRenderParam
{
    public MpvRenderParamType Type { get; }
    public IntPtr Data { get; }

    public MpvRenderParam(MpvRenderParamType type, IntPtr data)
    {
        Type = type;
        Data = data;
    }
}

And The MpvRenderParamType:

public enum MpvRenderParamType
{
    Invalid = 0,
    ApiType = 1,
    InitParams = 2,
    Fbo = 3,
    FlipY = 4,
    Depth = 5,
    IccProfile = 6,
    AmbientLight = 7,
    X11Display = 8,
    WlDisplay = 9,
    AdvancedControl = 10,
    NextFrameInfo = 11,
    BlockForTargetTime = 12,
    SkipRendering = 13,
    DrmDisplay = 14,
    DrmDrawSurfaceSize = 15,
    DrmDisplayV2 = 15
}

Update

I have taken into account all of the resources provided by Mattias Santoro I am just not entirely sure how to translate them into C# as it is a managed language. My Method for calling the mpv function looks like this now:

OpenGlControl.OpenGL.MakeCurrent();
IntPtr ptr;
var apiTypePtr = Marshal.StringToHGlobalAuto("opengl");
var opengl = new MpvOpenGlInitParams {get_proc_address = getProcAdress};
var size = Marshal.SizeOf(opengl);
var arr = new byte[size];

fixed (byte* arrPtr = arr)
{
    Marshal.StructureToPtr(opengl, (IntPtr)arrPtr, true);
    var parameters = new MpvRenderParam[]
    {
        new MpvRenderParam {Type = MpvRenderParamType.ApiType, Data = &apiTypePtr},
        new MpvRenderParam {Type = MpvRenderParamType.OpenGlInitParams, Data = &arrPtr},
        new MpvRenderParam {Type = MpvRenderParamType.Invalid, Data = null}
    };
    var i = mpv_render_context_create(&ptr, _mpvHandle, parameters);
}

The MpvOpenGlInitParams:

[StructLayout(LayoutKind.Sequential)]
public struct MpvOpenGlInitParams
{
    public delegate IntPtr GetProcAdressDelegate(IntPtr ctx, string name);

    public GetProcAdressDelegate get_proc_address;
    public IntPtr get_proc_address_ctx;
    public string extra_exts;
}

Sadly it still throws the same error, I am not sure what I am doing wrong and it's really frustrating.

J.Paravicini
  • 882
  • 12
  • 39
  • The first one seems decent, but how do you declare MpvRenderParam? – Simon Mourier Dec 22 '19 at 13:05
  • The MpvRenderParam has always been declared the same, like this `new [] { new MpvRenderParam(MpvRenderParamType.ApiType, apiTypePtr), new MpvRenderParam(MpvRenderParamType.Invalid, IntPtr. }` and the declaration is `MpvRenderParam[] parameters` – J.Paravicini Dec 22 '19 at 13:07
  • I meant how is declared the MpvRenderParam type – Simon Mourier Dec 22 '19 at 13:33
  • I'm sorry, I have added the declarations of both `MpvRenderParam` and `MpvRenderParamType`. @jdweng I tried so and It seems it doesn't make a difference, as the compiler tells me I should change it to just new[] – J.Paravicini Dec 22 '19 at 13:37
  • 1
    Don't use properties in p/invoke structs, but use fields (remove {get;}) – Simon Mourier Dec 22 '19 at 13:46
  • Yea that makes sense, but it didnt change anything :/ still getting AccessViolationException – J.Paravicini Dec 22 '19 at 13:48
  • What do you pass for mpvHandle? – Simon Mourier Dec 22 '19 at 14:43
  • It is a valid IntPtr created before from mpv_create – J.Paravicini Dec 23 '19 at 08:36
  • Parameter type invalid is 0, and its data is an IntPtr.Zero, so in my opinion that should be correct – J.Paravicini Dec 23 '19 at 19:01
  • Are you 100% certain that you need to change the calling convention? If not, then it would be best to stick with the default of `CallingConvention.StdCall`. – Ben Cottrell Dec 25 '19 at 21:07
  • I've tried to make a. Net core project with sdl2 and mpv, I've tried to use the mpv client api passing the window created with sdl but it crashes when I send the play command. Now I'm trying porting the full c++ sdl sample in c# but I'm yet stuck like you on the render_context_create (but now I have properly inited gl context) I should try without using properties in structs. My last try will be trying referencing the full c++ mpvlib code and using c++/cli to make a bridge and inspecting the code. – Mattias Santoro Dec 26 '19 at 08:23

1 Answers1

3

I've put my code in this repo: (for .net core 2.1 and sdl2 and mpv dll for X64) https://github.com/shodo/MPVCore.git

Update2: DOOOONEEEE! First, this is my (really really ugly) code. I don't know if every thing I've done is needed:

Function defined as:

 [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
 private delegate int MpvRenderContextCreate(ref IntPtr context, IntPtr mpvHandler, IntPtr parameters);
 private MpvRenderContextCreate _mpvRenderContextCreate;

And code (it's part of an hardcoded frankestein of the c++ sdl sample plus winform and other stuff)

if (_mpvHandle != IntPtr.Zero)
            _mpvTerminateDestroy(_mpvHandle);

        LoadMpvDynamic();
        if (_libMpvDll == IntPtr.Zero)
            return;

        _mpvHandle = _mpvCreate.Invoke();
        if (_mpvHandle == IntPtr.Zero)
            return;

        _mpvInitialize.Invoke(_mpvHandle);



        MpvOpenGlInitParams oglInitParams = new MpvOpenGlInitParams();
        oglInitParams.get_proc_address = (ctx, name) => SDL.SDL_GL_GetProcAddress(name);
        oglInitParams.get_proc_address_ctx = IntPtr.Zero;
        oglInitParams.extra_exts = IntPtr.Zero;

        var size = Marshal.SizeOf<MpvOpenGlInitParams>();
        var oglInitParamsBuf = new byte[size];

        fixed (byte* arrPtr = oglInitParamsBuf)
        {
            IntPtr oglInitParamsPtr = new IntPtr(arrPtr);
            Marshal.StructureToPtr(oglInitParams, oglInitParamsPtr, true);

            MpvRenderParam* parameters = stackalloc MpvRenderParam[3];

            parameters[0].type = MpvRenderParamType.ApiType;
            parameters[0].data = Marshal.StringToHGlobalAnsi("opengl");

            parameters[1].type = MpvRenderParamType.InitParams;
            parameters[1].data = oglInitParamsPtr;

            parameters[2].type = MpvRenderParamType.Invalid;
            parameters[2].data = IntPtr.Zero;

            var renderParamSize = Marshal.SizeOf<MpvRenderParam>();

            var paramBuf = new byte[renderParamSize * 3];
            fixed (byte* paramBufPtr = paramBuf)
            {
                IntPtr param1Ptr = new IntPtr(paramBufPtr);
                Marshal.StructureToPtr(parameters[0], param1Ptr, true);

                IntPtr param2Ptr = new IntPtr(paramBufPtr + renderParamSize);
                Marshal.StructureToPtr(parameters[1], param2Ptr, true);

                IntPtr param3Ptr = new IntPtr(paramBufPtr + renderParamSize + renderParamSize);
                Marshal.StructureToPtr(parameters[2], param3Ptr, true);


                IntPtr context = new IntPtr(0);
                _mpvRenderContextCreate(ref context, _mpvHandle, param1Ptr);
            }
        }

And what made the function works is:

parameters[0].data = Marshal.StringToHGlobalAnsi("opengl");

Instead of using

parameters[0].data = Marshal.StringToHGlobalAuto("opengl");

I think it wasn't just recognizing the "opengl" parameter needed to init the mpv player with the opengl render context

Update: in order to call that function you need an initialized opengl context, or, you should pass a parameter of type MPV_RENDER_PARAM_OPENGL_INIT_PARAMS with the callback needed to access open gl functions. Check this example in C++ that uses SDL in order to initialize a window and get an opengl context.

https://github.com/mpv-player/mpv-examples/blob/master/libmpv/sdl/main.c

Without a proper initalized OpenGl context you always receive an ViolationException error regarding of the proper c# Marshaling: github.com/mpv-player/mpv/issues/6249
https://github.com/mpv-player/mpv/issues/6507

-- Old: See this one: https://stackoverflow.com/questions/20419415/c-sharp-call-c-dll-passing-pointer-to-pointer-argument

Mattias Santoro
  • 184
  • 1
  • 5
  • Thanks for the link, i'll try it once i get home, but since the mpv_render_context is an opaque pointer should i use IntPtr** instead of DataStruct**? Or should i use something else? – J.Paravicini Dec 25 '19 at 12:20
  • So I tried the suggestion of the other answer and changing the mpv_render_context_create to be unsafe and the first parameter to be of type IntPtr* didn't help, I still get the same exact exception. – J.Paravicini Dec 25 '19 at 15:41
  • Check also this (it's from an example on XCode): https://github.com/mpv-player/mpv/issues/6249 The access violation maybe is not caused by a wrong C# marshaling but cause the OpenGL context is not initialized before calling mpv_render_context_create() – Mattias Santoro Dec 25 '19 at 17:04
  • Hey I really appreciate your efforts for helping me, I will award the bounty at the end if it still won't work, but I would greatly appreciate your help if we could get this to work – J.Paravicini Dec 25 '19 at 20:14
  • Thanks! Just a question.. you really want to work with an opengl render context or you are just trying to embed MPV in your application? – Mattias Santoro Dec 25 '19 at 20:43
  • Sry, misclicked, i am using .net core 3.1 and i can not get window handles afaik, and i dont know any other way of embeding the view into my app. I am open to other suggestions – J.Paravicini Dec 25 '19 at 20:51
  • Basically I want to embed mpv, but as I said I’m using .net core 3.1 which does not have access to the windows forms framework, so I wouldn’t know any other way – J.Paravicini Dec 26 '19 at 11:12
  • Damn you went over and out for that answer. I also just figured out, that my prof address function was wrong, I was using the Win32.wglGetProcAddress which returned 0, that’s why it always tried to read memory at position 0 – J.Paravicini Dec 26 '19 at 12:11