views:

113

answers:

2

Hello.

I have a unmanaged C++ function with the following signature:

int function(char* param, int ret)

I am trying to call it from C#:

unsafe delegate int MyFunc(char* param, int ret);

...

int Module = LoadLibrary("fullpathToUnamanagedDll");
IntPtr pProc = GetProcAddress(Module, "functionName");
MyFunc func = (MyFunc)System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer(pProc, typeof(MyFunc));

unsafe
{
    char* param = null;
    int ret = 0;
    int result = func(param, ret);
}

As far as I can tell from the old C++ project specification both null for param and 0 for ret are valid inputs to the function. When I try to call it it seems to work, however upon exiting I get the following error:

PInvokeStackImbalance was detected

A call to PInvoke function '...::Invoke' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.

I have tried pretty much anything I could think off (unsafe was last resort), however I can't find any way to run the function without getting unbalanced stack. Is there something else I could try?

+2  A: 

IIRC, you need to decorate the delegate signature with a calling convention. Unfortunately, this can only be done via IL or generating the stub with Reflection.Emit.

You can try this:

protected static Type MakeDelegateType(Type returntype, List<Type> paramtypes)
{
  ModuleBuilder dynamicMod = ... ; // supply this

  TypeBuilder tb = dynamicMod.DefineType("delegate-maker" + Guid.NewGuid(), 
      TypeAttributes.Public | TypeAttributes.Sealed, typeof(MulticastDelegate));

  tb.DefineConstructor(MethodAttributes.RTSpecialName | 
       MethodAttributes.SpecialName | MethodAttributes.Public |
       MethodAttributes.HideBySig, CallingConventions.Standard,
       new Type[] { typeof(object), typeof(IntPtr) }). 
       SetImplementationFlags(MethodImplAttributes.Runtime);

  var inv = tb.DefineMethod("Invoke", MethodAttributes.Public | 
       MethodAttributes.Virtual | MethodAttributes.NewSlot | 
       MethodAttributes.HideBySig, 
       CallingConventions.Standard ,returntype,null, 
       new Type[] 
       { 
          // this is the important bit
          typeof(System.Runtime.CompilerServices.CallConvCdecl)
       }, 
       paramtypes.ToArray(), null, null);

  inv.SetImplementationFlags(MethodImplAttributes.Runtime);

  var t = tb.CreateType();
  return t;
}
leppie
I'm sorry but I need some help understanding the code. Where do my Dll file (or path to it) and function name come in?
Rekreativc
@Rekreativc: Just like you did it. The result from my method is sent when retrieving the function pointer. That said, it will be of type Delegate, and you would need to call `DynamicInvoke` on it. You could make another Reflection.Emit wrapper to call it directly rather.
leppie
@Rekreativc: Yes, then on the result call `DynamicInvoke`.
leppie
@leppie thanks awfully!
Rekreativc
A: 

There are two things to be aware of: The calling convention between the C# bit and your DLL and how the char * data is marshalled across this interface. If you get either of these wrong then you will get stack corruption complaints. While defining your interface it is much easier if you can limit the size of your data block to something fixed, ie. set a maximum string length.

Here's the static version, where the DLL name is fixed and your string is handled as a byte[] and limited to 2Kbyte in size, and you can figure the dynamic version out from this:

    private const string MYDLL = @"my.dll";

    [DllImport(MYDLL, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto)]
    public static extern int DataBlockDownload([MarshalAs(UnmanagedType.U4)] int A, [MarshalAs(UnmanagedType.LPArray, SizeConst = 2048)] byte[] B, [MarshalAs(UnmanagedType.U4)] int C);

    // NOTE: The data block byte array is fixed at 2Kbyte long!!
public delegate int DDataBlockCallback([MarshalAs(UnmanagedType.U4)] int A, [MarshalAs(UnmanagedType.LPArray, SizeConst = 2048)] byte[] B, [MarshalAs(UnmanagedType.U4)] int C);

You may also want to specify the character set that you're using if you want to stay with char types, as above.

You don't say what you're doing with your char * data, if it is going in to your C++ code as a parameter or if the C++ code is passing it back to the managed world. Read up on the C# keywords ref and out as ways of avoiding the char * type and the unsafe modifier.

With a bit of Googling from that you should be able to figure it out.

Toby Martin
The problem is that you cant specify a calling convention for the delegate in C#, and that is what is causing the problem.
leppie