4

I am triggering my managed code and initiating a call to unmanaged code. There is a callback in unmanaged code. From unmanaged I am getting callback in my managed method 'DelegateMethod'. But I am not getting proper parameter/argument values from unmanaged code. Please help me with this

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace TestApp
{
    public class Program
    {
        public delegate void fPointer(byte[] Sendapdu, ref int Sendlen, byte[] Recvapdu, ref int Recvlen);

        public struct sCommsAbstraction
        {
            ///Function to send and receive.
            public fPointer pf_TxRx;

            ///Other functions if necessary, e.g. reset
        }

        // Create a method for a delegate. 
        public static void DelegateMethod(byte[] Sendapdu, ref int Sendlen, byte[] Recvapdu, ref int Recvlen)
        {
            //This is called from unmanaged code. I am not getting proper arguments
            Console.WriteLine(Sendlen);
        }

        [DllImport("AuthLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int CmdLib_RegisterItsIO(ref sCommsAbstraction psCommsFunctions);

        [DllImport("AuthLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int CmdLib_OpenApplication();

        [DllImport("TransparentChannel.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int TC_Transceive(byte[] writeBuf, ref int writeBufLen, byte[] readBuf, ref int pwReadBufferLen);

        static void Main(string[] args)
        {
            sCommsAbstraction psCommsFunctions = new sCommsAbstraction();
            // Instantiate the delegate.
            psCommsFunctions.pf_TxRx = DelegateMethod;

            CmdLib_RegisterItsIO(ref psCommsFunctions);
            CmdLib_OpenApplication();
        }
    }
}

My unmanged piece of code is present here - CmdLib.c

//C code - unmanaged

typedef int32_t (*pFTransceive)(const uint8_t *prgbWriteBuffer, const uint16_t *pwWriteBufferLen, uint8_t *prgbReadBuffer, uint16_t *pwReadBufferLen);

typedef struct sCommsAbstraction
{
    ///Function to send and receive.
    pFTransceive pf_TxRx;

    ///Other functions if necessary, e.g. reset
}sCommsAbstraction_d

static sCommsAbstraction_d sCommsAbstraction = {0};

void CmdLib_RegisterItsIO(const sCommsAbstraction_d *psCommsFunctions)
{
    sCommsAbstraction.pf_TxRx = psCommsFunctions->pf_TxRx;    
}
void CmdLib_OpenApplication()
{
    sCommsAbstraction.pf_TxRx(rgbAPDUBuffer,&wTotalLength,rgbAPDUBuffer,&psApduData->wResponseLength);
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
SHRI
  • 2,406
  • 6
  • 32
  • 48
  • Some observations. The calling convention is probably wrong. Your delegate should be cdecl I think. You may need to store the delegate ref in a static field to stop it being collected. And now the big one. The marshaller doesn't know how long the arrays are and cannot marshal them. Maybe need to specify size in attributes, or marshal as IntPtr and then use Marshal.Copy. – David Heffernan Jul 20 '15 at 08:48
  • How is `pFTransceive` defined? `pf_TxRx` has to fill the array or does it have to create a new array? – Matteo Umili Jul 20 '15 at 09:21
  • 1
    Marshaling .NET arrays from C++ to C# is complex (I'm speaking of the delegate)... very complex... You could try `public delegate void fPointer([MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] byte[] Sendapdu, ref int Sendlen, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=3)] byte[] Recvapdu, ref int Recvlen);` but I'm not sure it would work... And it would be slow (because the buffers will need to be copied around by the .NET) – xanatos Jul 20 '15 at 09:38
  • @codroipo I have updated the definition of pFTransceive – SHRI Jul 20 '15 at 10:05

1 Answers1

4

Your delegate declaration is not a match for the pFTransceive function pointer declaration. Which produces garbage argument values in your callback method.

Get closer with:

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void fPointer(
        [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
        byte[] Sendapdu, ref short Sendlen,
        [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]
        byte[] Recvapdu, ref short Recvlen
    );

Note the required attribute to match the default calling convention that's used in C/C++ programs. And the short argument types to match uint16_t.`

More about the mysteries of calling conventions in this post.


    sCommsAbstraction psCommsFunctions = new sCommsAbstraction();

This is another thing you have to fret about. That object needs to survive as long as the native code can make callbacks. Right now it doesn't, the next garbage collection is going to destroy it since the garbage collector has no way to find out that the native code is using it. Kaboom when the native code makes the callback. You are not seeing that bug yet, the debugger keeps the object alive right now. That is not going to happen in production.

As written, you need to add this statement to the bottom of your Main() method:

    GC.KeepAlive(psCommsFunctions);

Storing it in a static variable is the better approach, only ever set it back to null when you know for a fact that the native code isn't going to make callbacks anymore.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536