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

C#调用C++ DLL的ConnectLiveData获取实时更新值的实现问题

C# 对接LTscriptenginedll实时数据方案

现有代码核心问题

  • 委托、传入DLL的指针数组、值变量都定义在StartLiveInfo局部作用域内,方法执行完成后栈内存释放、委托被GC回收,C++侧持有的是野指针,回调无法正常触发,甚至会触发内存访问违规崩溃。
  • 错误将订阅接口ConnectLiveData当成单次读值接口,每次按钮点击都重复调用,会造成重复订阅、资源泄漏。
  • WebForm页面生命周期为短连接,按钮点击回发完成后服务端与客户端连接已断开,回调触发时无法直接修改已经渲染完成的客户端页面控件。
  • DllImport未指定调用约定,C++默认使用Cdecl调用约定,未显式指定会造成栈失衡,运行时随机崩溃。

正确实现步骤

1. 修正全局定义(委托、DLL导入、持久化字段)

所有传给C++ DLL的委托、内存指针必须提升为类级别字段,保证整个订阅生命周期内不被GC回收、不被释放:

// 回调委托签名需和C++头文件定义匹配,若你的DLL回调为无参版本,可删除参数直接保留无参结构
public delegate void LiveDataCallback(int updatedTagCount, IntPtr* updatedValues, IntPtr* tagNames);

[DllImport("LTscriptenginedll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe int ConnectLiveData(
    LiveDataCallback pCallBack, 
    IntPtr* pszTagNames, 
    int** pnTagAddress,
    bool bdiscconectConnection, 
    bool bPyRequest
);

// 持久化资源字段,禁止声明在局部方法内
private static LiveDataCallback _callbackInstance;
private static IntPtr[] _tagNamePtrArray;
private static GCHandle _tagNameArrayHandle;
private static GCHandle _tagNameBytesHandle;
private static int*[] _tagValuePtrArray;
private static GCHandle _valuePtrArrayHandle;
private static int _currentTagValue;
private static GCHandle _valueHandle;
// 全局缓存最新点位值
private static float _latestTagValue;

2. 初始化订阅(仅调用一次)

ConnectLiveData是订阅注册接口,不需要每次读值都调用,在ASP.NET场景下放到Global.asax的Application_Start生命周期中执行一次即可:

public unsafe void InitLiveDataSubscribe()
{
    string targetTagName = "M1.Speed";
    // 固定点位名称字符串内存,禁止GC移动/释放
    byte[] tagNameBytes = Encoding.ASCII.GetBytes(targetTagName);
    _tagNameBytesHandle = GCHandle.Alloc(tagNameBytes, GCHandleType.Pinned);
    _tagNamePtrArray = new IntPtr[2];
    _tagNamePtrArray[0] = _tagNameBytesHandle.AddrOfPinnedObject();
    _tagNamePtrArray[1] = IntPtr.Zero; // 保留原逻辑的数组结尾哨兵
    _tagNameArrayHandle = GCHandle.Alloc(_tagNamePtrArray, GCHandleType.Pinned);

    // 固定点位值存储内存
    _currentTagValue = 0;
    _valueHandle = GCHandle.Alloc(_currentTagValue, GCHandleType.Pinned);
    _tagValuePtrArray = new int*[2];
    _tagValuePtrArray[0] = (int*)_valueHandle.AddrOfPinnedObject();
    _tagValuePtrArray[1] = null;
    _valuePtrArrayHandle = GCHandle.Alloc(_tagValuePtrArray, GCHandleType.Pinned);

    // 持久化回调委托,防止GC回收
    _callbackInstance = OnTagValueUpdated;

    // 注册订阅,仅执行一次
    ConnectLiveData(
        _callbackInstance,
        (IntPtr*)_tagNameArrayHandle.AddrOfPinnedObject(),
        (int**)_valuePtrArrayHandle.AddrOfPinnedObject(),
        false,
        true
    );
}

3. 实现回调逻辑更新最新值

C++侧检测到点位变化时会触发该回调,直接读取固定内存中的最新值更新全局缓存即可:

public static unsafe void OnTagValueUpdated(int updatedTagCount, IntPtr* updatedValues, IntPtr* tagNames)
{
    // 单点位订阅直接读取第0位的值即可
    _latestTagValue = Convert.ToSingle(*_tagValuePtrArray[0]);
}

若你使用的DLL回调是无参版本,直接把委托改成无参,方法内执行同样的值读取逻辑即可。

4. 修正Web端界面刷新逻辑

Web场景下服务端无法直接主动修改客户端已渲染的页面,用前端定时拉取的轻量方案即可:

  1. 新增一般处理程序GetLatestTagValue.ashx用于返回最新值:
public class GetLatestTagValue : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/plain";
        context.Response.Write(_latestTagValue.ToString());
    }
    public bool IsReusable => false;
}
  1. 在ASPX页面中加入JS定时器,每秒拉取最新值更新文本框:
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<script type="text/javascript">
setInterval(function () {
    fetch('/GetLatestTagValue.ashx')
        .then(res => res.text())
        .then(val => {
            document.getElementById('<%=TextBox1.ClientID%>').value = val;
        })
}, 1000);
</script>

原按钮点击事件中不需要再调用StartLiveInfo,订阅初始化完成后前端会自动刷新最新值。程序退出时记得传bdiscconectConnection=true调用一次ConnectLiveData释放订阅资源。


内容的提问来源于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.26 11:06:19