C# Call C++ Dll Passing Pointer-To-Pointer Argument

C# call C++ DLL passing pointer-to-pointer argument

I used the following test implementation:

int API_ReadFile(const wchar_t* filename, DataStruct** outData)
{
*outData = new DataStruct();
(*outData)->data = (unsigned char*)_strdup("hello");
(*outData)->len = 5;
return 0;
}

void API_Free(DataStruct** pp)
{
free((*pp)->data);
delete *pp;
*pp = NULL;
}

The C# code to access those functions are as follows:

[StructLayout(LayoutKind.Sequential)]
struct DataStruct
{
public IntPtr data;
public int len;
};

[DllImport("ReadFile.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
unsafe private static extern int API_ReadFile([MarshalAs(UnmanagedType.LPWStr)]string filename, DataStruct** outData);

[DllImport("ReadFile.dll", CallingConvention = CallingConvention.Cdecl)]
unsafe private static extern void API_Free(DataStruct** handle);

unsafe static int ReadFile(string filename, out byte[] buffer)
{
DataStruct* outData;
int result = API_ReadFile(filename, &outData);
buffer = new byte[outData->len];
Marshal.Copy((IntPtr)outData->data, buffer, 0, outData->len);
API_Free(&outData);
return result;
}

static void Main(string[] args)
{
byte[] buffer;
ReadFile("test.txt", out buffer);
foreach (byte ch in buffer)
{
Console.Write("{0} ", ch);
}
Console.Write("\n");
}

The data is now transferred to buffer safely, and there should be no memory leaks. I wish it would help.

How to pass a pointer from C# to native function in DLL?

Well, based on the information you have provided I would say that you need to declare the C# function like this:

[DllImport("myLib.dll")]
public static extern int myFun(
IntPtr context,
string fileName,
uint bufferSize,
out WAVEFORMATEX wfx
);

And call the function like this:

WAVEFORMATEX wfx;
int result = DLLWrapper.myFun(context, @"C:\video.wmv", 176400, out wfx);

There's really no need for manual marshalling of this struct. It's a very simple blittable struct and it is much cleaner to let the framework handle the marshalling.

I am assuming that you are accurate when you state that the final struct parameter does not need to be initialised and its members are filled out by the function.

Packing looks reasonable. I don't think you need to build your DLL in any special way. I hope that you are picking up WAVEFORMATEX from the Windows header files and they already specify packing for that struct.

If you are still stuck then you should show the successful C++ calling code.


Judging from the comments, you still have a bug somewhere in your code. In such a situation, especially when you doubt the interop, it pays to make a simple reproduction to determine whether the interop is the problem, or not. Here is mine:

C++ DLL

#include <Windows.h>
#include <mmsystem.h>
#include <iostream>

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

__declspec(dllexport) int __stdcall myFun(void * const context,
const char * const pszFileName, const unsigned int buffSize,
void * const pWaveFormatex)
{
std::cout << context << std::endl
<< pszFileName << std::endl
<< buffSize << std::endl;

WAVEFORMATEX wfx;
wfx.cbSize = 1;
wfx.nAvgBytesPerSec = 2;
wfx.nBlockAlign = 3;
wfx.nChannels = 4;
wfx.nSamplesPerSec = 5;
wfx.wBitsPerSample = 6;
wfx.wFormatTag = 7;
CopyMemory(pWaveFormatex, &wfx, sizeof(wfx));

return 666;
}

C# console app

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication13
{
class Program
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct WAVEFORMATEX
{
public ushort wFormatTag;
public ushort nChannels;
public uint nSamplesPerSec;
public uint nAvgBytesPerSec;
public ushort nBlockAlign;
public ushort wBitsPerSample;
public ushort cbSize;
}

[DllImport(@"Win32Project1.dll", EntryPoint = "?myFun@@YGHQAXQBDI0@Z")]
public static extern int myFun(
IntPtr context,
string fileName,
uint bufferSize,
out WAVEFORMATEX wfx
);

static void Main(string[] args)
{
WAVEFORMATEX wfx;
int result = myFun((IntPtr)42, @"C:\video.wmv", 176400, out wfx);
Console.WriteLine(result);
Console.WriteLine(wfx.cbSize);
Console.WriteLine(wfx.nAvgBytesPerSec);
Console.WriteLine(wfx.nBlockAlign);
Console.WriteLine(wfx.nChannels);
Console.WriteLine(wfx.nSamplesPerSec);
Console.WriteLine(wfx.wBitsPerSample);
Console.WriteLine(wfx.wFormatTag);

Console.ReadLine();
}
}
}

Output


0000002A
C:\video.wmv
176400
666
1
2
3
4
5
6
7

Using a DLL function in C# that requires a pointer as argument, am I declaring this properly?

This is a quite simple case, you just need a pointer to an array, not an array of pointers (as you have declared).

This should work:

byte[] buf = new byte[4096]; // allocate managed buffer
fixed (int* p = &buf[0]) // create a fixed pointer to the first element of the buffer (= to the buffer itself)
{
int result = DataRec(p);
// ...
// use the data
}

Calling unmanaged function which takes a pointer to pointer parameter

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

C# call to C and pass parameter as a pointer to an array

If you already have a C++/CLI wrapper, I would adjust the API there and make that one more C#-ish.

So change the C++/CLI declaration to something like:

int MyFunc(array<double>^ points,
array<double>^ parameters);

and then do all the managed->unmanaged wrapping in that implementation.

In the end, it would look something like this:

int MyFunc(array<double>^ points,
array<double>^ parameters)
{

if (points->Length != 3)
{
throw gcnew System::ArgumentOutOfRangeException("Expecting exactly 3 points");
}

double *pPoints = (double*)alloca(sizeof(double) * points->Length);

for (int i = 0; i < points->Length; i++)
{
pPoints[i] = points[i];
}

double* pParams = (double*)alloca(sizeof(double) * parameters->Length);

for (int i = 0; i < parameters->Length; i++)
{
pParams[i] = parameters[i];
}

double(*pPoints3)[3] = (double(*)[3])pPoints;
int numParams = parameters->Length;
my_func(pPoints3, pParams, &numParams);
}

However, I'm completely confused on the line with the ??? as I don't exactly know what const double(*pPoints3)[3] means. An array of 3 doubles? Or an array of pointers to doubles?

How to pass function pointer from C# to a C++ Dll?

Your delegate uses the cdecl calling convention. In C# you would therefore declare the delegate like this:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate double CallbackDelegate(double x);

As an alternative, you could decide to declare the function pointer in C++ as __stdcall, in which case you would remove the UnmanagedFunctionPointer attribute and rely on the default calling convention being CallingConvention.StdCall.

Implement it like this:

public static double MyFunc(double x)
{
return Math.Sqrt(x);
}

In order to keep the unmanaged function pointer alive (guarding against GC), you need to hold an instance of the delegate in a variable.

private static CallbackDelegate delegateInstance;
....
delegateInstance = MyFunc;

In the simple example that you have here, the C++ code does not use the unmanaged function pointer outside of TestDelegate, but in a more complex example you may do so, in which case you must keep the unmanaged function pointer alive.

The function that you import is declared like this:

[DllImport("DllName.dll")]
public extern static double TestDelegate(CallbackDelegate f);

You can then call it like this:

double retval = TestDelegate(delegateInstance);

How to properly pass struct pointer from C# to C DLL

As @kennyzx mentions, since your st_struct is a class, it is already a reference type and will be passed as a pointer. I suspect throwing a ref onto that will give you a double pointer, which doesn't make much sense when mixing managed and unmanaged code. The marshaller can possibly handle that and create a new object for you if the pointer changes, but it seems like a sketchy thing to do.

So, when passing the class without ref, it works as expected (the C code gets a pointer). If you change it to a struct, passing it without ref should pass it on the stack and passing it with ref will pass it as a pointer.

Using struct seems like the obvious choice in your case, as it can be passed directly (the CLR just needs to pin it and pass a pointer). Using class I suspect will involve more marshalling.



Related Topics



Leave a reply



Submit