使用C++访问OPC Server的简单方法
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 str uct OLEInit {
OLEInit() { CoInitialize( NULL ); }
~OLEInit() { CoUninitialize(); }
} OLEInit;
OLEInit oleInit; // 必須在最前面定義,因為在使用COM之前必須初始化COM庫,否則程序會崩潰
// 由于是全局變量oleInit的構造函數在所有對象的構造函數調用之前調用,
// 析構函數在所有對象的析構函數調用之后調用
IOPCAutoServerPtr opcSvr; // 這些智能指針類型在OPCAuto.tlh中定義
IOPCGroupsPtr opcGrps;
IOPCGroupPtr opcGrp;
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_t quality;
_variant_t timestamp;
SAFEARRAY *pServerHandles;
SAFEARRAY *pValues;
SAFEARRAY *pErrors;
SAFEARRAYBOUND rgsabound[ 1 ];
long dim[ 1 ];
long svrHdl;
vector<_variant_t> values;
vector<long> errs;
int i;
_variant_t value;
long err;
// 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 = 0; i < 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 = 1; i <= 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 = 0; i < 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_t quality;
_variant_t timestamp;
SAFEARRAY *pServerHandles;
SAFEARRAY *pValues;
SAFEARRAY *pErrors;
longdim[ 1 ];
long svrHdl;
int i;
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 = 0; i < 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;
}
總結
以上是生活随笔為你收集整理的使用C++访问OPC Server的简单方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从玩扑克到软件开发
- 下一篇: BacNet开发入门2