开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜