• 
    <ul id="auswy"><sup id="auswy"></sup></ul>
  • <ul id="auswy"></ul>
    ABB
    關(guān)注中國(guó)自動(dòng)化產(chǎn)業(yè)發(fā)展的先行者!
    CAIAC 2025
    2025工業(yè)安全大會(huì)
    OICT公益講堂
    當(dāng)前位置:首頁(yè) >> 案例 >> 案例首頁(yè)

    案例頻道

    Delphi環(huán)境下使用定制接口開(kāi)發(fā)OPC數(shù)據(jù)訪問(wèn)客戶程序
    • 企業(yè):控制網(wǎng)     領(lǐng)域:電源    
    • 點(diǎn)擊數(shù):6046     發(fā)布時(shí)間:2005-07-13 17:23:40
    • 分享到:
    簡(jiǎn)要的介紹了OPC以及使用定制接口開(kāi)發(fā)OPC數(shù)據(jù)訪問(wèn)客戶程序的背景知識(shí)。文中較為詳細(xì)的講述了如何Delphi環(huán)境下使用定制接口開(kāi)發(fā)OPC數(shù)據(jù)訪問(wèn)客戶程序。



    1  引言


    圖1  OPC應(yīng)用架構(gòu)



        OPC(用于過(guò)程控制的OLE)是一個(gè)工業(yè)標(biāo)準(zhǔn)。它由一些世界上占領(lǐng)先地位的自動(dòng)化系統(tǒng)和硬件、軟件公司與微軟公司緊密合作而建立的。這個(gè)標(biāo)準(zhǔn)定義了應(yīng)用Microsoft操作系統(tǒng)在基于PC 的客戶機(jī)之間交換自動(dòng)化實(shí)時(shí)數(shù)據(jù)的方法。管理這個(gè)標(biāo)準(zhǔn)的國(guó)際組織是OPC基金會(huì)。因?yàn)閼?yīng)用程序要和不同的設(shè)備,比如PLC、變頻器、現(xiàn)場(chǎng)總線的儀表等通訊,如果不同的設(shè)備廠家都遵守一個(gè)相同的程序接口標(biāo)準(zhǔn)的話,那么程序和不同設(shè)備的溝通將變得非常容易。OPC 就是這樣一種工業(yè)標(biāo)準(zhǔn),它是OLE for Process Control 的英文縮寫。OPC 是基于微軟的COM(Component Object Model,組件對(duì)象模型)和OLE(Object Linking and Embedding,對(duì)象鏈接與嵌入)技術(shù)之上的。和以前不同的是,現(xiàn)在設(shè)備廠家提供
    不同的OPC Server。OPC Server負(fù)責(zé)從設(shè)備中取數(shù)據(jù)和寫數(shù)據(jù)。人們所要做的就是利用統(tǒng)一的COM 規(guī)范編寫OPC Client。客戶程序和OPC Server 打交道。OPC Server是一座在客戶和硬件設(shè)備之間的橋梁,通過(guò)它,人們可以很容易的取得現(xiàn)場(chǎng)的溫度、壓力、流量、位置等信號(hào),以及控制現(xiàn)場(chǎng)的閥門開(kāi)度、電機(jī)轉(zhuǎn)速等。注意客戶程序和服務(wù)器程序可以在同一臺(tái)計(jì)算機(jī)上,也可以在不同的計(jì)算機(jī)上,區(qū)別是使用COM 還是使用DCOM(Distributed Component Object Model,分布式組件對(duì)象模型)。如圖1所示。

    2  OPC數(shù)據(jù)訪問(wèn)服務(wù)器接口方式

        OPC數(shù)據(jù)訪問(wèn)規(guī)范提供了兩套接口方案,即定制接口(Custom Interface)和自動(dòng)化接口(Automation Interface)。其中自動(dòng)化接口是對(duì)定制接口的進(jìn)一步封裝。定制接口效率高,采用它能夠發(fā)揮OPC服務(wù)器的最佳性能。用C++編寫訪問(wèn)OPC服務(wù)器的程序一般采用定制接口方案;對(duì)于VB、VBA、EXCEL等編程軟件或工具,不能直接訪問(wèn)通用接口,而要通過(guò)自動(dòng)化接口,因此使用VB或VBA等語(yǔ)言的客戶程序一般使用自動(dòng)化接口。


    圖2  OPC數(shù)據(jù)訪問(wèn)方式

    3  OPC數(shù)據(jù)訪問(wèn)服務(wù)器的數(shù)據(jù)訪問(wèn)方法

    (1)  同步數(shù)據(jù)訪問(wèn)處理
        OPC 服務(wù)器把按照OPC 客戶應(yīng)用程序的要求得到的數(shù)據(jù)訪問(wèn)結(jié)果,作為方法的參數(shù)返回給OPC 客戶程序。OPC 客戶應(yīng)用程序在結(jié)果被返回之前必須處于等待狀態(tài)。
    (2)  異步數(shù)據(jù)訪問(wèn)處理
        OPC 服務(wù)器接到 OPC 應(yīng)用程序的要求后,幾乎立即將方法返回。OPC 客戶應(yīng)用程序隨后可以進(jìn)行其他處理。當(dāng)OPC 完成數(shù)據(jù)訪問(wèn)時(shí),觸發(fā)OPC 客戶應(yīng)用程序的一部訪問(wèn)事件,將數(shù)據(jù)訪問(wèn)結(jié)果傳送給OPC 應(yīng)用程序。OPC 應(yīng)用程序在事件處理程序中接受OPC 服務(wù)器傳送來(lái)的數(shù)據(jù)。
    (3)  訂閱方式數(shù)據(jù)采集
        此種方式是OPC 客戶應(yīng)用程序不需要向OPC 服務(wù)器程序提出數(shù)據(jù)請(qǐng)求,而是自動(dòng)接收OPC 服務(wù)器送來(lái)的變化通知信號(hào)的訂閱方式數(shù)據(jù)采集。服務(wù)器按照一定的更新周期更新OPC 服務(wù)器的數(shù)據(jù)緩沖器的數(shù)值,如果發(fā)現(xiàn)數(shù)值有變化時(shí),就會(huì)以數(shù)據(jù)變化事件通知OPC 客戶應(yīng)用程序。OPC 服務(wù)器支持不敏感帶寬 (Dead Band),當(dāng)OPC標(biāo)簽的數(shù)據(jù)類型是模擬量時(shí),只有當(dāng)前值與上一次值的偏差的絕對(duì)值超過(guò)一定的限度時(shí),才更新緩沖器的數(shù)據(jù)并通知OPC 客戶應(yīng)用程序。這樣就可以忽略模擬量的微小變化,從而減輕OPC 服務(wù)器和OPC 客戶的負(fù)擔(dān)。
    (4)  刷新方式
        刷新是一種特殊形式的訂閱,它強(qiáng)制更新活動(dòng)組中的所有活動(dòng)項(xiàng)數(shù)據(jù),而不管這些數(shù)據(jù)與以前相比是否有了變化。

    4  使用Delphi進(jìn)行COM程序開(kāi)發(fā)的優(yōu)點(diǎn)

        Delphi是功能強(qiáng)大的應(yīng)用程序開(kāi)發(fā)工具。它具有功能強(qiáng)大、運(yùn)行速度快、易于學(xué)習(xí)、使用和開(kāi)發(fā)效率高等特點(diǎn)。它是可視化應(yīng)用編程環(huán)境,可重用性面向?qū)ο缶幊陶Z(yǔ)言,快速編譯器和數(shù)據(jù)庫(kù)的完美組合。編寫OPC定制接口的客戶程序的本質(zhì)就是編寫COM客戶程序,而使用Delphi進(jìn)行COM開(kāi)發(fā)時(shí),人們會(huì)發(fā)現(xiàn)Object Pascal為COM提供了強(qiáng)大的語(yǔ)言支持。主要有以下幾點(diǎn):
    (1)  Variant 和 OleVariant支持
        直接使用C和C++處理變體需要調(diào)用VariantInit(),VariantCopy(),VariantClear()等函數(shù)。而Object Pascal對(duì)變體的支持,使得在使用Variant和OleVariant類型的情況下,編譯器能夠自動(dòng)生成對(duì)API的變體支持程序的調(diào)用。
    (2)  可變數(shù)組支持
        在Delphi中一旦一個(gè)Variant包含了一個(gè)可變數(shù)組,就可以用標(biāo)準(zhǔn)數(shù)組的下標(biāo)來(lái)訪問(wèn)數(shù)組元素。與C和C++中手工生成的安全數(shù)組相比,Object Pascal的封裝功能要簡(jiǎn)潔的多,且不易出錯(cuò)。
    (3)  后期綁定Automation支持
        Object Pascal對(duì)于Variant和OleVariant的支持使得編寫后期綁定的自動(dòng)化(Automation)客戶程序成為可能。因此使用Delphi來(lái)開(kāi)發(fā)基于自動(dòng)化接口的OPC客戶程序也是很方便的。
    (4)  寬字符串支持
        Delphi提供的寬字符串(WideString)類型是一個(gè)和COM BSTR字符串兼容的字符串。而傳統(tǒng)方式下,使用標(biāo)準(zhǔn)的諸如SysStringLen()等API函數(shù)與BSTR一起工作是一件相當(dāng)麻煩的事。
    (5)  接口支持
        Object Pascal為合乎COM規(guī)范的接口提供了完全自包容的實(shí)現(xiàn)代碼,接口的實(shí)現(xiàn)不需要任何的COM API函數(shù)。程序員不需要考慮在傳統(tǒng)COM編程中的引用計(jì)數(shù)和接口查詢等底層細(xì)節(jié)。
    (6)  Dispinterface接口支持
        Object Pascal對(duì)于Dispinterface的支持使得編寫支持雙接口的COM程序非常容易。

    5  Delphi環(huán)境下使用定制接口開(kāi)發(fā)OPC數(shù)據(jù)訪問(wèn)客戶程序

        OPC規(guī)范中規(guī)定OPC服務(wù)器必須提供定制接口,而自動(dòng)化接口則可以有選擇地提供。因此編寫使用定制接口的OPC客戶程序更具有一般意義,而且使用定制接口的效率要遠(yuǎn)遠(yuǎn)高于使用自動(dòng)化接口的效率。本文限于篇幅只討論針對(duì)OPC數(shù)據(jù)訪問(wèn)服務(wù)器的客戶應(yīng)用程序。
    5.1  OPC數(shù)據(jù)訪問(wèn)服務(wù)器簡(jiǎn)介
        OPCDataAccess服務(wù)器是最基本的OPC服務(wù)器,它包括OPCServer、OPCGroup和OPCItem三類典型的對(duì)象。OPC服務(wù)器對(duì)象維護(hù)有關(guān)服務(wù)器的信息并用作OPC組對(duì)象的容器,而OPC組對(duì)象維護(hù)組的信息,提供包容OPC項(xiàng)的機(jī)制,并管理OPC項(xiàng)。OPC組提供了客戶程序組織數(shù)據(jù)的手段。有兩種類型的組:公共(Public)組和局部(Local)組。公共組可以被多個(gè)客戶共享,而局部組只能被一個(gè)客戶使用。每個(gè)組中都可以定義一個(gè)或多個(gè)OPC項(xiàng)。OPC項(xiàng)代表了與服務(wù)器中的數(shù)據(jù)的連接。客戶程序?qū)PC項(xiàng)的操作都是通過(guò)包容此項(xiàng)的OPC組來(lái)進(jìn)行的,而不是直接把OPC項(xiàng)作為一個(gè)對(duì)象來(lái)操作。每個(gè)OPC項(xiàng)都有值(Value)、品質(zhì)(Quality)和時(shí)間戳(Time Stamp)三個(gè)屬性。



    圖3  OPC數(shù)據(jù)訪問(wèn)服務(wù)器的數(shù)據(jù)組織方式

        人們要的就是上面的item,這就是點(diǎn),人們所謂的點(diǎn),就是PLC的I/O點(diǎn)、儀表的數(shù)值等。編寫客戶端的程序的過(guò)程實(shí)際上就是對(duì)在OPC服務(wù)器中的數(shù)據(jù)項(xiàng)目(Item)進(jìn)行操作。這就要求人們要了解OPC數(shù)據(jù)訪問(wèn)服務(wù)器不同對(duì)象的接口的功能:
    (1)  OPCServer對(duì)象接口
        OPCServer對(duì)象是OPC中的首要對(duì)象,它提供了如下接口:
        IUnknown接口是COM的標(biāo)準(zhǔn)接口;
       IOPCServer接口可對(duì)OPCGroup對(duì)象進(jìn)行有關(guān)操作;
       IOPCServerPublicGroups接口為客戶和服務(wù)器提供了管理公共組的功能;
       IOPCBrowseServerAddressSpace接口提供了客戶瀏覽服務(wù)器數(shù)據(jù)項(xiàng)的功能;
       IOPCItemProperties接口讓客戶能夠?yàn)g覽與ItemID相關(guān)的可訪問(wèn)屬性;
       IOPCCommon接口提供了設(shè)置和詢問(wèn)LocaleID的功能;
       IPersistFile接口允許客戶裝載和保存服務(wù)器的配置信息;
       IConnectionPointContainer接口允許用戶探查發(fā)現(xiàn)連接點(diǎn)。
    (2)  OPCGroup對(duì)象接口
       OPCGroup對(duì)象是管理數(shù)據(jù)項(xiàng)集合的對(duì)象,它提供的接口如下:
       IUnknown接口是COM的標(biāo)準(zhǔn)接口;
       IOPCItemMgt接口為客戶提供了添加,刪除和控制組中數(shù)據(jù)項(xiàng)的功能;
       IOPCGroupStateMgt接口允許客戶管理組中的所有狀態(tài)信息;
       IOPCPublicGroupStateMgt接口用來(lái)將私有組轉(zhuǎn)換為公共組;
       IOPCSyncIO接口允許用戶對(duì)服務(wù)器執(zhí)行同步讀寫操作;
       IOPCAsyncIO接口允許客戶對(duì)服務(wù)器執(zhí)行異步讀寫操作;
       IOPCAsyncIO2接口用來(lái)替代IOPCAsyncIO接口;
       IConnectionPointContainer接口允許用戶探查發(fā)現(xiàn)連接點(diǎn);
       IDataObject接口允許客戶和使用OPC數(shù)據(jù)流格式的組之間產(chǎn)生連接。
    5.2  同步讀寫方式編程
       編程部分限于篇幅只列出核心代碼,關(guān)于一些類型定義,接口描述均省略了。
    (1)  COM庫(kù)的初始化
       在調(diào)用任何COM或OLE API函數(shù)之前,必須要用CoIntialize()函數(shù)來(lái)對(duì)COM庫(kù)進(jìn)行初始化,為了關(guān)閉COM庫(kù)在最后一次調(diào)用COM庫(kù)后,要調(diào)用CoUnitialize()函數(shù)。使用Delphi開(kāi)發(fā)時(shí),事情就簡(jiǎn)化了,人們只需要在程序中包括ComObj單元即可,這樣應(yīng)用程序在調(diào)用Application.Initialize()時(shí)會(huì)自動(dòng)調(diào)用CoInitialize()函數(shù),而ComObj單元的finalization部分會(huì)自動(dòng)調(diào)用CoUnitialize()函數(shù)。只需要一句代碼:
    Uses ComObj;
    (2)  創(chuàng)建服務(wù)器對(duì)象
       Const ServerProgID = 'hua.da2.1';//OPC服務(wù)器的注冊(cè)名稱
       Var 
       ServerIf: IOPCServer;//聲明服務(wù)器對(duì)象接口
       HR: HResult;//用來(lái)保存函數(shù)返回值
       ServerIf := CreateComObject(ProgIDToClassID(ServerProgID)) as IOPCServer;
       / /本函數(shù)用來(lái)獲取服務(wù)器對(duì)象的IOPCServer接口,這是一個(gè)COM庫(kù)函數(shù)
    (3)  添加組對(duì)象
       Var  
       GroupIf: IOPCItemMgt; 
       GroupHandle: OPCHANDLE;
       HR:=ServerAddGroup(ServerIf, 'MyGroup', True, 500, 0, GroupIf, GroupHandle);
       //本函數(shù)用來(lái)對(duì)IOPCServer.AddGroup方法進(jìn)行包裝,在服務(wù)器對(duì)象中添加一個(gè)名為MyGroup的組對(duì)象,激活狀態(tài)為true,更新率為500毫秒。如果組對(duì)象添加成功,則組對(duì)象接口保存在GroupIf中,組對(duì)象句柄保存在GroupHandle中。其實(shí)現(xiàn)過(guò)程如下:
       function ServerAddGroup(ServerIf: IOPCServer; Name: string; Active: BOOL;
            UpdateRate: DWORD; ClientHandle: OPCHANDLE; var GroupIf: IOPCItemMgt;
            var ServerHandle: OPCHANDLE): HResult;
    var
      PercentDeadBand: Single;
      RevisedUpdateRate: DWORD;
    begin
      Result := E_FAIL;
      if ServerIf <> nil then
      begin
        PercentDeadBand := 0.0;
        Result := ServerIf.AddGroup(PWideChar(WideString(Name)), Active, UpdateRate,
                                ClientHandle, nil, @PercentDeadBand, 0,
                                ServerHandle, RevisedUpdateRate, IOPCItemMgt,
                                IUnknown(GroupIf));
      end;
      if Failed(Result) then
      begin
        GroupIf := nil;
      end;
    end;
    (4)  添加項(xiàng)目
    const  Item0Name = 'item.hua.bstr';//要添加的項(xiàng)目名稱
    Var 
    ItemType: TVarType;
    Item0Handle: OPCHANDLE;
    HR := GroupAddItem(GroupIf, Item0Name, 0, VT_EMPTY, Item0Handle,ItemType);
    //本函數(shù)用來(lái)對(duì)IOPCItemMgt.AddItems進(jìn)行包裝,在組對(duì)象中添加一個(gè)類型為VT_EMPTY,名稱為item.hua.bstr的項(xiàng)目,如果添加成功,則項(xiàng)目句柄保存在Item0Handle中,實(shí)際項(xiàng)目類型保存在ItemType中。其實(shí)現(xiàn)過(guò)程如下:
    function GroupAddItem(GroupIf: IOPCItemMgt; ItemID: string;
              ClientHandle: OPCHANDLE; DataType: TVarType;
              var ServerHandle: OPCHANDLE; var CanonicalType: TVarType): HResult;
    var
      ItemDef: OPCITEMDEF;
      Results: POPCITEMRESULTARRAY;
      Errors: PResultList;
    begin
      if GroupIf = nil then
      begin
        Result := E_FAIL;
        Exit;
      end;
      with ItemDef do
      begin
        szAccessPath := '';
        szItemID := PWideChar(WideString(ItemID));
        bActive := True;
        hClient := ClientHandle;
        dwBlobSize := 0;
        pBlob := nil;
        vtRequestedDataType := DataType;
      end;
      Result := GroupIf.AddItems(1, @ItemDef, Results, Errors);
      if Succeeded(Result) then
      begin
        Result := Errors[0];
        try
          if Succeeded(Result) then
          begin
            ServerHandle := Results[0].hServer;
            CanonicalType := Results[0].vtCanonicalDataType;
          end;
        finally
          CoTaskMemFree(Results[0].pBlob);
          CoTaskMemFree(Results);
          CoTaskMemFree(Errors);
        end;
      end;
    end;
    (5)  同步讀
    Var
    ItemValue: string;
    ItemQuality: Word;
    HR := ReadOPCGroupItemValue(GroupIf, Item0Handle, ItemValue, ItemQuality);
    //本函數(shù)用來(lái)同步讀取組中的項(xiàng)目值,如果讀取成功,則項(xiàng)目值保存在ItemValue中,項(xiàng)目質(zhì)量保存在ItemQuality中,其實(shí)現(xiàn)過(guò)程如下:
    function ReadOPCGroupItemValue(GroupIf: IUnknown; ItemServerHandle: OPCHANDLE;
              var ItemValue: string; var ItemQuality: Word): HResult;
    var
      SyncIOIf: IOPCSyncIO;
      Errors: PResultList;
      ItemValues: POPCITEMSTATEARRAY;
    begin
      Result := E_FAIL;
      try
        SyncIOIf := GroupIf as IOPCSyncIO;
      except
        SyncIOIf := nil;
      end;
      if SyncIOIf <> nil then
      begin
        Result := SyncIOIf.Read(OPC_DS_CACHE, 1, @ItemServerHandle, ItemValues,
                                Errors);
        if Succeeded(Result) then
        begin
          Result := Errors[0];
          CoTaskMemFree(Errors);
          ItemValue := VarToStr(ItemValues[0].vDataValue);
          ItemQuality := ItemValues[0].wQuality;
          VariantClear(ItemValues[0].vDataValue);
          CoTaskMemFree(ItemValues);
        end;
      end;
    end;
    (6)  同步寫
    ItemValue:='hello,the operation is sync-write';
    HR := WriteOPCGroupItemValue(GroupIf, Item0Handle, ItemValue);
    //本函數(shù)用來(lái)將ItemValue同步寫入Item0Handle所代表的項(xiàng)目中,其實(shí)現(xiàn)過(guò)程如下:
    function WriteOPCGroupItemValue(GroupIf: IUnknown; ItemServerHandle: OPCHANDLE;
              ItemValue: OleVariant): HResult;
    var
      SyncIOIf: IOPCSyncIO;
      Errors: PResultList;
    begin
      Result := E_FAIL;
      try
        SyncIOIf := GroupIf as IOPCSyncIO;
      except
        SyncIOIf := nil;
      end;
      if SyncIOIf <> nil then
      begin
        Result := SyncIOIf.Write(1, @ItemServerHandle, @ItemValue, Errors);
        if Succeeded(Result) then
        begin
          Result := Errors[0];
          CoTaskMemFree(Errors);
        end;
      end;
    end;
    (7)  斷開(kāi)服務(wù)器連接
       HR := ServerIf.RemoveGroup(GroupHandle, False);
       //本函數(shù)用來(lái)將在服務(wù)器中創(chuàng)建的組GroupHandle刪除,服務(wù)器對(duì)象在程序結(jié)束時(shí)會(huì)自動(dòng)銷毀。
    5.3  異步讀寫方式編程
        使用異步方式進(jìn)行讀寫編程,需要客戶提供IOPCDataCallback接口,還需要使用到COM的連接點(diǎn)容器,連接點(diǎn)和接收器等相關(guān)知識(shí)。以下關(guān)于異步讀寫方式編程中和同步方式相同的部分均未給出其實(shí)現(xiàn)部分。下面給出異步讀寫方式編程的主要步驟:
       (1)  COM庫(kù)的初始化   //實(shí)現(xiàn)過(guò)程同上
       (2)  創(chuàng)建服務(wù)器對(duì)象 //實(shí)現(xiàn)過(guò)程同上
       (3)  添加組對(duì)象 //實(shí)現(xiàn)過(guò)程同上
       (4)  添加項(xiàng)目  //實(shí)現(xiàn)過(guò)程同上
       (5)  實(shí)現(xiàn)IOPCDataCallback接口
       為了使用連接點(diǎn),客戶必須創(chuàng)建同時(shí)支持IUnknown和IOPCDataCallback接口的對(duì)象。下面的TOPCDataCallback從TInterfacedObject繼承所以支持IUnknown接口,它實(shí)現(xiàn)了IOPCDataCallback接口。
    type
       // 本類用來(lái)接收 IConnectionPointContainer 的數(shù)據(jù)變化回調(diào)
      TOPCDataCallback = class(TInterfacedObject, IOPCDataCallback)
      public
        function OnDataChange(dwTransid: DWORD; hGroup: OPCHANDLE;
          hrMasterquality: HResult; hrMastererror: HResult; dwCount: DWORD;
          phClientItems: POPCHANDLEARRAY; pvValues: POleVariantArray;
          pwQualities: PWordArray; pftTimeStamps: PFileTimeArray;
          pErrors: PResultList): HResult; stdcall;
        function OnReadComplete(dwTransid: DWORD; hGroup: OPCHANDLE;
          hrMasterquality: HResult; hrMastererror: HResult; dwCount: DWORD;
          phClientItems: POPCHANDLEARRAY; pvValues: POleVariantArray;
          pwQualities: PWordArray; pftTimeStamps: PFileTimeArray;
          pErrors: PResultList): HResult; stdcall;
        function OnWriteComplete(dwTransid: DWORD; hGroup: OPCHANDLE;
          hrMastererr: HResult; dwCount: DWORD; pClienthandles: POPCHANDLEARRAY;
          pErrors: PResultList): HResult; stdcall;
        function OnCancelComplete(dwTransid: DWORD; hGroup: OPCHANDLE):
          HResult; stdcall;
      end;
    (6)  連接IOPCDataCallback接口
       客戶程序除了實(shí)現(xiàn)接收器對(duì)象外,還必須建立接收器與連接點(diǎn)對(duì)象之間的連接關(guān)系
    var 
      AsyncConnection: Longint;
      OPCDataCallback: IOPCDataCallback;
      OPCDataCallback := TOPCDataCallback.Create;//創(chuàng)建接收器對(duì)象
      HR := GroupAdvise2(GroupIf, OPCDataCallback, AsyncConnection);
    //本函數(shù)實(shí)現(xiàn)了接收器對(duì)象與連接點(diǎn)對(duì)象之間建立關(guān)系的過(guò)程,如果連接成功則用AsyncConnection來(lái)標(biāo)識(shí)這個(gè)連接,其具體實(shí)現(xiàn)如下:
    function GroupAdvise2(GroupIf: IUnknown; OPCDataCallback: IOPCDataCallback;
              var AsyncConnection: Longint): HResult;
    var
      ConnectionPointContainer: IConnectionPointContainer;
      ConnectionPoint: IConnectionPoint;
    begin
      Result := E_FAIL;
      try
        ConnectionPointContainer := GroupIf as IConnectionPointContainer;
      except
        ConnectionPointContainer := nil;
      end;
      if ConnectionPointContainer <> nil then
      begin
        Result := ConnectionPointContainer.FindConnectionPoint(IID_IOPCDataCallback,
          ConnectionPoint);
        if Succeeded(Result) and (ConnectionPoint <> nil) then
        begin
          Result := ConnectionPoint.Advise(OPCDataCallback as IUnknown,
            AsyncConnection);
        end;
      end;
    end;
    (7)  異步讀寫
       客戶進(jìn)行異步讀寫操作只需要簡(jiǎn)單的調(diào)用IOPCAsyncIO2接口的Read,Write等方法,這個(gè)過(guò)程不需要客戶程序等待,函數(shù)的返回值中并不包括操作的結(jié)果,而操作的具體結(jié)果會(huì)由服務(wù)器通過(guò)調(diào)用客戶的IOPCDataCallback接口中的OnDataChange,OnReadComplete,OnWriteComplete,OnCancelComplete等方法來(lái)返回給客戶。下面給出IOPCAsyncIO2接口的Delphi描述:
    IOPCAsyncIO2 = interface(IUnknown)
        ['{39C13A71-011E-11D0-9675-0020AFD8ADB3}']
        function Read(
                dwCount:                    DWORD;
                phServer:                   POPCHANDLEARRAY;
                dwTransactionID:            DWORD;
          out   pdwCancelID:                DWORD;
          out   ppErrors:                   PResultList): HResult; stdcall;
        function Write(
                dwCount:                    DWORD;
                phServer:                   POPCHANDLEARRAY;
                pItemValues:                POleVariantArray;
                dwTransactionID:            DWORD;
          out   pdwCancelID:                DWORD;
          out   ppErrors:                   PResultList): HResult; stdcall;
        function Refresh2(
                dwSource:                   OPCDATASOURCE;
                dwTransactionID:            DWORD;
          out   pdwCancelID:                DWORD): HResult; stdcall;
        function Cancel2(
                dwCancelID:                 DWORD): HResult; stdcall;
        function SetEnable(
                bEnable:                    BOOL): HResult; stdcall;
        function GetEnable(
          out   pbEnable:                   BOOL): HResult; stdcall;
      end;
       當(dāng)服務(wù)器中的數(shù)據(jù)發(fā)生變化時(shí),服務(wù)器會(huì)調(diào)用客戶的IOPCDataCallback接口中OnDataChange函數(shù),當(dāng)服務(wù)器異步讀完成后會(huì)調(diào)用客戶的IOPCDataCallback接口中OnReadComplete函數(shù),當(dāng)服務(wù)器異步寫完成后會(huì)調(diào)用客戶的IOPCDataCallback接口中OnWriteComplete函數(shù),當(dāng)服務(wù)器撤銷操作完成后會(huì)調(diào)用客戶的IOPCDataCallback接口中OnCancelComplete函數(shù)。限于篇幅,這里只給出客戶的IOPCDataCallback接口中OnDataChange函數(shù)實(shí)現(xiàn),其他函數(shù)的實(shí)現(xiàn)過(guò)程類似。
    function TOPCDataCallback.OnDataChange(dwTransid: DWORD; hGroup: OPCHANDLE;
      hrMasterquality: HResult; hrMastererror: HResult; dwCount: DWORD;
      phClientItems: POPCHANDLEARRAY; pvValues: POleVariantArray;
      pwQualities: PWordArray; pftTimeStamps: PFileTimeArray;
      pErrors: PResultList): HResult;
    var
      ClientItems: POPCHANDLEARRAY;
      Values: POleVariantArray;
      Qualities: PWORDARRAY;
      I: Integer;
      NewValue: string;
    begin
      Result := S_OK;
      ClientItems := POPCHANDLEARRAY(phClientItems);
      Values := POleVariantArray(pvValues);
      Qualities := PWORDARRAY(pwQualities);
      for I := 0 to dwCount - 1 do
      begin
        if Qualities[I] = OPC_QUALITY_GOOD then
        begin
          NewValue := VarToStr(Values[I]);
          Form1.LblValue.Caption :=NewValue;
        end
        else begin
          ShowMessage('Callback received for item , but quality not good');
        end;
      end;
    end;
    (8)  斷開(kāi)IOPCDataCallback接口
    GroupUnadvise2(GroupIf, AsyncConnection);
    //斷開(kāi)客戶與組對(duì)象GroupIf之間用AsyncConnection標(biāo)識(shí)的IOPCDataCallback接口連接,其實(shí)現(xiàn)過(guò)程如下:
    function GroupUnadvise2(GroupIf: IUnknown; var AsyncConnection: Longint): HResult;
    var
      ConnectionPointContainer: IConnectionPointContainer;
      ConnectionPoint: IConnectionPoint;
    begin
      Result := E_FAIL;
      try
        ConnectionPointContainer := GroupIf as IConnectionPointContainer;
      except
        ConnectionPointContainer := nil;
      end;
      if ConnectionPointContainer <> nil then
      begin
        Result := ConnectionPointContainer.FindConnectionPoint(IID_IOPCDataCallback,
          ConnectionPoint);
        if Succeeded(Result) and (ConnectionPoint <> nil) then
        begin
          Result := ConnectionPoint.Unadvise(AsyncConnection);
        end;
      end;
    end;
    (9)  斷開(kāi)服務(wù)器連接 //實(shí)現(xiàn)過(guò)程同上

    6  結(jié)語(yǔ)

        使用OPC的定制接口進(jìn)行客戶程序的編程,需要一些COM知識(shí)的理解以及對(duì)于以下開(kāi)發(fā)工具的熟練使用,因此相對(duì)于自動(dòng)化接口的編程來(lái)說(shuō)時(shí)比較困難的,但是使用定制接口的效率和靈活性卻是使用其他方式所無(wú)法比擬的。本文只是粗淺的介紹了在Delphi環(huán)境下使用定制接口進(jìn)行OPC客戶程序的開(kāi)發(fā),希望能達(dá)到拋磚引玉的效果。

    熱點(diǎn)新聞

    推薦產(chǎn)品

    x
    • 在線反饋
    1.我有以下需求:



    2.詳細(xì)的需求:
    姓名:
    單位:
    電話:
    郵件: