Reputation: 892
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
}
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.
Upvotes: 4
Views: 1598
Reputation: 184
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
Upvotes: 3