OPC客戶端開發工具WTopcclient補充說明

Skr·Plato發表於2020-10-14

前言

   經過多個月的開發、現場測試和反饋修改,自己開發的OPC客戶端終於真正能在現場使用了。由於工業現場的伺服器很古老,只支援OPCDA架構,我們也只能按照DA的標準設計客戶端。由於有著WTopcclient這個相對好用的客戶端開發工具,在開發方面花費的精力還好,可是卻苦了現場的同學,在DCOM配置上花費了不少功夫╮(╯-╰)╭,在這裡向他們表示最高的敬意。
   言歸正傳,WTopcclient在使用中,我幾乎找遍了整個百度,真正有用的除了人家在.h裡函式宣告處加的註釋,就只剩下一個英文說明書了,除此之外幾乎沒有任何有價值的資料,為此開發OPC客戶端中也遇到不少坑,下面把自己的一些經驗總結一下,方便以後的開發(當然,如果以後OPCUA普及的話,就再也不用這麼費勁的整一個上個世紀的玩意了)。下面對OPCclient的標頭檔案和官方英文說明書中的內容進行潤色翻譯,並結合自己的備註額外補充一些說明,防止以後使用再踩這些坑。
20201014記錄:持續施工中,目前非完整版本,部分翻譯仍然為機翻沒有經過潤色,部分備註未新增。

一、手冊前言

備註: 全文中如有“應用程式”字樣,全部指的是“由開發者依據這個DLL開發的OPC客戶端應用程式”

譯文:
WTclient DLL 使用手冊
   WinTECH軟體快速客戶端開發DLL(WTclient)提供了一套很簡單就可以使用的API,用於將使用者開發的OPC客戶端應用程式與來自任何OPC伺服器的資料建立連線。COM和OPC的所有細節由DLL自己處理,所以開發應用程式時很簡單地就可以從伺服器獲取資料點,而不必關心底層介面的實際實現。在OPC通訊中所有必需的OPC介面,本DLL都支援OPC1.0和OPC 2.0資料訪問標準以及瀏覽介面,並支援連線到簡單的報警和事件伺服器。 該使用手冊應配合OPC資料訪問和OPC警報和事件規範檔案(規範檔案是OPC基金會官方定義的有關OPC的標準,可從基金會官網http://www.opcfoundation.org中獲得)。

製作自己的OPC客戶端程式
使用 WTclient.DLL 關聯WTclient.lib 到專案中
   WTclient.lib包含了DLL中 API函式的匯出定義。將此檔案包含在自己開發的OPC客戶端應用程式的專案檔案中,並將WTclientAPI.h包含在將要呼叫DLL的模組中。
備註:在這個標頭檔案中有lib中函式的宣告,可以看到函式呼叫和返回的引數,以及對函式的註釋
初始化DCOM
   從WTClient.DLL版本2.0開始,現在由客戶端應用程式負責正確初始化DCOM。在版本2.0之前,WTClient.dll在DLLMain函式中執行DCOM和安全初始化,並在解除安裝dll時呼叫CoUninitialize()。由於各種Windows dll的載入順序不同,這樣會出現一些問題,所以現在這些初始化呼叫被移到一個新的匯出函式WTclientCoInit( )中,客戶端應用程式可以在主執行緒啟動期間呼叫該函式。WTclientCoInit()將DCOM初始化為多執行緒,並使用預設安全性。您所開發的OPC客戶端應用程式也可以選擇自己進行DCOM初始化,而不使用函式WTclientCoInit( )。無論是否呼叫API函式進行DCOM初始化,應用程式在終止時都必須呼叫CoUninitialize()。
備註: 可以看到,如果想簡單通過MFC或Qt開發一個OPC客戶端,在程式開始時必須WTclientCoInit()初始化才可以正常使用DCOM,程式結束時必須呼叫CoUninitialize()。需要注意一下,在WTclientAPI.h中還宣告瞭一個WTclientCoUninit()函式,且註釋裡說這個函式用來取消DCOM初始化,但說明書裡並沒有提到這個函式- -。CoUninitialize()這個是windows提供的標頭檔案中取消初始化的函式,與COM和OLE有關係,OPC是過程控制中使用的OLE嘛,所以OPC基礎還是COM和OLE沒毛病。

二、手冊正文部分函式翻譯和說明

譯文:
獲取 OPC 伺服器列表
   WTclient.dll 提供了兩個函式來使客戶端應用程式可以獲取可用的OPC Servers列表.

int NumberOfOPCServers (BOOL UseOPCENUM, LPCSTR MachineName);

   OPCENUM是OPC基金會提供的一個元件,它允許客戶端應用程式獲取本地或遠端計算機上可用伺服器的列表(OPCENUM顧名思義,會列舉指定計算機擁有OPC伺服器的數量)。很遺憾在編寫元件時,OPCENUM的操作沒有很好的跨平臺一致性。所以WTclient.dll允許應用程式使用OPCENUM元件獲取伺服器列表,或通過掃描Windows登錄檔的本地副本繞過OPCENUM並獲取列表。如果可以使用OPCENUM的話,你可以傳遞一個遠端計算機的名稱(其實是它的IP地址)來獲取遠端OPC伺服器列表。
標頭檔案註釋翻譯:
   NumberOfOPCServers(…)
   返回可用OPC伺服器的數量
   如果UseOPCENUM為FALSE
   伺服器列表從本地Windows登錄檔獲取
   忽略MachineName
   如果UseOPCENUM為真
   伺服器列表是從OPCENUM元件獲取的
   由OPC基金會和MachineName提供
   從遠端計算機獲取伺服器列表
備註: 實際上之所以OPC伺服器端初始化有個步驟叫“註冊伺服器”,就是因為它是在計算機登錄檔裡註冊了OPC伺服器的資訊
功能:獲取伺服器數量
引數:BOOL UseOPCENUM 是否使用OPCENUM來列舉伺服器
LPCSTR MachineName 計算機名,本地伺服器為空,遠端伺服器為IP地址
返回值:int 返回查詢到的伺服器數量

譯文:
   從WTclient.dll返回伺服器數後,應用程式可以通過呼叫下面函式獲取伺服器名:

BOOL GetServerName (int index, LPSTR Buffer, int BufSize); 

   由客戶端程式開發者提供一個提前定義好的字串,伺服器名字會放在該字串中,也可以直接使用該函式讀取指定第幾個伺服器名字。BufSize標識使用者提供的字元緩衝區的長度,可以防止dll載入過長的名字,也就是限定了讀取伺服器名字串的長度。如果Buffer中返回了一個有效的伺服器名稱,GetServerName函式返回TRUE,否則返回FALSE。
備註: 這個函式用來獲取伺服器名字。一個由使用者提供的字元緩衝區Buffer負責存放傳遞過來的index標識的伺服器列表中某個伺服器名字。也就是說,比如呼叫NumberOfOPCServers()得到本地有兩個伺服器,建立一個for迴圈就可以逐個讀取伺服器名字。
譯文:
建立與 OPC 伺服器的連線

HANDLE ConnectOPC(LPCSTR MachineName, LPCSTR ServerName, BOOL EnableDLLBuffering); 
HANDLE ConnectOPC1(LPCSTR MachineName, LPCSTR ServerName, BOOL EnableDLLBuffering); 
HANDLE ConnectOPC_AE(LPCSTR MachineName, LPCSTR ServerName); 

   如果可以通過MachineName(OPC伺服器所在計算機名,本地則為空字串,遠端則為IP地址)和ServerName(OPC伺服器名字,在之前的步驟中得到)建立到指定OPC伺服器的連線,那麼這個函式返回一個有效的控制程式碼HANDLE ,這個控制程式碼是伺服器的控制程式碼,對指定伺服器的內容進行操作,都需要提供這個控制程式碼,一定要好好儲存!客戶端程式可以同時連線多個伺服器,每個伺服器連線成功後都會返回這個伺服器的控制程式碼。WTclient將嘗試使用OPC 2.0連線點介面進行連線。如果不可用,它將恢復OPC 1.0dataobject介面。
EnableDLLBuffering如果為TRUE,DLL會自己維護客戶端應用程式建立的所有項的列表並接收伺服器更新,當伺服器資料改變,dll維護的這個列表會更新,應用程式要用ReadOPCItem()來讀取項的資料(本身是為了讀取OPC伺服器項對應標籤的資料,實際是讀取了DLL中的列表中的對應資料)。
   如果EnableDLLBuffering為FALSE,DLL不會保留項資料的本地副本和對的後續呼叫(DLL不會自己維護一個項列表),ReadOPCItem()將失敗。使用者應用程式必須定義正確的回撥函式,(EnableOPCNotification()),獲取項更新
對於那些使用不支援回撥函式的工具設計的應用程式(如Visual Basic 5.0),可以將WTclient配置為維護由應用程式建立的所有OPC項(標記)的列表(即上面的引數設為TRUE)。當來自伺服器的資料更改時,dll列表中相應的標記值將被更新。控制應用程式可以隨時讀取標記的值。
   ConnectOPC1 會一直嘗試一個 OPC 1.0 標準對OPC伺服器的連線。.
   ConnectOPC_AE 將會建立一個到 OPC 報警 & 事件伺服器的連線 。

void DisconnectOPC(HANDLE hConnect);
void DisconnectOPC_AE(HANDLE hConnect);

   當應用程式終止時,它負責與連線的伺服器斷開連線。The DisconnectOPC(), (或者 Disconnect_OPCAE()), 函式會將hConnect(這個控制程式碼就是在連線到指定伺服器的時候返回的控制程式碼,斷開時也要用到,後面很多操作都需要,十分重要)定義的連線的完全關閉。
獲取 OPC Server 標籤名列表

int NumberOfOPCItems(HANDLE hConnect);
BOOL GetOPCItemName(HANDLE hConnect, int index, char *pBuf, int BufSize);

   這兩個函式使用就像之前獲取伺服器名時一樣,先獲取伺服器擁有的標籤item數目,通過NumberOfOPCItems返回值,然後逐個讀取標籤的名字. NumberOfOPCItems() 返回由控制程式碼hConnect指向的 OPC Server所擁有的標籤總數. 建立一個for迴圈, GetOPCItemName將會遍歷所有可用的標籤名,來讓使用者選擇要建立連線項的標籤,或者生成一個標籤名列表. NumberOfOPCItems 將從伺服器瀏覽專案名稱的完整列表,並以“FLAT”格式顯示該列表。

BOOL GetNameSpace(HANDLE hConnect, WORD *pNameSpace);
BOOL BrowseTo(HANDLE hConnect, LPCSTR NodeName);
char SetWTclientQualifier(char qualifier);
int BrowseItems(HANDLE hConnect, WORD Filter);

   為了更好地訪問具有分層名稱空間的伺服器,WTClient dll提供了一些函式,允許應用程式直接從伺服器瀏覽項名稱。GetNameSpace允許應用程式確定伺服器的名稱空間。BrowseTo將當前瀏覽位置移動到指定的名稱,BrowseItems使用伺服器中的節點名填充內部名稱列表。BrowseTo可用於讀取OPC_BRANCH下面的OPC_LEAF(葉子的名字),首先要用GetOPCItemName獲取OPC_BRANCH樹枝名,然後使用BrowseTo移動瀏覽位置到該樹枝名位置,然後繼續使用GetOPCItemName就可以讀取當前樹枝下的葉子名。WtClient.DLL為NumberOfOPCItems()函式和BrowseItems()函式維護一個單一的名稱列表,因此應用程式在瀏覽伺服器名稱空間的時候必須很小心,避免覆蓋前一次呼叫的值。在各個等級的樹的相互作用過程中,呼叫BrowseItems之後,所有的專案名稱必須立刻拷貝到本地儲存中。
使用OPC_FLAT從根位置呼叫BrowseItems的行為與NumberOfOPCItems()相同。

   SetWTclientQualifier 允許應用程式更改用於分析分層名稱空間節點的定界字元。客戶端dll所需的預設分隔符是“.”,但是,某些伺服器使用“.”字元作為節點名的一部分。

BOOL SetBrowseFilters(HANDLE hConnect, LPCSTR UserString, VARTYPE DataType, DWORD AccessType);

  應用程式可以設定瀏覽過濾器,以影響從伺服器返回的OPC專案數。
應用程式可以選擇僅檢索具有特定資料型別或訪問許可權的那些項。
UserString:只能瀏覽到包含指定字元的標籤
DataType:只能瀏覽到指定資料型別的標籤
AccessType:只能瀏覽到包含指定讀寫標誌的標籤(可讀 可寫 既可讀又可寫)
建立 OPC 組 and 項

HANDLE  AddOPCGroup(HANDLE hConnect, LPCSTR Name, DWORD *pRate, float *pDeadBand);
void  RemoveOPCGroup(HANDLE hConnect, HANDLE hGroup);

  WTclient.dll匯出兩個允許您建立和刪除OPC組的函式。函式的作用是:建立一個組後返回一個組控制程式碼(對組內內容的操作都要通過組控制程式碼),必要時必須提供給其他Wtclient函式。作為引數傳遞給AddOPCGroup的值由控制應用程式確定,並且特定於每個組。LPCSTR Name名稱是任意的,不能使用伺服器名。DWORD *pRate是指向所需重新整理率(以毫秒為單位)的指標,伺服器使用該重新整理率向客戶端提供更新,就是說每隔重新整理率的時間,伺服器向這個組內內容更新資料。

BOOL  ChangeOPCGroupState(HANDLE hConnect, HANDLE hGroup, BOOL Active));

  使用此功能,可以啟用(Active = TRUE)或停用(Active = FALSE)定義的OPC組。

HANDLE  AddOPCItem(HANDLE hConnect, HANDLE hGroup, LPCSTR ItemName);
HANDLE AddOPCItemWithPath(HANDLE hConnect, HANDLE hGroup, 
LPCSTR PathName, LPCSTR ItemName);
void  RemoveOPCItem(HANDLE hConnect, HANDLE hGroup, HANDLE hItem);

  要將伺服器標籤新增到定義的OPC組,只需使用組的控制程式碼和所需新增項的伺服器標籤名和/或路徑名呼叫AddOPCItem函式。這個函式將返回唯一的項控制程式碼(如果請求的項不存在,比如伺服器沒有這個名字的標籤,則返回無效的控制程式碼值)。由於項都屬於某個組,關閉組時,需要呼叫RemoveOPCItem清除對每個項所分配的記憶體。

BOOL  ChangeOPCItemState(HANDLE hConnect, HANDLE hGroup, HANDLE hItem, 
BOOL Active);

  使用此功能,可以啟用(Active = TRUE)或停用(Active = FALSE)定義的OPC項。

BOOL  GetOPCItemNameFromHandle(HANDLE hConnect,  HANDLE hGroup, HANDLE hItem,char *pBuf, int BufSize); 

  此函式允許客戶端應用程式通過傳遞從AddOPCItem返回的控制程式碼來檢索OPC項名稱(通過得到的項控制程式碼來反向獲取伺服器標籤的名字)獲取到的標籤名會儲存在char *pBuf,int BufSize是客戶端提供的標籤名最大長度。

BOOL  GetOPCItemType(HANDLE hConnect, HANDLE hGroup, LPCSTR ItemName,VARTYPE *pType, DWORD *pAccessRights);

  這個函式返回了指定標籤的VARTYPE *pType變數型別以及DWORD *pAccessRights讀寫許可權.

int  NumberOfItemProperties(HANDLE hConnect, LPCSTR ItemName);

  這個函式返回與指定標籤相關聯的 OPC 項屬性數目。

BOOL  GetItemPropertyDescription(HANDLE hConnect, int PropertyIndex, DWORD *pPropertyID,VARTYPE *pVT, BYTE *pDescr, int BufSize);

  可以通過遍歷NumberOfOPCItemProperties建立的列表來獲得每個項屬性的BYTE *pDescr,描述.

BOOL  ReadPropertyValue(HANDLE hConnect, LPCSTR Itemname, DWORD PropertyID, VARIANT *pValue);

  ReadItemProperty返回項屬性的當前值
從OPC伺服器獲取更新的標籤值(讀標籤值)

BOOL  EnableOPCNotification(HANDLE hConnect, NOTIFYPROC lpCallback);

  如果應用程式支援回撥,當連線的伺服器發出了標籤資料值改變的通知時,WTclient將發出回撥。回撥的原型定義如下:

void CALLBACK EXPORT OPCUpdateCallback(HANDLE hGroup, HANDLE hItem, VARIANT *pVar, FILETIME timestamp, DWORD quality)

  一個標籤的當前值、時間戳和OPC質量標誌將通過回撥提供給應用程式。標籤由組控制程式碼和項控制程式碼聯絡。

BOOL  ReadOPCItem(HANDLE hConnect, HANDLE hGroup, HANDLE hItem, VARIANT *pVar, FILETIME *pTimeStamp, DWORD *pQuality);

  如果已將WTclient配置為DLLBuffering=TRUE,由DLL維護項列表,則應用程式可通過ReadOPCItem函式讀取標籤的當前值。如果請求的項不可用(或DLLBuffering=FALSE),函式將返回FALSE

HRESULT  ReadOPCItemFromDevice(HANDLE hConnect, HANDLE hGroup, HANDLE hItem, VARIANT *pVar, FILETIME *pTimeStamp, DWORD *pQuality);
HRESULT  ReadOPCItemFromCache(HANDLE hConnect, HANDLE hGroup, HANDLE hItem, VARIANT *pVar, FILETIME *pTimeStamp, DWORD *pQuality);

  這些函式從連線的伺服器對指定項執行同步的讀取。讀取資料可以從伺服器的快取請求,也可以直接從裝置請求。
向伺服器寫標籤值

DWORD  WriteOPCItem(HANDLE hConnect, HANDLE hGroup, HANDLE hItem, VARIANT *pVar, BOOL DoAsync);

  向連線的伺服器寫入新資料是通過WriteOPCItem函式完成的。必須指定組和項的控制程式碼以及要寫入的資料。DoAsync引數為TRUE時dll對伺服器執行非同步寫入。為FALSE則同步寫入,同步寫入時返回值表示從伺服器返回的hResult,非零值表示錯誤。對於非同步寫入,返回值是定義非同步回撥控制程式碼的事務ID,零值表示錯誤。

BOOL  EnableAsyncWriteNotification(HANDLE hConnect, NOTIFYPROC lpCallback);

  對於非同步寫入請求,可以將dll配置為在寫入完成時嚮應用程式發出回撥。回撥的原型定義如下:

void CALLBACK EXPORT AsyncWriteCallback (HANDLE hGroup, DWORD TransactionID,   DWORD hResult)

  The TransactionID 定義特定於此回撥的寫入請求,也就是說,之前的WriteOPCItem函式在非同步寫時,如果返回的TransactionID與這裡的回撥中的TransactionID相等,那麼進入這個回撥。 hResult定義寫入狀態.,這個變數為S_OK,代表寫入成功,否則寫入失敗。
一些雜項函式和回撥函式

BOOL  GetSvrStatus(HANDLE hConnect, OPCSERVERSTATUS *pSvrStatus,int VendorInforBufSize);

  在提供的緩衝區OPCSERVERSTATUS *pSvrStatus中返回標識的伺服器的當前狀態

BOOL  SetClientName(HANDLE hConnect, LPCSTR Name);

  此函式允許客戶端定義一個名稱來指定與伺服器的連線

BOOL  EnableErrorNotification(ERRORPROCAPI  lpCallback);

  WTClient dll可以通知客戶端應用程式在處理對OPC伺服器的資料介面呼叫期間發生的錯誤。如果在處理使用者請求期間出現意外情況,則dll可能會生成錯誤訊息。dll的預設操作是會彈出一個對話方塊提示使用者錯誤資訊。在大多數情況下,應用程式本身更適合處理錯誤訊息,而不是由dll生成對話方塊。如果啟用了EnableErrorNotification,則控制元件將通過以下回撥傳遞給應用程式:

void CALLBACK EXPORT ErrorMsgCallback(DWORD hResult, char *pMsg)

  pMsg 中包含錯誤的文字描述以及由此產生的 hResult.

BOOL  EnableClientEventMsgs(EVENTMSGPROC  lpCallback);

  WTClient dll還可以在與伺服器連線的操作過程中通知客戶端應用程式各種事件。這些事件基本上用於除錯,並表示與處理單個介面相關的正常活動(例如進入和退出特定函式)。通常,客戶端應用程式不需要知道這些事件,但是,如果它希望提供連線如何工作的低階描述,則可以通過啟用事件msg在這些除錯語句發生時接收它們。

void CALLBACK EXPORT EventMsgCallback(char *pMsg)

  pMsg包含事件的文字描述。

BOOL  EnableShutdownNotification(HANDLE hConnect, SHUTDOWNPROCAPI  lpCallback);

  如果連線的OPC伺服器希望關閉,它可以請求任何或所有客戶端斷開連線。客戶端應用程式應在WTClient dll中啟用關閉通知EnableShutdownNotification來處理此請求並終止連線。

void CALLBACK EXPORT ShutdownCallback(HANDLE hConnect)

  hConnect引數標識請求斷開連線的伺服器連線。

相關文章