跳到主要内容

数据回调

Subscription::AddNode / AddMany 接受 std::function<void(DataValue const&)> 回调, 在 MI 数据变化时由服务端推送触发.

using OnDataChange = std::function<void(DataValue const&)>;
关联事件

DataValue 字段访问

回调里的 DataValue借用, 生命周期仅在回调内. 直接访问字段:

访问器类型说明
dv.Value()Variant实际值 (借用)
dv.StatusCode()uint32_t原始 32-bit 状态码
dv.StatusEnum()Status强类型枚举
dv.IsGood()bool等价 StatusCode() == 0
dv.SourceTimestamp()int64_tFileTime
dv.ServerTimestamp()int64_tFileTime

例子

简单日志

sub.AddNode("ns=2;s=T1", [](DataValue const& dv) {
std::cout << "ft=" << dv.SourceTimestamp()
<< " val=" << dv.Value().AsString()
<< " status=0x" << std::hex << dv.StatusCode() << "\n";
});

写文件

std::mutex write_lock;
sub.AddNode("ns=2;s=T1", [&](DataValue const& dv) {
std::lock_guard<std::mutex> lock(write_lock);
std::ofstream out("data.csv", std::ios::app);
out << dv.SourceTimestamp() << ","
<< dv.Value().AsString() << "\n";
});

UI 绑定 (Qt)

sub.AddNode("ns=2;s=T1", [&](DataValue const& dv) {
// 切回 UI 线程 (Qt)
QMetaObject::invokeMethod(qApp, [&, val = dv.Value().AsString()]() {
labelTemp->setText(QString::fromStdString(val));
});
});
必须切回 UI 线程

否则跨线程 UI 操作崩溃.


线程模型

  • 回调在 C 层 Publish 线程执行, 单线程串行 (同一订阅内部不会并发)
  • 回调里不要执行长操作 (>100 ms), 否则会阻塞 Publish 队列
  • 回调里抛异常会被 SDK 内部 try-catch 吃掉 (避免 native 崩溃), 不会传播
  • 跨订阅的回调在不同 Publish 线程, 可能并发 — 共享数据自己加锁

DataValue 借用注意

DataValue 在回调结束后失效, 不要保存指针 / 引用:

DataValue* g_last = nullptr;   // ❌ 不要这样

sub.AddNode("ns=2;s=T1", [&](DataValue const& dv) {
g_last = const_cast<DataValue*>(&dv); // ❌ use-after-free
});

如果需要把数据传出回调, 同步抽取值:

struct Snapshot { int64_t ft; double val; uint32_t status; };
std::atomic<Snapshot> latest;

sub.AddNode("ns=2;s=T1", [&](DataValue const& dv) {
Snapshot s;
s.ft = dv.SourceTimestamp();
s.status = dv.StatusCode();
dv.Value().TryGetDouble(s.val);
latest.store(s);
});

与 s.Events() 的关系

// 方式 A: 单订阅级 (本节)
sub.AddNode("ns=2;s=T1", [](DataValue const&) { /* ... */ });

// 方式 B: 全局统一通道 (所有订阅汇集)
s.Events().on_any = [](OpcUaEventEntry const& e) {
if (e.category == OpcUaEventCategory::DataChange) { /* ... */ }
};

两个事件对同一数据点都会触发, 选一个用即可.

下一步