vc如何對opc數據的訪問
1. 如何在C++中實現OPC訪問
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的構造函數在所有對象的構造函數調用之前調用,
// 析構函數在所有對象的析構函數調用之後調用
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;
long dim[ 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;
}
2. vc開發的opc客戶端怎麼鏈接opc伺服器
很簡單,wincc裡面添加一個opc通道,就象添加一個s7協議(與300-400plc通訊)一樣