你的语句是添加条目的,用法是:组名.集合名.additem(条目名称)。
连wincc的服务器也是一样的写法,确认好名称就好了。
OPC的文档网上很多,我在这里要介绍的主题是使用C++通过自动化接口来访问OPC Server,写这篇文章的目的是我在网上没有搜索到这方面的文档,如果我有这方面的需要,我想在网上一定也有其他朋友有这个需要,希望能对这些朋友有一些帮助。使用C++来访问OPC Server, 相对于使用自定义接口来说,自动化接口要简单很多,因为这和Visual Basic使用的是同一个接口,使用过Visual Basic来访问OPC Server的朋友一定能有这个体会。首先是准备好开发环境,一般测试是在模拟环境中进行,这样比较保险,可以使用一些免费的模拟OPC Server。我这里准备的是Matrikon的模拟服务器,模拟器安装以后。编程环境是VC++ 6.0,使用200X和2010也都大同小异。
为了演示简单,新建一个Win32控制台工程agOPC,新建agOPC.cpp源文件并加到工程里。
// --------------------------------- agOPC.cpp -----------------------------------------------
//在agOPC.cpp开头添加如下一行
#import "C:Program FilesMatrikonOPCCommonOPCAuto.dll" no_namespace
//这是通过OPCAuto.dll里所包含的类型库信息产生C++能访问的头文件,此时在工程的Debug文件夹下产生OPCAuto.tlh和OPCAuto.tli两个文件。
//添加需要的头文件
#pragma warning( disable : 4786 ) // 为了避免vector报出的C4786警告
#include<comdef.h> // 使用到了_bstr_t,_variant_t,_com_error都在这个文件里定义
#include<iostream>
#include<vector>
using namespace std
//声明全局变量
typedef struct OLEInit {
OLEInit() { CoInitialize( NULL )}
~OLEInit() { CoUninitialize()}
} OLEInit
OLEInit oleInit // 必须在最前面定义,因为在使用COM之前必须初始化COM库,否则程序会崩溃
// 由于是全局变量oleInit的构造函数在所有对象的构造函数调用之前调用,
// 析构函数在所有对象的析构函数调用之后调用
IOPCAutoServerPtropcSvr // 这些智能指针类型在OPCAuto.tlh中定义
IOPCGroupsPtropcGrps
IOPCGroupPtropcGrp
vector<OPCItemPtr> opcItms // 使用vector来保存三个测试Item。
//连接到OPC Server, 我所使用的参数是"Matrikon.OPC.Simulation.1"
void agOPCConn( const char *opcSvrName ) {
HRESULT hr
hr = opcSvr.CreateInstance( __uuidof( OPCServer ) )
if( FAILED( hr ) ) {
cerr<<"OPCServer CreateInstance failed, hr = " <<hr<<endl
exit(1)
}
opcSvr->Connect( opcSvrName )
}
//断开和OPC Server的连接
void agOPCDisc() {
opcGrps->RemoveAll()// 删除所有的组, 这个演示实例只有一个组
opcSvr->Disconnect()// 断开和OPC Server的连接
}
//创建一个组
void agOPCCreateGroup() {
// OPCGroups是特殊的属性,执行的时候会调用OPCAuto.tlh中的IOPCGroupsPtr GetOPCGroups()
opcGrps = opcSvr->OPCGroups
opcGrp = opcGrps->Add( _variant_t( "group1" ) ) // 组名随意取
}
//在组里添加三个不同类型的测试Item, 类型可以从Item的名字可以看出
void agOPCAddItems() {
OPCItemPtr opcItm
opcItm = opcGrp->OPCItems->AddItem( _bstr_t( "Bucket Brigade.Int4" ), 1 )
opcItms.push_back( opcItm )
opcItm = opcGrp->OPCItems->AddItem( _bstr_t( "Bucket Brigade.Int2" ) , 1)
opcItms.push_back( opcItm )
opcItm = opcGrp->OPCItems->AddItem( _bstr_t( "Bucket Brigade.String" ) , 1)
opcItms.push_back( opcItm )
}
//用来显示读取的Item的值
void agDumpVariant(VARIANT *v)
{
switch(v->vt)
{
case VT_I2:
printf(" value(VT_I2) = %d ", v->iVal )
break
case VT_I4:
printf(" value(VT_I4) = %ld ", v->lVal )
break
case VT_BSTR:
printf(" value(VT_BSTR) = %ls ", v->bstrVal )
break
default:
printf(" value(unknown type:%d) ", v->vt )
break
}
}
//同步读取三个Item的值,同步在很多情况下都是简单有效的选择方案,其实读取的异步方式在C++中可以建立一个工作线程来执行同步读的操作,等有新的Item值的时候再通过某种线程间通信的方式告诉主线程“数据改变”的事件
void agOPCReadItems() {
_variant_tquality
_variant_ttimestamp
SAFEARRAY*pServerHandles
SAFEARRAY*pValues
SAFEARRAY*pErrors
SAFEARRAYBOUNDrgsabound[ 1 ]
longdim[ 1 ]
longsvrHdl
vector<_variant_t> values
vector<long> errs
inti
_variant_tvalue
longerr
// VC数组索引从0开始,而在OPCAuto.dll需要中从1开始,所以是rgsabound[ 0 ].cElements = 4,而给pServerHandles赋值的时候应该给索引是1,2,3相应的赋值Server Handle
rgsabound[ 0 ].cElements = 4
rgsabound[ 0 ].lLbound = 0
pServerHandles = SafeArrayCreate( VT_I4, 1, rgsabound )//构建一个1维数组,类型是VT_I4
for( i = 0i <opcItms.size()i++ ) {
svrHdl = opcItms[i]->ServerHandle
dim[ 0 ] = i + 1
// 给数组的每个元素赋值,对应的索引值是1, 2, 3
SafeArrayPutElement( pServerHandles, dim, &svrHdl )
}
opcGrp->SyncRead( OPCDevice,
3, // 读取的Item数目
&pServerHandles,// 输入的服务器端句柄数组
&pValues, // 输出的Item值数组
&pErrors, // 输出的Item错误状态数组
&quality, // 读取的值的状态
&timestamp ) // 读取的事件戳
for( i = 1i <= opcItms.size()i++ ) {
dim[ 0 ] = i
SafeArrayGetElement( pValues, dim, &value )// 读取Item值在value中
SafeArrayGetElement( pErrors, dim, &err ) // 读取错误状态值在err中
values.push_back( value )
errs.push_back( err )
}
for( i = 0i <values.size()i++ ) {
agDumpVariant( &values[ i ] ) // 显示读取的Item值
cout<<", err = "<<errs[ i ]<<endl
}
SafeArrayDestroy( pServerHandles )
SafeArrayDestroy( pValues )
SafeArrayDestroy( pErrors )
}
// 写入3个Item的值,为了演示实例简单,参数传递3个对应的Item值
void agOPCWriteItems( vector<_variant_t>values) {
_variant_tquality
_variant_ttimestamp
SAFEARRAY*pServerHandles
SAFEARRAY*pValues
SAFEARRAY*pErrors
longdim[ 1 ]
longsvrHdl
inti
SAFEARRAYBOUND rgsabound[ 1 ]
rgsabound[ 0 ].cElements = values.size() + 1
rgsabound[ 0 ].lLbound = 0
pServerHandles = SafeArrayCreate( VT_I4, 1, rgsabound )
pValues = SafeArrayCreate(VT_VARIANT, 1, rgsabound)
for( i = 0i <values.size()i++ ) {
svrHdl = opcItms[i]->ServerHandle
dim[ 0 ] = i + 1
SafeArrayPutElement( pServerHandles, dim, &svrHdl )
SafeArrayPutElement( pValues, dim, &values[i] )
}
opcGrp->SyncWrite( 3,&pServerHandles, &pValues,&pErrors )
SafeArrayDestroy( pServerHandles )
SafeArrayDestroy( pValues )
SafeArrayDestroy( pErrors )
}
//main主程序
int main()
{
try
{
agOPCConn( "Matrikon.OPC.Simulation.1" )
agOPCCreateGroup()
agOPCAddItems()
// 第一次写和读
vector<_variant_t>values
values.push_back( ( long )156 )
values.push_back( ( short )11 )
values.push_back( "opc" )
agOPCWriteItems( values )
agOPCReadItems()
cout <<"---------------------------------------"<<endl
// 第二次写和读
vector<_variant_t>values1
values1.push_back( ( long )123456 )
values1.push_back( ( short )666 )
values1.push_back( "hello" )
agOPCWriteItems( values1 )
agOPCReadItems()
}
catch ( _com_error &e ) {
// 应该在上面的子函数里面捕捉异常,但为了演示简单,在主函数里面捕捉异常
_bstr_t bstrSource( e.Source( ) )
_bstr_t bstrDescription( e.Description( ) )
cout<<"Code = "<<e.Error()<<endl
cout<<"Code meaning = "<<e.ErrorMessage()<<endl
cout<<"Source = "<<( LPCTSTR ) bstrSource<<endl
cout<<"Description = "<<( LPCTSTR ) bstrDescription<<endl
}
return 0
}
第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服务器
欢迎分享,转载请注明来源:夏雨云
评论列表(0条)