You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

托管C#到非托管C++代码回调异常:返回垃圾值问题排查

C#托管代码调用带回调的非托管C++代码返回垃圾值问题

我用C#托管代码调用包含回调逻辑的非托管C++代码,当前初始化委托的C#代码运行后返回垃圾值,无法确认代码是否正常工作。

现有C#代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace ConnectToScriptEngineDll
{
    public class ConnectToScriptEngine
    {
        public unsafe delegate float myCallBackPtr(float** tagValue1);
            
        [DllImport("LTscriptenginedll.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern unsafe int ConnectLiveData(myCallBackPtr ps, IntPtr* test, int** temp, bool bdiscconectConnection, bool bPyRequest);

        public unsafe float StartLiveInfo(string tagNameFromRequest)
        {
            string tagName = tagNameFromRequest;//"Motor_1.OutPut.VI";
            byte[] tagNameByte = Encoding.ASCII.GetBytes(tagName);
            IntPtr tagNamePtr = Marshal.UnsafeAddrOfPinnedArrayElement(tagNameByte, 0);

            IntPtr[] tagNameIntPtr = new IntPtr[2];
            tagNameIntPtr[0] = tagNamePtr;
            tagNameIntPtr[1] = IntPtr.Zero;

            int tagValueTemp = 0;
            int*[] tagValuePtr = new int*[2];
            tagValuePtr[0] = &tagValueTemp;
            tagValuePtr[1] = null;
            float test;

            fixed (int** tagValue1 = tagValuePtr)
            {
                fixed (IntPtr* tagName1 = tagNameIntPtr)
                {
                    try
                    {
                        myCallBackPtr obj = new myCallBackPtr(CallMethod);
                            
                        var finalTagValue = ConnectLiveData(obj, tagName1, tagValue1, false, true);
                        test = Convert.ToSingle(*tagValue1[0]);
                        return test;
                    }
                    catch (Exception ex)
                    {
                        return 0;
                    }
                }
            }
        }

        public unsafe static float CallMethod(float** tagValue1)
        {
            var test4 = Convert.ToSingle(*tagValue1[0]);
            Console.WriteLine("callbacked");
            return test4;
        }
    }
}

对应C++代码

USE_SE short ConnectLiveData(void* pCallBack, const char** pszTagNames, const int** pnTagAddress,
bool bdiscconectConnection, bool bPyRequest)
{
    short lnResult = -1;
    Lstring lsErrorMsg;

    if (NULL == g_pLiveDataChannel)
    {
        g_pLiveDataChannel = new CLiveDataChannel("", g_bIncomingLogEnableFlag, g_bOutgoingLogEnableFlag);
        g_nLiveDataChannelsCount++;
    }

    CSLCallInfo* lpcSLCall = new CSLCallInfo(pszTagNames, pnTagAddress, (onDPConnectdata)pCallBack, bPyRequest);

    if (lpcSLCall->m_nTagCount > 100)
    {
        lsErrorMsg = "Invalid number of parameters received for StartLiveCall TagName = " + lpcSLCall->m_sTagNamesKey;
        ST_LOG_DATA_BASE(ERROR_MSG, APP_NAME, (char*)lsErrorMsg.c_str());
        delete lpcSLCall;
        lpcSLCall = NULL;
        return lnResult;
    }

    if (lpcSLCall->m_sTagNamesKey.empty())
    {
        lsErrorMsg = "Failed start StartLiveCall - fiiled to create CSLCallInfo key : TagName = " + lpcSLCall->m_sTagNamesKey;
        ST_LOG_DATA_BASE(ERROR_MSG, APP_NAME, (char*)lsErrorMsg.c_str());
        delete lpcSLCall;
        lpcSLCall = NULL;
        return lnResult;
    }

    lnResult = g_pLiveDataChannel->InitLiveDataCall(lpcSLCall, pszTagNames, pnTagAddress);

    if (lnResult >= 0)
    {
        g_mLiveDataChannelMutex.lock();
        g_LiveDataChannelsList.insert(std::pair<Lstring, Lpair<CLiveDataChannel*, Lstring>>
            (lpcSLCall->m_sTagNamesKey, Lpair<CLiveDataChannel*, Lstring>(g_pLiveDataChannel, "")));
        g_mLiveDataChannelMutex.unlock();
    }
    else
    {
        delete lpcSLCall;
        lpcSLCall = NULL;
    }

    return lnResult;
}

问题分析与修复方案

1. 委托类型不匹配

C中回调类型onDPConnectdata的参数与C#委托定义不一致:C#委托用float**,但C传递的pnTagAddress是const int**,回调实际接收的是整数指针数组,类型不匹配直接导致垃圾值。修正委托定义:

public unsafe delegate float myCallBackPtr(int** tagValue1);

2. 委托被GC回收风险

局部委托obj在ConnectLiveData调用后可能被GC回收,导致回调时指针失效。将委托存储为类成员变量延长生命周期:

private static myCallBackPtr _callbackDelegate; // 类成员存储委托

public unsafe float StartLiveInfo(string tagNameFromRequest)
{
    // ... 其他代码 ...
    _callbackDelegate = new myCallBackPtr(CallMethod);
    var finalTagValue = ConnectLiveData(_callbackDelegate, tagName1, tagValue1, false, true);
    // ... 其他代码 ...
}

3. 字符串内存未固定

tagNameByte是局部数组,未固定就获取指针,GC可能移动数组内存导致指针失效。用fixed语句固定数组,同时补充C++字符串需要的终止符:

byte[] tagNameByte = Encoding.ASCII.GetBytes(tagName + '\0'); // 补充字符串终止符
fixed (byte* tagNamePtr = tagNameByte)
{
    IntPtr[] tagNameIntPtr = new IntPtr[2];
    tagNameIntPtr[0] = (IntPtr)tagNamePtr;
    tagNameIntPtr[1] = IntPtr.Zero;

    // ... 后续代码放在fixed块内 ...
}

4. 函数返回值类型不匹配

C++函数返回short,但C#定义为int,虽然能兼容但建议保持一致:

[DllImport("LTscriptenginedll.dll", CallingConvention = CallingConvention.StdCall)]
public static extern unsafe short ConnectLiveData(myCallBackPtr ps, IntPtr* test, int** temp, bool bdiscconectConnection, bool bPyRequest);

5. 回调函数修正

回调参数改为int**后,转换逻辑对应调整:

public unsafe static float CallMethod(int** tagValue1)
{
    var test4 = Convert.ToSingle(*tagValue1[0]);
    Console.WriteLine("callbacked");
    return test4;
}

内容的提问来源于stack exchange,提问作者Alok Sharma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 11:48:25