How do I marshal a pointer to an array of pointers to structures from managed to unmanaged code?
I'm trying to write plugin dll which has to implement the following function:
int GetFunctionTable(FuncDescStruct **ppFunctionTable);
So my plugin code in C# declares this:
public static unsafe int GetFunctionTable(IntPtr functionTablePtr);
This function will be called and expected to fill functionTablePtr with pointer to array of structs describing set of callback functions.
In plain C/C++ it looks something like:
// declare func table
// VExampleF1, VExampleF2 - are function pointers
FuncDescStruct funcTable[] = {
"ExampleF1", { VExampleF1, 0, 0, 0, 0, NULL }, //filling descriptions.
"ExampleF2", { VExampleF2, 1, 0, 1, 0, NULL }
};
int GetFunctionTable(FuncDescStruct **ppFunctionTable)
{
*ppFunctionTable = funcTable; // how to do this correctly in C#?
// must return the number of functions in the table
return funcTableSize;
}
I'm trying to do the following:
static unsafe FunctionTag[] funcTable;
static List<IntPtr> allocatedMemory;
public static unsafe int GetFunctionTable(IntPtr functionTablePtr)
{
//create just one test callback description
funcTable = new FunctionTag[1];
funcTable[0].Name = "VExampleF1";
funcTable[0].Description.Function = VExampleF1;
funcTable[0].Description.ArrayQty = 0;
funcTable[0].Description.FloatQty = 0;
funcTable[0].Description.StringQty = 0;
funcTable[0].Description.DefaultQty = 0;
funcTable开发者_JAVA百科[0].Description.DefaultValues = null;
// saving allocated memory for further cleanup
allocatedMemory = new List<IntPtr>();
int intPtrSize = Marshal.SizeOf(typeof(IntPtr));
IntPtr nativeArray = Marshal.AllocHGlobal(intPtrSize * funcTable.Length);
for (int i = 0; i < funcTable.Length; i++)
{
IntPtr nativeFD = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FunctionTag)));
allocatedMemory.Add(nativeFD);
Marshal.StructureToPtr(funcTable[i], nativeFD, false);
Marshal.WriteIntPtr(nativeArray, i * intPtrSize, nativeFD);
}
Marshal.WriteIntPtr(functionTablePtr, nativeArray);
return funcTable.Length;
}
Such code doesn't work, and the question is how to send a pointer to array of managed structs for use by unmanaged code? In which direction should I go?
We've been doing quite a lot of this sort of thing over the last couple of years. We use a mixed-mode 'bridge' assembly to manage mapping function calls and marshalling data between managed and unmanaged environments. We often use a 'mixed-mode' class to wrap a managed class, providing a native interface for calling it's functionality.
Let's consider your problem. You could write GetFunctionTable in managed c++. It can call some managed c# code to get the function information in a managed struct, and then 'marshall' it into the native struct.
In (c#) a managed version of GetFunctionTable function:
delegate void FunctionDelegate();
public struct FuncDescStructManager
{
public string Name;
public FunctionDelegate Function;
//...
}
public static List<FuncDescStructManager> GetFunctionTableManaged()
{
List<FuncDescStructManager> list = new List<FuncDescStructManager>();
list.Add(new FuncDescStructManaged () {"ExampleF1", VExampleF1});
return list;
}
In the mixed-mode bridge assembly you can implement the native function GetFunctionTable, calling the managed function and marshalling the data:
int GetFunctionTable(FuncDescStruct **ppFunctionTable)
{
// Call the managed function
List<FuncDescStructManaged>^ managedList = GetFunctionTableManaged();
nativeArray = malloc(managedList.Length * sizeof(FuncDescStruct));
int i=0;
foreach (FuncDescStructManaged managedFunc in managedList)
{
// Marshall the managed string to native string could look like this:
stringPtr = Marshal::StringToHGlobalUni(managedFunc.Name);
nativeArray[i].Name = ((wchar_t*)stringPtr.ToPointer());
Marshal::FreeHGlobal(stringPtr);
// Marshall a delegate into a native function pointer using a
// wrapper class:
WrapDelegateAsPtr funcPtr = new WrapDelegateAsPtr(managedFunc.Function);
// funcPtr will need to be deleted by caller
nativeArray[i].Function = funcPtr.NativeFunction;
i++;
}
return i;
}
// Mixed mode wrapper class
// Member is a reference to a managed delegate.
// Because we need to reference this from native code, the wrapped
// delegate will be stored as a void*.
class WrapDelegateAsFPtr
{
public:
WrapDelegateAsNativeFunctionPointer(FunctionDelegate _delegate)
{
delegate = _delegate;
// Tell the garbage handler not to remove the delegate object yet
GCHandle gch = GCHandle::Alloc(svgContents);
managedDelegatePtr = GCHandle::ToIntPtr(gch).ToPointer();
}
~WrapDelegateAsNativeFunctionPointer
{
// Tell the garbage collector we are finished with the managed object
IntPtr temp(managedDelegatePtr;);
GCHandle gch = static_cast<GCHandle>(temp);
gch.Free();
}
void NativeFunction()
{
}
private:
void* managedDelegatePtr;
}
Hope this helps - Any questions just ask!
THis is a rather belatedd answer, but I have come up against exactly the same problem. I have implmented a DATA PLUGIN using Kostya's framework, and got it to work perfectly, then trying to implmement an AFL plugin, I ran into the problem as per above. I have managed to get itworking without using a C++ rapper class. The problem is with the function pointer/reference/address that is passed from withing the FUnctionTable. AS these functions have been dclared as STATIC for EXPORT purposes, they are incompatible with the C++ delegate implmentation in the GETFUNCTIONTABLE method. IF you ddo the following , it should work:-
Add the 2 signatures:-
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern bool SetDllDirectory(string lpPathName);
the function reference definition in the
GetFunctionTable
structure must be changed to: IntPtr Function;Add the following statements to get the exported function address:-
IntPtr dllHandle = LoadLibrary(fullPath); IntPtr fptr = GetProcAddress(dllHandle, "VfmExample1");
and lastly initialize the function variable in the GetFunctionTable
structure , e.g.
functable[0].Description.function = fptr;
and that should do it
You have to use the fixing construct to fix the buffer in place, because the C# GC reserves the right to move it around if you don't, invalidating the pointer. I don't know how to fix a buffer indefinitely. You're going to have to worry about how that memory is managed, too.
精彩评论