Browse 自动分页 (上层封装)
C# 提供 BrowseWithPaging + BrowseNext 的自动循环, C SDK 没有现成 API, 需要业务自己封装. 本页给出一个推荐模板.
前置阅读
- 单层 Browse 请看 Browse.
- 续翻分页 API 请看 BrowseNext.
推荐封装
把 Browse + 循环 BrowseNext 包装成一个 helper 函数, 业务直接拿一个完整的 (NodeId, BrowseName, NodeClass) 列表:
/* browse_all.h */
#include <darra_opcua/darra_opcua.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char node_id[128];
char browse_name[128];
int32_t node_class;
} BrowsedRef;
typedef struct {
BrowsedRef* items;
uint32_t count;
uint32_t capacity;
} BrowsedList;
void BrowsedList_Init(BrowsedList* l) {
l->items = NULL; l->count = 0; l->capacity = 0;
}
void BrowsedList_Free(BrowsedList* l) {
free(l->items); l->items = NULL; l->count = 0; l->capacity = 0;
}
static int append_one(BrowsedList* l, const DarraUa_ReferenceDescription* rd)
{
if (l->count == l->capacity) {
uint32_t cap = l->capacity ? l->capacity * 2 : 16;
BrowsedRef* p = (BrowsedRef*)realloc(l->items, cap * sizeof(BrowsedRef));
if (!p) return 0;
l->items = p; l->capacity = cap;
}
BrowsedRef* item = &l->items[l->count++];
DarraUa_Ref_GetNodeIdString(rd, item->node_id, (int32_t)sizeof(item->node_id));
const char* bn = DarraUa_Ref_GetBrowseName(rd);
snprintf(item->browse_name, sizeof(item->browse_name), "%s", bn ? bn : "");
item->node_class = DarraUa_Ref_GetNodeClass(rd);
return 1;
}
DarraUa_Status BrowseAll(DarraUa_SessionHandle h,
const char* node_id,
DarraUa_NodeClass filter,
BrowsedList* out)
{
BrowsedList_Init(out);
DarraUa_BrowseResult* page = NULL;
DarraUa_Status st = DarraUa_Session_BrowseNode(h, node_id, filter, &page);
if (!DARRA_UA_STATUS_IS_GOOD(st) || !page) return st;
while (page) {
uint32_t n = DarraUa_BrowseResult_GetCount(page);
for (uint32_t i = 0; i < n; ++i) {
append_one(out, DarraUa_BrowseResult_GetReference(page, i));
}
int32_t cp_len = 0;
const uint8_t* cp = DarraUa_BrowseResult_GetContinuationPoint(page, &cp_len);
if (!cp || cp_len <= 0) {
DarraUa_BrowseResult_Delete(page);
break;
}
uint8_t cp_copy[256];
if (cp_len > (int32_t)sizeof(cp_copy)) {
DarraUa_BrowseResult_Delete(page);
break;
}
memcpy(cp_copy, cp, (size_t)cp_len);
DarraUa_BrowseResult_Delete(page);
page = NULL;
DarraUa_Session_BrowseNext(h, 0, cp_copy, cp_len, &page);
}
return DARRA_UA_STATUS_GOOD;
}
用法
BrowsedList list;
DarraUa_Status st = BrowseAll(h, "ns=2;s=BigFolder",
DARRA_UA_NODE_CLASS_VARIABLE, &list);
if (DARRA_UA_STATUS_IS_GOOD(st)) {
printf("Got %u children\n", list.count);
for (uint32_t i = 0; i < list.count; ++i) {
printf(" %s -> %s\n", list.items[i].browse_name,
list.items[i].node_id);
}
}
BrowsedList_Free(&list);
何时用 BrowseNode 直接 vs BrowseAll 封装
| 场景 | API |
|---|---|
| 已知子数 ≤ 50, 不关心分页 | DarraUa_Session_BrowseNode 直接用 |
| 不确定子数, 安全起见 | BrowseAll 封装 |
| 100+ 子节点的大节点 | BrowseAll 必选 |
| 一次浏览多个节点 | DarraUa_Session_BrowseNodes (注意当前 BrowseNodes 不返回 ContinuationPoint) |
ContinuationPoint 是否会过期
服务端 ContinuationPoint 通常 5 分钟超时. 客户端逻辑在 5 分钟内完成翻页就行, 否则需要从头 Browse 重新拿 CP.