从非托管代码回调托管代码

4

我正在触发我的托管代码并启动对非托管代码的调用。在非托管代码中存在回调函数。从非托管代码中,我可以在我的托管方法“DelegateMethod”中接收回调。但是,我无法获得来自非托管代码的正确参数/参数值。请帮助我解决这个问题。

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();
        }
    }
}

这里有我未经管理的代码 - CmdLib.c


(该段内容涉及IT技术,翻译时保留了HTML标签)
//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);
}

一些观察。调用约定可能是错误的。我认为你的委托应该是 cdecl。你可能需要将委托引用存储在静态字段中,以防止其被回收。现在来看最大的问题。编组程序不知道数组的长度,因此无法进行编组。也许需要在属性中指定大小,或将其作为 IntPtr 进行编组,然后使用 Marshal.Copy。 - David Heffernan
pFTransceive 是如何定义的?pf_TxRx 是需要填充数组还是需要创建一个新数组? - Matteo Umili
1
将 .NET 数组从 C++ 传递到 C# 是复杂的(我说的是委托)...非常复杂...您可以尝试 public delegate void fPointer([MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] byte[] Sendapdu, ref int Sendlen, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=3)] byte[] Recvapdu, ref int Recvlen); 但我不确定它是否有效...而且它会很慢(因为缓冲区需要在 .NET 中被复制)。 - xanatos
@codroipo 我已经更新了 pFTransceive 的定义。 - SHRI
1个回答

4

您的委托声明与 pFTransceive 函数指针声明不匹配。这会在回调方法中产生垃圾参数值。

请尝试以下更接近的代码:

    [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
    );

注意将required属性匹配到C/C++程序中使用的默认调用约定。并且将uint16_t的参数类型设为short
有关调用约定的更多信息,请参见此帖子中的更多内容。
    sCommsAbstraction psCommsFunctions = new sCommsAbstraction();

这是你必须担心的另一件事情。那个对象需要存活,只要本地代码可以进行回调。但是现在它没有存活下来,因为垃圾收集器无法找到本地代码正在使用它的方式,所以下一个垃圾收集将会销毁它。当本地代码进行回调时就会出现问题。你现在还没有看到这个错误,调试器现在保持对象的存活状态。但是在生产中不会发生这种情况。
按照现有的写法,你需要在Main()方法的底部添加这个语句:
    GC.KeepAlive(psCommsFunctions);

将其存储在静态变量中是更好的方法,只有当您确定本地代码不再进行回调时,才将其设置回null


非常好的解释@Hans,非常感谢 :) - SHRI
编组器如何在不知道数组长度的情况下对数组进行编组? - David Heffernan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接