如何通过代码连接远程OPC服务器

如何通过代码连接远程OPC服务器,第1张

第62行的代码,DCOM既然是远程服务器,那它就应该是可以运行在另外一台机器上,然后被其他机器的客户端所使用。所以C++的客户端代码里,你可以通过编程的方式指定服务器的名称,但是对于C#来说,因为连接到DCOM服务器并激活COM对象的 *** 作是由CLR完成的,没有办法在代码里指定。不过不用着急,指定DCOM服务器还有另外一个方式,就是修改注册表的键值,告诉本机的COM运行库,服务器在另外一台机器上,请把下面的键值添加到客户端机器的注册表里:

HKEY_CLASSES_ROOT\APPID\{5e9ddec7-5767-11cf-beab-00aa006c3606}\RemoteServerName=<机器名>

下面的是我的代码

public void ListAll(Guid catid, out OpcServers[] serverslist)

{

serverslist = null

Dispose()

Guid guid = new Guid("13486D51-4821-11D2-A494-3CB306C10000")

Type typeoflist = Type.GetTypeFromCLSID(guid)

OPCListObj = Activator.CreateInstance(typeoflist)

ifList = (IOPCServerList)OPCListObj

if (ifList == null)

Marshal.ThrowExceptionForHR(HRESULTS.E_ABORT)

ifList.EnumClassesOfCategories(1, ref catid, 0, ref catid, out EnumObj)

if (EnumObj == null)

Marshal.ThrowExceptionForHR(HRESULTS.E_ABORT)

ifEnum = (IEnumGUID)EnumObj

if (ifEnum == null)

Marshal.ThrowExceptionForHR(HRESULTS.E_ABORT)

int maxcount = 300

IntPtr ptrGuid = Marshal.AllocCoTaskMem(maxcount * 16)

int count = 0

ifEnum.Next(maxcount, ptrGuid, out count)

if (count <1)

{ Marshal.FreeCoTaskMem(ptrGuid)return}

serverslist = new OpcServers[count]

byte[] guidbin = new byte[16]

int runGuid = (int)ptrGuid

for (int i = 0i <counti++)

{

serverslist[i] = new OpcServers()

Marshal.Copy((IntPtr)runGuid, guidbin, 0, 16)

serverslist[i].ClsID = new Guid(guidbin)

ifList.GetClassDetails(ref serverslist[i].ClsID,

out serverslist[i].ProgID, out serverslist[i].ServerName)

runGuid += 16

}

Marshal.FreeCoTaskMem(ptrGuid)

Dispose()

}

用此代码连接本地OPC服务器是没有问题的,但是通过上面对注册表的编辑,将服务器换成远程服务器,就不能访问,代码运行起来还是连接的是本地OPC服务器。请教各位我该如何设置才能连接远程OPC服务器

首先申明OPC对象:

Option Base 1

Dim WithEvents ServerObj As OPCServer 'OPC Server对象,连接OPC服务器

Dim GroupsObj As OPCGroups 'OPC Groups对象,添加OPC组

Dim WithEvents GroupObj As OPCGroup 'OPC Group对象

Dim ItemsObj As OPCItems 'OPC Item集合

Dim ServerHandles() As Long '服务器端OPC Item的句柄

Dim ClientHandles() as Long'客户端OPC Item的句柄

Dim ItemId(2) As String

Dim Errors() As Long

接下来,生成各个对象:

If ServerObj Is Nothing Then Set ServerObj = New OPCServer

'连接OPC服务器

If ServerObj.ServerState = OPCDisconnected Then

ServerObj.Connect ("OPC.SimaticNET") '假设OPC服务器运行在本机

End If

If GroupsObj Is Nothing Then Set GroupsObj = ServerObj.OPCGroups

If GroupObj Is Nothing Then Set GroupObj = GroupsObj.Add

If ItemsObj Is Nothing Then Set ItemsObj = GroupObj.OPCItems

GroupObj.IsActive = True '设置组为活动状态

'假设有两个数据源,一个是8位开关量输入,一个是8位开关量输出

ItemId(1) = "S7:[S7 connection_1]IB0"

ItemId(2) = "S7:[S7 connection_1]QB0"

ClientHandles(1) = 1

ClientHandles(2) = 2

'添加组项目,ServerHandles数组的值为各个OPC Item的服务器句柄,

' ClientHandles数组的值为各个OPC Item的客户端句柄,由应用程序设定

Call ItemsObj.AddItems(2, ItemId, ClientHandles, ServerHandles, Errors)

1、 配置OPC服务器

对于服务器的配置与同步通讯的配置一样,这里不需再讲解,若有不清楚的,可以参阅之前发布的<运用VC#编程通过OPC方式实现PC机与西门子PLC通讯>

2、 OPC编程

变量组、项的命名规则与同步通讯的一样,这里不再描叙,下面主要就开发一个异步通讯类 AsynServer来讲解如何编程。

<1>、引用

在VC#开发环境中添加对OpcRcw.Da库以及OpcRcw.Comn库的引用,该库属于.NET库,不属于COM库,西门子虽然编写了类库,以提供对.NET平台的支持,但这些类库仍然难于编程,里面包含了大量的在托管和非托管区传输数据,因此我们需要在它的基础上再开发一个类库,以简化以后的编程,首先在类的开头使用命名空间:

using OpcRcw.Comn

using OpcRcw.Da

using System.Runtime.InteropServices

using System.Collections

<2>、编程

异步编程的原理就是在OPC服务器那边检测当前活动的变量组,一但检测到某一个变量,譬如变量Q0.0从1变成0,就会执行一个回调函数,以实现针对变量发生变化时需要实现的动作,在这里可以采用委托来实现该功能。

1、 在命名空间的内部、类 AsynServer声明之前添加委托的申明:

// 定义用于返回发生变化的项的值和其对应的客户句柄

public delegate void DataChange(object[] values,int[] itemsID)

2、 该类继承于西门子提供的库接口IOPCDataCallback

public class AsynServer:IOPCDataCallback

在类的开头部分声明变量:

struct groupStru

{

public int groupID

public object groupObj

}

internal const int LOCALE_ID = 0x407//本地语言

private Guid iidRequiredInterface

private string serverType=""

private int hClientGroup = 0//客户组号

private int nSvrGroupID// server group handle for the added group

private Hashtable hashGroup//用于把组收集到一起

private int hClientItem=0//Item号

3、编写构造函数,接收委托参数已确定当数据发生变化时需要执行的方法入口点:

//创建服务器

//svrType 服务器类型的枚举

//dataChange 提供用于在数据发生变化时需要执行的函数入口

public AsynServer(ServerType svrType,DataChange dataChange)

{

switch(svrType)

{

case ServerType.OPC_SimaticHMI_PTPRO:

serverType="OPC.SimaticHMI.PTPro"break

case ServerType.OPC_SimaticNET:

serverType="OPC.SimaticNET"break

case ServerType.OPC_SimaticNET_DP:

serverType="OPC.SimaticNET.DP"break

case ServerType.OPC_SimaticNET_PD:

serverType="OPC.SimaticNET.PD"break

case ServerType.OPCServer_WinCC:

serverType="OPCServer.WinCC"break

}

hashGroup=new Hashtable(11)

dtChange=dataChange

}

4、创建服务器

// 创建一个OPC Server接口

//error 返回错误信息

//若为true,创建成功,否则创建失败

public bool Open(out string error)

{

error=""bool success=true

Type svrComponenttyp

//获取 OPC Server COM 接口

iidRequiredInterface = typeof(IOPCItemMgt).GUID

svrComponenttyp = System.Type.GetTypeFromProgID(serverType)

try

{

//创建接口

pIOPCServer =(IOPCServer)System.Activator.CreateInstance(svrComponenttyp)

error=""

}

catch (System.Exception err) //捕捉失败信息

{

error="错误信息:"+err.Messagesuccess=false

}

return success

}

5、 编写添加Group的函数

///

/// 添加组

///

///

///

///

///

/// 若为true,添加成功,否则添加失败

public bool AddGroup(string groupName,int bActive,int updateRate,out string error)

{

error=""bool success=true

int dwLCID = 0x407//本地语言为英语

int pRevUpdateRate

float deadband = 0

// 处理非托管COM内存

GCHandle hDeadband

IntPtr pTimeBias = IntPtr.Zero

hDeadband = GCHandle.Alloc(deadband,GCHandleType.Pinned)

try

{

pIOPCServer.AddGroup(groupName, //组名

bActive, //创建时,组是否被激活

updateRate, //组的刷新频率,以ms为单位

hClientGroup, //客户号

pTimeBias, //这里不使用

(IntPtr)hDeadband,

dwLCID, //本地语言

out nSvrGroupID, //移去组时,用到的组ID号

out pRevUpdateRate, //返回组中的变量改变时的最短通知时间间隔

ref iidRequiredInterface,

out pobjGroup1)//指向要求的接口

hClientGroup=hClientGroup+1

groupStru grp=new groupStru()

grp.groupID=nSvrGroupIDgrp.groupObj=pobjGroup1

this.hashGroup.Add(groupName,grp)//储存组信息

// 对异步 *** 作设置回调,初始化接口

pIConnectionPointContainer = (IConnectionPointContainer)pobjGroup1

Guid iid = typeof(IOPCDataCallback).GUID

pIConnectionPointContainer.FindConnectionPoint(ref iid,out pIConnectionPoint)

pIConnectionPoint.Advise(this,out dwCookie)

}

catch (System.Exception err) //捕捉失败信息

{

error="错误信息:"+err.Messagesuccess=false

}

finally

{

if (hDeadband.IsAllocated) hDeadband.Free()

}

return success

}

6、 编写激活、或者取消激活组的函数

在同步编程中对于组的激活或者取消激活没有实质的意义,但在异步通讯编程中却异常重要,这是因为OPC服务器只对当前处于活动状态的组中的变量进行监控,同时这也是很有必要的,因为我们可以把不同界面中的变量编程不同的组,即同一界面中的变量规成一个组,而在某一时刻提供给用户的只有一个界面,让该界面中用到的组处于活动状态,这样执行委托调用时只会执行于该界面中有关的变量检测,而如果让所有的组处于活动状态,则当前没有显示给用户的界面用到的变量若发生变化也会触发对委托函数的调用,这根本是没有必要的,同时会大大降低程序的性能,请严格控制组的激活。

///

/// 激活或者取消激活组

///

///

///

///

/// 若为true,添加成功,否则添加失败

public bool AciveGroup(string groupName,bool toActive,out string error)

{

error=""bool success=true

//通过名称获取组

object grp=((groupStru)hashGroup[groupName]).groupObj

IOPCGroupStateMgt groupStateMgt=(IOPCGroupStateMgt)grp

//初始化传递参数

IntPtr pRequestedUpdateRate = IntPtr.Zero//由客户指定的Item更新间隔时间

int nRevUpdateRate = 0//由服务器返回的能够更新的最短时间间隔

IntPtr hClientGroup = IntPtr.Zero//客户组

IntPtr pTimeBias = IntPtr.Zero

IntPtr pDeadband = IntPtr.Zero

IntPtr pLCID = IntPtr.Zero

// 激活或者取消激活组

int nActive = 0

GCHandle hActive = GCHandle.Alloc(nActive,GCHandleType.Pinned)

if(toActive)

hActive.Target = 1

else

hActive.Target = 0

try

{

groupStateMgt.SetState(pRequestedUpdateRate,out nRevUpdateRate,hActive.AddrOfPinnedObject(),pTimeBias,pDeadband,pLCID,hClientGroup)

}

catch(System.Exception err)

{

error="错误信息:"+err.Messagesuccess=false

}

finally

{

hActive.Free()

}

return success

}

7、 向指定的组中添加变量的函数

///

/// 向指定的组添加一系列项

///

///

///

///

/// 无错误,返回true,否则返回false

public bool AddItems(string groupName,string[] itemsName,int[] itemsID)

{

bool success=true

OPCITEMDEF[] ItemDefArray=new OPCITEMDEF[itemsName.Length]

for(int i=0i {

hClientItem=hClientItem+1//客户项自动加1

ItemDefArray[i].szAccessPath = ""// 可选的通道路径,对于Simatiic Net不需要。

ItemDefArray[i].szItemID = itemsName[i]// ItemID, see above

ItemDefArray[i].bActive = 1// item is active

ItemDefArray[i].hClient = hClientItem// client handle ,在OnDataChange中会用到

ItemDefArray[i].dwBlobSize = 0// blob size

ItemDefArray[i].pBlob = IntPtr.Zero// pointer to blob

ItemDefArray[i].vtRequestedDataType = 4//DWord数据类型

}

//初始化输出参数

IntPtr pResults = IntPtr.Zero

IntPtr pErrors = IntPtr.Zero

try

{

// 添加项到组

object grp=((groupStru)hashGroup[groupName]).groupObj

((IOPCItemMgt)grp).AddItems(itemsName.Length,ItemDefArray,out pResults,out pErrors)

int[] errors = new int[itemsName.Length]

IntPtr pos = pResults

Marshal.Copy(pErrors, errors, 0,itemsName.Length)

for(int i=0i<itemsname.lengthi++) 循环检查错误 {

if (errors[i] == 0)

{

OPCITEMRESULT result = (OPCITEMRESULT)Marshal.PtrToStructure(pos, typeof(OPCITEMRESULT))

itemsID[i] = result.hServer

pos = new IntPtr(pos.ToInt32() + Marshal.SizeOf(typeof(OPCITEMRESULT)))

}

else

{

String pstrError

pIOPCServer.GetErrorString(errors[0],0x407,out pstrError)

success=false

break

}

}

SetItenClient(groupName,itemsID,itemsID)//要求始终只有一个组被激活,才不会引起冲突。

}

catch (System.Exception err) // catch for error in adding items.

{

success=false

//error="错误信息:"+error+err.Message

}

finally

{

// 释放非托管内存

if(pResults != IntPtr.Zero)

{

Marshal.FreeCoTaskMem(pResults)

pResults = IntPtr.Zero

}

if(pErrors != IntPtr.Zero)

{

Marshal.FreeCoTaskMem(pErrors)

pErrors = IntPtr.Zero

}

}

return success

}

说明:使用该函数时,在类的开头,应该先声明整数数据,以用于保存由本函数返回的服务器对每一项分配的Item ID号:

8、 下面编写的是一个最重要的重载函数,当检测到当前活动组中的某个变量发生变化时,就会调用委托。

//数据变化时处理的问题

public virtual void OnDataChange ( Int32 dwTransid ,

Int32 hGroup ,

Int32 hrMasterquality ,

Int32 hrMastererror ,

Int32 dwCount ,

int[] phClientItems ,

object[] pvValues ,

short[] pwQualities ,

OpcRcw.Da.FILETIME[] pftTimeStamps ,

int[] pErrors )

{

dtChange(pvValues,phClientItems)

}

该函数的代码只有一句,即调用委托函数。

以上编写的是需要实现监控的最重要的方法,当然不完善,还有许多方法和重载函数可以编写,这里就不详细介绍。

9、 编写基本的测试程序,用于检测上面编写的异步类AsynServer

<1>、 重新创建一个工程,添加对上面编写的异步类的引用,并在类的开头部分添加变量声明:

//声明委托

private S7Connection.DataChange dt

//声明服务器

S7Connection.AsynServer server

<2>、初始化服务器数据

dt=new S7Connection.DataChange(DataChange)

server =new AsynServer(S7Connection.ServerType.OPC_SimaticNET,dt)

string err

server.Open(out err)

server.AddGroup("maiker",1,300,out err)

server.AddItems("maiker",m1,nt1)

server.AddGroup("maiker1",1,300,out err)

server.AddItems("maiker1",m2,nt2)

nt[0]=nt1[0]nt[1]=nt1[1]

<3>、添加两个单选按钮,用于选择某个组,并编写相应的程序

string err,err1

if(server==null) return

if(radioButton1.Checked)

{ nt[0]=nt1[0]nt[1]=nt1[1]

server.AciveGroup("maiker",true,out err)

server.AciveGroup("maiker1",false,out err1)

}

else

{

nt[0]=nt2[0]nt[1]=nt2[1]

server.AciveGroup("maiker1",true,out err)

server.AciveGroup("maiker",false,out err1)

}

<4>、添加文本框、按钮等,并编写委托执行函数:

private void DataChange(object[] obj,int[] itemsID)

{

for(int j=0j {

if(itemsID[j]==nt[0])

this.textBox1.Text=obj[j].ToString()

if(itemsID[j]==nt[1])

this.textBox4.Text=obj[j].ToString()

}

}

其中参数obj用于返回当前发生变化的变量的结果值,而itemsID返回当前发生变化的变量的ID号,其与添加变量时服务器返回的ID号对应。以上就是一个基本的测试函数,其相对同步编程来说,应该还简单一些。

3、 同步编程与异步编程的使用场合

一般来讲,同步编程需要使用定时器来循环检测变量,而异步编程则不需要,当服务器检测到数据发生变化时,可以直接调用传入的函数,从这方面来讲,使用异步编程更简单一些,但同步编程使用外部的定时器控制,编程则会更加灵活,一般只监控变量时可以使用异步编程,而当需要写入数据时可以使用同步编程,但这也不是绝对的,我曾编写了一个标准监控程序,没有使用异步编程。

4、 关于开发监控界面的说明

毫无疑问,我们应该开发一系列控件,用于简化界面的设计,否则工作量会异常大。设计一个标准模块,用于第一次运行监控软件时添加变量,并可以设定当前已经组态的界面中的各控件元素与之关联,这样在以后再运行该软件时,不需要再设定,就可以直接连接变量,并进行相应的变化。否则若在编程时编写代码进行关联,其工作量将会异常大。

其实该类我早已经开发了,但一直没有时间写成文章,本来想开发一系列标准控件和标准模块,但由于换到上海工作,可能不会再有时间搞这方面的研究了。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/bake/11603228.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-17
下一篇 2023-05-17

发表评论

登录后才能评论

评论列表(0条)

保存