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场景下服务端无法直接主动修改客户端已渲染的页面,用前端定时拉取的轻量方案即可:
- 新增一般处理程序
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; }
- 在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
相关产品推荐
相关产品推荐

