OPC DA 通讯开发笔记

本文档基于自写的 OPC_Test_Pro 项目编写,讲解 OPC DA 通讯的原理、句柄关系及每个方法的实现细节。

本文只用于对该项目的技术分析。项目为自用项目,源码不对外开源。


目录

  1. 项目概述
  2. 核心类结构
  3. 句柄详解(重点)
  4. 方法详解
  5. 完整使用流程
  6. 常见问题

1. 项目概述

1.1 项目结构

复制代码
OPC_Test_Pro/
├── OPC_Test_Pro/           # 主程序(WinForm界面)
│   ├── FrmOPCDA.cs        # 主窗体(界面交互)
│   └── FrmOPCItemSelect.cs # 变量选择窗体
├── Related_OPCLib/         # OPC通讯核心库
│   ├── OPCDALib.cs        # OPC DA核心类
│   ├── OPCDAGroup.cs      # OPC组定义
│   ├── OPCDAItem.cs       # OPC项定义
│   └── OPCDAAsyncEventArgs.cs # 异步事件参数
└── Related_DataConvertLib/ # 数据转换库
    └── OperateResult.cs   # 操作结果类

1.2 核心依赖

  • OPCAutomation.dll: OPC DA COM 组件的.NET互操作程序集

2. 核心类结构

2.1 OPCDALib - 核心通讯类

位置 : Related_OPCLib/OPCDALib.cs

核心属性:

csharp 复制代码
private OPCServer opcServer;              // OPC服务器对象
private List<Array> serverHandleList;     // 服务器句柄列表(重要!)
public List<OPCDAGroup> OPCGroupList;     // OPC组列表

核心事件:

csharp 复制代码
public event EventHandler<OPCDAAsyncEventArgs> OnReadCompleted;  // 异步读取完成事件
public event EventHandler<OPCDAAsyncEventArgs> OnDataChanged;    // 数据订阅变化事件

2.2 OPCDAGroup - OPC组定义

位置 : Related_OPCLib/OPCDAGroup.cs

csharp 复制代码
public class OPCDAGroup
{
    public bool IsActive { get; set; }          // 组是否激活
    public int UpdateRate { get; set; }         // 更新速率(毫秒)
    public string GroupName { get; set; }       // 组名称
    public int GroupHandle { get; set; }        // 组客户端句柄(用户自定义)
    public OPCGroup OPCGroup { get; set; }      // OPC组对象(系统创建)
    public OPCDAItem[] OPCDAItems { get; set; } // 包含的变量数组
}

2.3 OPCDAItem - OPC项(变量)定义

位置 : Related_OPCLib/OPCDAItem.cs

csharp 复制代码
public class OPCDAItem
{
    public string ItemId { get; set; }      // 变量ID(如 "Channel1.Device1.Tag1")
    public int ClientHandle { get; set; }   // 客户端句柄(用户自定义,唯一标识)
    public object Value { get; set; }       // 变量值
    public int ServerHandle { get; set; }   // 服务器句柄(系统分配,用于读写操作)
}

3. 句柄详解(重点)

3.1 句柄关系图

复制代码
┌─────────────────────────────────────────────────────────────┐
│                         OPC 客户端                          │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────────┐       ┌──────────────────────────────┐   │
│  │ OPCDAGroup   │       │ OPCDAItem[]                  │   │
│  ├──────────────┤       ├──────────────────────────────┤   │
│  │ GroupHandle  │◄──────┤ ClientHandle (0, 1, 2, ...)  │   │
│  │ = 0          │       │ ItemId                       │   │
│  └──────┬───────┘       │ "Channel1.Device1.Tag1"      │   │
│         │               │ "Channel1.Device1.Tag2"      │   │
│         │               └──────────────┬───────────────┘   │
│         │                              │                    │
│         │         添加变量时            │                    │
│         │         系统分配             │                    │
│         ▼                              ▼                    │
│  ┌──────────────┐              ┌────────────────┐          │
│  │ OPCGroup     │              │ ServerHandle[] │          │
│  │ (COM对象)    │              │ [0] 无效值     │          │
│  │              │              │ [1] 12345      │          │
│  │              │              │ [2] 12346      │          │
│  └──────────────┘              │ [3] 12347      │          │
│                                 └────────────────┘          │
│                                                              │
└─────────────────────────────────────────────────────────────┘
                              │
                              │ 读写操作时使用
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                         OPC 服务器                          │
└─────────────────────────────────────────────────────────────┘

3.2 三种句柄详解

3.2.1 GroupHandle(组客户端句柄)
  • 定义者: 用户(程序员)
  • 用途: 唯一标识一个 OPC 组
  • 值范围: 用户自定义,通常是 0, 1, 2...
  • 使用场景 :
    • 查找对应的 OPC 组
    • 异步操作的事务ID

示例 (FrmOPCDA.cs:125):

csharp 复制代码
OPCDAGroup group = new OPCDAGroup()
{
    GroupName = "Group1",
    IsActive = true,
    GroupHandle = 0,  // 用户自定义组ID
};
3.2.2 ClientHandle(项客户端句柄)
  • 定义者: 用户(程序员)
  • 用途: 唯一标识一个 OPC 变量(项)
  • 值范围: 用户自定义,通常是 0, 1, 2...(对应变量在数组中的索引)
  • 重要特性: 必须在同一个组内唯一
  • 使用场景 :
    • 异步读取时匹配返回的数据
    • 写入时指定要写入的变量

示例 (FrmOPCDA.cs:130-136):

csharp 复制代码
for (int i = 0; i < selList.Count; i++)
{
    group.OPCDAItems[i] = new OPCDAItem()
    {
        ItemId = selList[i],
        ClientHandle = i  // 0, 1, 2, ...
    };
}
3.2.3 ServerHandle(服务器句柄)
  • 定义者: OPC 服务器系统自动分配
  • 用途: OPC 服务器内部标识变量,用于实际的读写操作
  • 值范围: 系统分配,通常是任意整数(如 12345, 12346...)
  • 重要特性 :
    • 添加变量后由系统返回
    • 数组从索引 1 开始(索引 0 无效)
    • 必须配合 ServerHandle 使用

示例 (OPCDALib.cs:204-205):

csharp 复制代码
opcItems.AddItems(count, itemIds, clentHandles,
                  out Array serverHandles,  // 系统返回的服务器句柄数组
                  out Array errors);

3.3 句柄转换关系

复制代码
用户操作流程:
1. 定义 ClientHandle (0, 1, 2)
2. 添加变量到服务器
3. 服务器返回 ServerHandle [无用, 12345, 12346, 12347]
4. 读写时通过 ClientHandle 找到对应的 ServerHandle

读取流程:
ClientHandle(1) ──查找──> ServerHandle数组索引2 ──获取──> ServerHandle值(12346) ──调用──> SyncRead()

代码示例 (OPCDALib.cs:362):

csharp 复制代码
// 通过 ClientHandle 找到在数组中的位置
int itemIndex = this.OPCGroupList[index].OPCDAItems.ToList()
    .FindIndex(c => c.ClientHandle == clientHandle);

// +1 是因为 ServerHandle 数组从索引 1 开始
serverHandle.SetValue(
    this.serverHandleList[index].GetValue(itemIndex + 1),
    1
);

4. 方法详解

4.1 连接相关方法

4.1.1 GetOPCServerNodes - 获取服务器节点列表

位置: OPCDALib.cs:24-48

原理:

csharp 复制代码
1. 获取本机IP地址列表
2. 通过DNS反向查询获取主机名
3. 过滤并去重,返回可用的OPC服务器节点

返回示例:

复制代码
["DESKTOP-ABC123", "192.168.1.100"]
4.1.2 GetOPCServerNames - 获取OPC服务器名称列表

位置: OPCDALib.cs:54-73

原理:

csharp 复制代码
1. 创建 OPCServer 对象
2. 调用 GetOPCServers() 获取本机所有OPC服务器
3. 返回服务器名称列表

返回示例:

复制代码
["Kepware.KEPServerEX.V6", "OPC.SimaticNET"]
4.1.3 Connect - 建立OPC连接

位置: OPCDALib.cs:81-98

参数:

  • serverNode: 服务器节点(IP或主机名)
  • serverName: OPC服务器名称

原理:

csharp 复制代码
1. 创建新的 OPCServer 实例
2. 调用 Connect(serverName, serverNode) 连接到OPC服务器
3. 成功返回 OperateResult.CreateSuccessResult()
4. 失败捕获异常并返回错误信息

调用示例:

csharp 复制代码
var result = objOPC.Connect("DESKTOP-ABC123", "Kepware.KEPServerEX.V6");

4.2 变量管理方法

4.2.1 GetOPCBrower - 获取OPC变量列表

位置: OPCDALib.cs:115-131

原理:

csharp 复制代码
1. 创建 OPCBrowser 对象
2. 调用 ShowBranches() 显示分支
3. 调用 ShowLeafs(true) 显示叶子节点
4. 遍历返回所有变量路径

返回示例:

复制代码
[
    "Channel1.Device1.Tag1",
    "Channel1.Device1.Tag2",
    "Channel2.Device1.Tag1"
]
4.2.2 InitOPCGroup - 初始化OPC组(核心方法)

位置: OPCDALib.cs:137-164

参数:

  • opcGroupList: OPC组列表

原理详解:

csharp 复制代码
1. 保存 OPCGroupList 到类属性(供后续读写使用)
2. 清空所有旧的OPC组
3. 设置默认属性:
   - DefaultGroupDeadband = 0 (死区为0,任何变化都触发)
   - DefaultGroupIsActive = true (组默认激活)
4. 遍历每个组,调用 AddOPCGroup() 添加
5. 返回操作结果

重要代码 (OPCDALib.cs:152):

csharp 复制代码
serverHandleList = new List<Array>(); // 清空并重新初始化
4.2.3 AddOPCGroup - 添加单个OPC组(私有方法)

位置: OPCDALib.cs:171-214

原理详解:

csharp 复制代码
1. 创建 OPCGroup 对象
   ├─ 设置 IsActive = true
   ├─ 设置 DeadBand = 0
   ├─ 设置 IsSubscribed = true (启用订阅)
   ├─ 设置 UpdateRate
   └─ 设置 ClientHandle = GroupHandle

2. 绑定事件:
   ├─ AsyncReadComplete (异步读取完成)
   ├─ DataChange (数据变化订阅)
   └─ AsyncWriteComplete (异步写入完成)

3. 添加OPC项(变量):
   ├─ 创建数组(长度 = 变量数 + 1)
   │   ├─ itemIds: 存储变量ID
   │   └─ clientHandles: 存储客户端句柄
   │
   ├─ 填充数组(从索引1开始,索引0无效):
   │   ├─ itemIds.SetValue(ItemId, i + 1)
   │   └─ clientHandles.SetValue(ClientHandle, i + 1)
   │
   ├─ 调用 AddItems() 添加到服务器
   │   └─ 返回 serverHandles (服务器句柄数组)
   │
   └─ 保存 serverHandles 到 serverHandleList

关键代码 (OPCDALib.cs:193-205):

csharp 复制代码
Array itemIds = Array.CreateInstance(typeof(string), count + 1);
Array clentHandles = Array.CreateInstance(typeof(int), count + 1);

// OPC 要求从索引 1 开始
for (int i = 0; i < count; i++)
{
    itemIds.SetValue(opcGroup.OPCDAItems[i].ItemId, i + 1);
    clentHandles.SetValue(opcGroup.OPCDAItems[i].ClientHandle, i + 1);
}

opcItems.AddItems(count, itemIds, clentHandles,
                  out Array serverHandles,  // 服务器返回
                  out Array errors);

serverHandleList.Add(serverHandles);  // 保存供后续使用

4.3 读取方法

4.3.1 SyncRead - 同步读取

位置: OPCDALib.cs:273-299

原理:

csharp 复制代码
遍历每个 OPC 组:
    1. 获取该组的 ServerHandle 数组
    2. 调用 SyncRead() 同步读取
    3. 参数说明:
       - Source: 1 (从缓存读取)
       - ItemCount: 变量数量
       - ServerHandles: 服务器句柄数组
    4. 返回读取到的值数组
    5. 转换为 object[] 并添加到结果列表

调用示例 (FrmOPCDA.cs:178):

csharp 复制代码
List<object[]> value = this.objOPC.SyncRead();
// value[0] 包含第一个组所有变量的值
4.3.2 AsyncRead - 异步读取

位置: OPCDALib.cs:327-345

原理:

csharp 复制代码
遍历每个 OPC 组:
    1. 获取该组的 ServerHandle 数组
    2. 调用 AsyncRead() 发起异步读取请求
    3. 参数说明:
       - ItemCount: 变量数量
       - ServerHandles: 服务器句柄数组
       - Errors: 返回错误码数组
       - TransactionID: 设为 GroupHandle(用于回调时识别)
       - CancelID: 返回的取消ID
    4. 读取完成后触发 AsyncReadComplete 事件

事件处理 (FrmOPCDA.cs:213-239):

csharp 复制代码
private void ObjOPC_OnReadCompleted(object sender, OPCDAAsyncEventArgs e)
{
    // 1. 通过 GroupHandle 找到对应的组
    int groupIndex = this.objOPC.OPCGroupList
        .FindIndex(c => c.GroupHandle == e.GroupHandle);

    // 2. 通过 ClientHandle 匹配,将值赋给对应的 Item
    for (int i = 0; i < opcDAItems.Length; i++)
    {
        for (int j = 0; j < e.Count; j++)
        {
            if (opcDAItems[i].ClientHandle == e.ClientItemsHandle[j])
            {
                opcDAItems[i].Value = e.Value[j];
            }
        }
    }
}

4.4 订阅方法

4.4.1 开启订阅

位置: FrmOPCDA.cs:246-256

原理:

csharp 复制代码
1. 订阅 OnDataChanged 事件
2. 服务器自动检测数据变化
3. 变化时触发 DataChange 事件
4. 无需主动调用读取方法

事件处理 (FrmOPCDA.cs:258-281):

csharp 复制代码
private void ObjOPC_OnDataChanged(object sender, OPCDAAsyncEventArgs e)
{
    // e.ClientItemsHandle: 变化的变量 ClientHandle 数组
    // e.Value: 变化后的值数组

    for (int i = 0; i < e.Count; i++)
    {
        int handle = e.ClientItemsHandle[i];

        // 遍历所有组和变量,找到匹配的 ClientHandle
        for (int j = 0; j < this.objOPC.OPCGroupList.Count; j++)
        {
            for (int k = 0; k < this.objOPC.OPCGroupList[j].OPCDAItems.Length; k++)
            {
                if (this.objOPC.OPCGroupList[j].OPCDAItems[k].ClientHandle == handle)
                {
                    // 更新值
                    this.objOPC.OPCGroupList[j].OPCDAItems[k].Value = e.Value[i];
                }
            }
        }
    }
}

4.5 写入方法

4.5.1 SyncWrite - 同步写入

位置: OPCDALib.cs:353-380

参数:

  • value: 要写入的值
  • groupHandle: 组句柄
  • clientHandle: 项句柄

原理详解:

csharp 复制代码
1. 通过 groupHandle 找到对应的 OPC 组索引
2. 通过 clientHandle 找到对应的 OPC 项索引
3. 从 serverHandleList 获取 ServerHandle:
   ├─ itemIndex = FindIndex(ClientHandle == clientHandle)
   ├─ serverHandleValue = serverHandleList[itemIndex + 1]
   │   (注意: +1 是因为数组从索引1开始)
4. 创建写入数组(长度为2,使用索引1):
   ├─ writeServerHandles.SetValue(serverHandleValue, 1)
   └─ writeValues.SetValue(value, 1)
5. 调用 SyncWrite(1, writeServerHandles, writeValues, out errors)

关键代码 (OPCDALib.cs:362):

csharp 复制代码
// 找到 ClientHandle 对应的索引位置
int itemIndex = this.OPCGroupList[index].OPCDAItems.ToList()
    .FindIndex(c => c.ClientHandle == clientHandle);

// 从 serverHandleList 获取对应的 ServerHandle(+1 是关键)
serverHandle.SetValue(
    this.serverHandleList[index].GetValue(itemIndex + 1),
    1
);

调用示例 (FrmOPCDA.cs:312):

csharp 复制代码
// 正确示例: 使用变量下拉框的 SelectedIndex
var result = this.objOPC.SyncWrite(
    this.txt_SetValue.Text.Trim(),  // 写入的值
    0,                               // GroupHandle
    this.cmb_SetVariable.SelectedIndex // ClientHandle (0, 1, 2...)
);

❌ 错误示例:

csharp 复制代码
// 错误: 使用了服务器名称下拉框的索引
var result = this.objOPC.SyncWrite(
    "100",
    0,
    this.cmb_ServerName.SelectedIndex  // 这是服务器索引,不是变量句柄!
);
// 结果: 找不到对应的 ClientHandle,导致索引超限错误
4.5.2 AsyncWrite - 异步写入

位置: OPCDALib.cs:389-416

参数: 与 SyncWrite 相同

原理:

csharp 复制代码
1. 参数准备与 SyncWrite 完全相同
2. 调用 AsyncWrite() 而非 SyncWrite():
   AsyncWrite(1, serverHandle, values, out errors, transactionID, out cancelID)
3. 写入完成后触发 AsyncWriteComplete 事件

差异对比:

特性 SyncWrite AsyncWrite
阻塞等待 ✅ 是 ❌ 否
返回时机 写入完成后返回 立即返回
适用场景 单次少量写入 批量写入
事件回调 AsyncWriteComplete

5. 完整使用流程

5.1 初始化流程

csharp 复制代码
// 步骤1: 创建 OPC 对象
OPCDALib objOPC = new OPCDALib();

// 步骤2: 获取服务器节点
var nodes = objOPC.GetOPCServerNodes();
// 返回: ["DESKTOP-ABC123"]

// 步骤3: 获取OPC服务器名称
var serverNames = objOPC.GetOPCServerNames("DESKTOP-ABC123XXX");
// 返回: ["Kepware.KEPServerEX.V6"]

// 步骤4: 建立连接
var result = objOPC.Connect("DESKTOP-ABC123XXX", "Kepware.KEPServerEX.V6");
if (!result.IsSuccess)
{
    MessageBox.Show("连接失败: " + result.Message);
    return;
}

// 步骤5: 获取变量列表
var varList = objOPC.GetOPCBrower();
// 返回: ["Channel1.Device1.Tag1", "Channel1.Device1.Tag2", ...]

// 步骤6: 选择变量并创建OPC组
List<string> selectedVars = new List<string>()
{
    "Channel1.Device1.Tag1",
    "Channel1.Device1.Tag2"
};

OPCDAGroup group = new OPCDAGroup()
{
    GroupName = "Group1",
    IsActive = true,
    GroupHandle = 0,
    UpdateRate = 1000
};

group.OPCDAItems = new OPCDAItem[selectedVars.Count];
for (int i = 0; i < selectedVars.Count; i++)
{
    group.OPCDAItems[i] = new OPCDAItem()
    {
        ItemId = selectedVars[i],
        ClientHandle = i  // 0, 1
    };
}

// 步骤7: 初始化OPC组
List<OPCDAGroup> groups = new List<OPCDAGroup>() { group };
result = objOPC.InitOPCGroup(groups);
if (!result.IsSuccess)
{
    MessageBox.Show("初始化失败: " + result.Message);
    return;
}

5.2 读取流程

csharp 复制代码
// 方式1: 同步读取
List<object[]> values = objOPC.SyncRead();
if (values != null && values.Count > 0)
{
    foreach (var val in values[0])
    {
        Console.Write(val.ToString() + " ");
    }
}

// 方式2: 异步读取
// 订阅事件
objOPC.OnReadCompleted += (sender, e) =>
{
    int groupIndex = objOPC.OPCGroupList.FindIndex(g => g.GroupHandle == e.GroupHandle);

    for (int i = 0; i < objOPC.OPCGroupList[groupIndex].OPCDAItems.Length; i++)
    {
        for (int j = 0; j < e.Count; j++)
        {
            if (objOPC.OPCGroupList[groupIndex].OPCDAItems[i].ClientHandle == e.ClientItemsHandle[j])
            {
                var item = objOPC.OPCGroupList[groupIndex].OPCDAItems[i];
                Console.WriteLine($"{item.ItemId} = {e.Value[j]}");
            }
        }
    }
};

// 发起异步读取
objOPC.AsyncRead();

5.3 订阅流程

csharp 复制代码
// 开启订阅
objOPC.OnDataChanged += (sender, e) =>
{
    for (int i = 0; i < e.Count; i++)
    {
        int handle = e.ClientItemsHandle[i];

        foreach (var group in objOPC.OPCGroupList)
        {
            foreach (var item in group.OPCDAItems)
            {
                if (item.ClientHandle == handle)
                {
                    Console.WriteLine($"{item.ItemId} 变化: {e.Value[i]}");
                }
            }
        }
    }
};

// 注意: IsSubscribed = true 时自动订阅,无需额外操作

5.4 写入流程

csharp 复制代码
// 同步写入
var result = objOPC.SyncWrite(
    "100",        // 要写入的值
    0,            // GroupHandle
    0             // ClientHandle (第一个变量)
);

if (result.IsSuccess)
{
    Console.WriteLine("写入成功");
}
else
{
    Console.WriteLine("写入失败: " + result.Message);
}

// 异步写入
result = objOPC.AsyncWrite(
    "200",
    0,
    1  // 写入第二个变量
);

5.5 断开连接

csharp 复制代码
objOPC.DisConnect();

6. 常见问题

6.1 索引超限错误

问题 : 调用 SyncWrite()AsyncWrite() 时报"索引超限"错误

原因:

  • 传入了错误的 clientHandle
  • 通常混淆了下拉框的 SelectedIndex

解决方案:

csharp 复制代码
// ❌ 错误
var result = objOPC.SyncWrite(value, 0, cmb_ServerName.SelectedIndex);

// ✅ 正确
var result = objOPC.SyncWrite(value, 0, cmb_SetVariable.SelectedIndex);

6.2 为什么数组从索引1开始?

原因: OPC DA COM 规范要求从索引1开始,索引0保留未使用

代码处理:

csharp 复制代码
// 创建数组时长度 = 变量数 + 1
Array itemIds = Array.CreateInstance(typeof(string), count + 1);

// 填充时从索引1开始
for (int i = 0; i < count; i++)
{
    itemIds.SetValue(opcGroup.OPCDAItems[i].ItemId, i + 1);
}

// 访问时也要 +1
object serverHandle = serverHandles.GetValue(itemIndex + 1);

6.3 ClientHandle vs ServerHandle 何时使用?

场景 使用句柄 原因
异步读取回调 ClientHandle 服务器返回的是 ClientHandle
数据订阅回调 ClientHandle 服务器返回的是 ClientHandle
同步读取 ServerHandle 需要传递给 OPC 服务器
同步写入 ServerHandle 需要传递给 OPC 服务器
异步写入 ServerHandle 需要传递给 OPC 服务器

6.4 如何添加多个OPC组?

csharp 复制代码
OPCDAGroup group1 = new OPCDAGroup()
{
    GroupName = "Group1",
    GroupHandle = 0,
    OPCDAItems = new OPCDAItem[] { /* ... */ }
};

OPCDAGroup group2 = new OPCDAGroup()
{
    GroupName = "Group2",
    GroupHandle = 1,  // 不同的 GroupHandle
    OPCDAItems = new OPCDAItem[] { /* ... */ }
};

List<OPCDAGroup> groups = new List<OPCDAGroup>() { group1, group2 };
objOPC.InitOPCGroup(groups);

6.5 异步写入完成后如何处理?

当前代码: AsyncWriteComplete 事件为空

建议添加:

csharp 复制代码
private void KepGroup_AsyncWriteComplete(int TransactionID, int NumItems,
                                         ref Array ClientHandles, ref Array Errors)
{
    if (OnWriteCompleted != null)
    {
        var e = new OPCDAAsyncEventArgs()
        {
            GroupHandle = TransactionID,
            Count = NumItems,
            ClientItemsHandle = ArrayToIntArray(ClientHandles),
            Errors = ArrayToIntArray(Errors)
        };

        OnWriteCompleted(this, e);
    }
}

7. 总结

7.1 核心要点

  1. 三种句柄必须区分清楚:

    • GroupHandle: 用户定义的组ID
    • ClientHandle: 用户定义的变量ID
    • ServerHandle: 系统分配的句柄,用于实际读写
  2. ServerHandle 数组从索引1开始:

    • 创建时长度 = 变量数 + 1
    • 访问时索引 = 变量索引 + 1
  3. 异步操作需要事件回调:

    • 异步读取: OnReadCompleted
    • 数据订阅: OnDataChanged
    • 异步写入: AsyncWriteComplete(需实现)
  4. 读写时使用 ServerHandle:

    • 通过 ClientHandle 找到变量索引
    • 通过索引获取 ServerHandle
    • 使用 ServerHandle 调用读写方法

7.2 最佳实践

  1. 保持 ClientHandle 连续且从0开始
  2. 一个应用只创建一个 OPCServer 实例
  3. 合理设置 UpdateRate 避免频繁读取
  4. 使用订阅替代轮询读取
  5. 正确处理异常和错误码

附录

参考资料

  • OPC DA 规范: OPC Foundation
  • OPCAutomation.dll: OPC DA 2.0/3.0 COM 组件
相关推荐
taoqick2 小时前
rubric系列论文粗读笔记
笔记
航Hang*2 小时前
第2章:进阶Linux系统——第8节:配置与管理MariaDB服务器
linux·运维·服务器·数据库·笔记·学习·mariadb
自小吃多2 小时前
电气安全检测说明书
笔记·安全
水蓝烟雨2 小时前
LeetCode刷题笔记:合并两个有序链表(0021)
笔记·leetcode·链表
鱼鳞_2 小时前
Java学习笔记_Day23(双列集合)
java·笔记·学习
ZhiqianXia2 小时前
Pytorch 学习笔记(9): PyTorch.Compile
pytorch·笔记·学习
Xudde.2 小时前
班级作业笔记报告0x09
笔记·学习·安全·web安全·php
ZzYH223 小时前
文献阅读 260407-Leveraging edge artificial intelligence for sustainable agriculture
笔记
劳埃德福杰3 小时前
Windows系统卸载Edge浏览器
前端·windows·edge