概觀
LabVIEW 為風行 20 年整的軟體。LabVIEW 屬於設計工具嗎?LabVIEW 是程式設計語言嗎?LabVIEW 兩者都是。因為不需電腦設計師的參與,科學家與工程師即可針對電腦進行程式設計。對想要新增功能的 LabVIEW 開發者而言,僅需顧慮客戶,而非設計者的需求。針對 8.2 版,NI 介紹 LabVIEW 物件導向程式設計 (LVOOP)。物件導向 (OO) 為程式設計形式,均為艱深的抽象概念與技術詞彙。要了解此程式設計形式,則必須專精程式設計的相關知識,或花費極長時間進行學習。NI 希望簡化此程式設計的複雜度,讓廣大的使用者都能使用物件導向的強大功能。該結果可能讓熟悉其他程式設計語言的物件導向擁護者感到驚訝。此白皮書將揭露 NI 建立 LabVIEW 物件導向程式設計時,於其背後所下的決定與理由。 此白皮書將假設對 LabVIEW 物件導向程式設計具有某程度的了解。客戶可重新檢視 LabVIEW Help 的相關部分與範例程式之後,再繼續閱讀此白皮書。
目錄
LabVIEW 物件導向程式設計的高階設計
LabVIEW 為什麼需要物件導向的程式設計?
LabVIEW 的目標,是要將程式設計的強大功能交予工程師與科學家,並不侷限於程式設計而已。針對沒有受過程式設計訓練的使用者,NI 想架構 LabVIEW 出直覺式的介面。
在多種程式設計語言中,物件導向的程式設計,已經展現其於程序化 (Procedural) 程式設計中的優勢。OOP 加強了程式碼區段間的乾淨介面、可輕鬆進行除錯,並可針對大型的程式設計團隊進行調整。LabVIEW R&D 希望將此強大功能帶給使用者。NI 將應用此語言,進行這些軟體的最佳使用機會。
與程序化程式設計相較,物件導向程式設計需要更多的前置計畫。如果想要撰寫可用上一段時間的 VI 集,可能要計算某些值或從 DAQ 資料擷取卡中取得特別的值,接著建立類別 (Class) 以翹曲 (wrap) 這些 VI;這些步驟可能過於煩雜。如果使用者計畫維持這些 VI 所形成的應用數年以上,則物件導向應為正確的選擇。NI 想將新的軟體設計工具加入至 LabVIEW,而又不希望這些工具阻礙 LabVIEW 的原型製作設計功能。
For those who choose object designs,
we want wire and node
to morph naturally
into class and method.
NI 選擇 「物件導向程式設計 (OOP)」的意義?
C++ 為物件導向的語言。Java 亦然。Smalltalk 亦屬於一份子。仍有許多程式語言屬於此一領域。每個語言均包含獨特的功能集。當 NI 選擇新增物件導向至 LabVIEW,亦必須決定其理由。C++ 能夠產生 C++ 的功能範本或物件導向的功能範本嗎?操作者負擔的工作量如何?或考慮到 Java 中的關鍵字「已同步化」?
NI 選擇物件導向代表 2 件事:資料封裝 (Encapsulation) 與繼承 (Inheritance)。當決定 NI 必須支援物件導向語言時,其實有 2 大支持此決定的理由。LabVIEW 使用者必須能夠「封裝」一種類別 (Class) 資料的類型 – 如叢集般定義資料區,並讓 LabVIEW 可存取由使用者指定函式中的資料。使用者必須能夠「繼承」新的類別 (Class) – 使用現有類別為開始點,選擇現有的類別,並建立新的類別;接著從母體類別置換方法。堅持此 2 個原則,可避免功能蔓延 (Feature creep)。
物件導向應如何符合資料流?「依數值 (By-Value)」與「依參考 (By-Reference)」的爭論)
LabVIEW 使用資料流語法。圖中節點將於該輸入進行作業,並僅限於這些輸入;同時不影響圖中其他接線的值。當接線分叉,則將複製該值。這些使用「依數值」語法的接線,將與資料流保持一致。例外狀況即為參考數字 (Refnum) 的資料類型。參考數字 (Refnum) 為記憶體共享位置的參考,透過某些保護機制,以多重節點進行作業。當接線分叉,將不會複製共享記憶體。這些接線則使用「依參考」語法。當開發 LabVIEW 類別 (Class) 時,NI 即面臨類別接線應為「依數值」或為「依參考」的選擇。
考慮 C++。該語言具有 2 種宣告 (Declare) 物件的方法:於堆疊 (stack) 上宣告,或作為堆積 (heap) 中的指標。當物件通過至函式,或指派該物件至另一個變數時,則必須持續注意要依數值或依參考來通過該物件。使用者僅僅複製資料,或有與其他人共享資料?另一方面來說,Java 僅具有「依參考」的語法。函式參數的變數指派,往往會參照原始物件。若要進行複製,則必須將原始物件作為來源,建立新物件。
哪個方式適合 LabVIEW?在資料流語言中,使用「依數值 (by-value)」的語法,則表示個別接線的值均獨立於各接線之外。此將可於程式碼中執行多執行緒,且不需要擔心程式碼單區段的值,會影響到另一區段中的值。「依參考 (By-reference)」語法將破壞獨立性。使用者必須負責追蹤參考所共享的次數,並確定沒有程式碼的區段於同時間存取參考。使用者必須知道受保護的區段、互斥 (Mutex),與競態條件 (Race condition)。另一方面來說,若透過參考建立如圖表或樹狀圖的大型資料架構,則作業過程將簡單多,而這些資料架構亦可大量使用物件導向的結構。
LabVIEW 已具備於單接線上共享資料的機制。雖然照某些標準來看,這些機制並不具有效率,但是該機制仍存在。LabVIEW 並不需要其他共享資料的方法。僅需要隔離資料的方法。當使用者未限制資料變更,則很難保持資料的一致性。為了支援資料封裝 (Encapsulation),NI 決定 LabVIEW 中的類別 (Class),應為不受所有 VI 限制的基礎叢集。因此 LabVIEW 不同於 Java 與 C++,採用純「依數值 (by-value)」的語法。當接線分叉,則可能複製物件。
選擇「依數值」而非「依參考」,卻與其他設計決定產生衝突。許多標準的物件導向設計形式將述詞 (predicate) 於一樣物件,以進行接點 (point) 並敘述:「我關心在這裡的物件。」LabVIEW 具有建立參考至資料的機制 – 未初始化移位暫存器、通用變數、單元素佇列 – 但是以上這些機制需要比「依參考」語法更多的作業。而 NI 已成功建立 Map 與 Graph 的資料架構。要建立這些架構,則必須用與資料流一致的方法。若沒有用相同的方法,則將無法具有 LabVIEW 最強大的功能之一:自然平行特性 (Natural parallelism)。下列其他語言的設計形式,可能造成低效率與奇怪的問題。NI 自己的設計形式將可擺脫時間問題。
當使用者第一次接觸 LabVIEW 物件導向程式設計時,此決定將成為最讓人滿意的要點,然而 LabVIEW R&D 團隊已經花了數年評估多次,而 NI 亦確信此為 LabVIEW 的正確決定。針對參考功能的提升,亦考慮到未來的擴充性。對實際開發而言,固定的「依數值」建置動作,則必須與資料流一致。
應如何選擇所用的字彙?
針對概念所選擇的名稱,將影響使用者對此概念的想法,並可讓使用者了解此概念。當 NI 將物件導向程式設計帶至 LabVIEW,亦考慮到介紹「電腦科學」的深入程度。LabVIEW 希望能夠讓未接受過電腦科學訓練的使用者,都能進行程式設計。NI 的客戶均為科學家與工程師,進行測試、量測、工業級控制,與硬體設計。由於 LabVIEW 程式語言接線電路圖的相似度,可讓這些客戶輕鬆適應。NI 亦試著找到可讓多種物件導向概念存取的方式。
NI 決定針對繼承體系使用家族字彙:母體與子系、兄弟姐妹與表兄弟姐妹的關係。這些為客戶心中早已熟知的概念詞彙。當發話方 (Speaker) 指向所給予類別 (Class) 的母體,收話方 (Listener) 則可輕鬆了解其間關係與接下來的對話方式。NI 已使用如「母體類別 (super class)」或「基礎類別 (base class)」,但並未於個人概念中建立相同的家族關係。翻譯小組將比較喜歡這些字彙的用法。NI 亦希望使用者介面與記錄文件,可用繼承樹狀圖的方式呈現;包含頂端的父母 (Ancestor) 與底端的子嗣 (Descendent)。在其他語言中,開發者往往不自覺倒看樹狀圖,而浪費了許多時間。NI 希望能串連起 LabVIEW 開發者之間的一致性。
透過示波專用術語,NI 決定延用業界標準的「私有 (private)」、「受保護 (protected)」,與「公用 (public)」。某些開發者起初拒絕「受保護」的用詞。私有資料與公用資料為直覺性的概念。「受保護」的用辭其實不具有意義 – 要保護什麼呢?– 而且並需選擇可實際辨認圖形的字彙,如「家庭 (Family)」;可保有繼承的概念。然而,不論何種語言,「受保護」是業界中一致使用的用詞,而使用者亦常常在其他輔助說明或教科書中看到該詞彙。既然辭會並未衝突 LabVIEW 中的現有概念,NI 決定延用標準詞彙。
「類別 (Class)」較具有爭議。「類別 (Class)」明顯為「資料與函式的區塊,可於分組資料中進行操作」的工業標準用字。亦為物件導向程式設計的基礎概念。但是 NI 內部討論另一個讓使用者更容易接受的用字。LabVIEW 已經有了 typedef 的概念 – 為控制 VI,可根據現有類型定義新的資料類型。除了避免使用關鍵字「類別 (Class)」,亦可代替新類型的 typedef:Typedef 物件。雖然此概念可能讓第一次看到物件導向的 LabVIEW 使用者,可以接受該觀念,但是 NI 的使用者仍習慣於其他語言中看到物件導向。不使用「類別」亦可能讓這些人困擾 – 一個物件導向的語言怎能沒有類別?因佌,如同「受保護」,NI 接著採用工業標準用詞。NI 亦仔細記錄,以區別「LabVIEW 類別」與「VI Server 類別」。
「虛擬」與「虛擬配置 (virtual dispatching)」的概念,亦對 LabVIEW 形成問題。這些用詞亦為工業標準用詞。在 C++ 或 Java 中,虛擬功能將根據輸入 (該「此」物件) 之一的執行時間資料類型,以選用不同的版本。LabVIEW 功能即所謂的虛擬儀器。若說成「虛擬虛擬儀器」,則會感到很奇怪。同樣的,「虛擬」亦不算是精確的用詞 – 極難以了解這些功能的實際「虛擬」概念。所以 NI 選擇了「動態」與「動態配置」。這些詞彙可清楚連接靜態配置的概念;靜態配置為 LabVIEW 使用數十年 subVI 的稱呼。使用「動態」與「靜態」,將清楚辨別此 2 種 VI 的分組方式。在其他物件導向語言中的「靜態」用詞,可能會在 LabVIEW 中產生問題;本份文件將於稍後討論此詞彙。
還有針對其他詞彙的小型討論。「物件導向」則為其他實際語言概念中所常見。若要讓所有人廣泛接受「物件導向」,則必須質疑每個新概念。NI 無法假設為 C++ 或 Java 命名的功能,亦對 LabVIEW 來說是正確的功能名稱。
LabVIEW 類別的設計
「LabVIEW Object」類別之目的為何?
「LabVIEW Object」為特殊 LabVIEW 類別的名稱。此特殊類別為所有 LabVIEW 類別的基礎起源。JAVA 具有相似的類別 (java.lang.object)。C++ 並沒有。
由於具有一般的基礎類別,則可於撰寫函式時採用任何類別,並可依一般方式操作此類別。由於 C++ 具有範本,因此不需要此類別;但若新增範本至語言,將大幅增加語言的複雜度。雖然 LabVIEW 有可能具有自己的類別範本,NI 仍認為必須降低解決方案的複雜度。一般的基礎類別,將提供由節點所使用的類型 (Type),如建立陣列 (Build Array),以儲存多重類別至單一陣列中。但是 LabVIEW 並不需要此「通用類別 (generic class)」。因為 LabVIEW 即具有可包含 LabVIEW 資料的多種資料類型。但是 LabVIEW Object 可連接「To More Specific Class」函式,並且是許多類型所無法連接的 (如果 NI 讓 LabVIEW 可連接變數,亦會成為很奇怪的事)。
LabVIEW Object 類別的特殊使用案例,即為 LVClassLibrary 參考的「預設範例 (Default Instance)」屬性。此屬性一如其名稱所示,將退回包含其預設值的類別範例;如 Private Data Control 中所設定的預設值。此功能可讓使用者大量示例任何類別。此功能亦將成為「plug-in」應用中的通用技術。由於 LabVIEW Object 並不具有可定義該功能的函式,因此使用者將使用「To More Specific Class」功能,以轉換接線至定義過的類別類型。針對「plug-in」架構,使用者必須透過所有已定義的函式,以定義類別名稱為「Generic Plugin.lvclass」或相似的名稱。針對所有的特定「plug-in」項目,將此類別做為母體類別。接著即可動態載入子類別。

- 於磁碟上開啟 .lvclass 檔案的參考,即可轉回 LVClassLibrary 的參考號碼。
- 取得類別的預設範例。
- 傳送 LabVIEW Object,並取得「plug-in」的資料類型。
為何類別僅具私有資料?為何不具有受保護資料或公用資料?
如同 NI 挑戰其他語言所使用的名稱,NI 亦挑戰整個功能。LabVIEW 僅具私有資料。使用者並無法建立公用或受保護的資料。僅有 VI 可為公用資料或受保護的資料。
資料僅能夠為私有資料的主要理由,即為使用者的程式碼正確性 (Correctness)。NI 試著建立 LabVIEW 的語言與環境,即便使用者未受過選擇架構的訓練,不需要了解太多電腦科學的概念,亦可選擇出最佳的架構。若僅透過函式存取類別的資料,則必然遇到除錯值變更的瓶頸。此將建立範圍檢查的空間,並可新增其他健全性檢查 (Sanity check) 的程式碼。而且確定的是,類別外部的程式碼並不具有二元相依性 (Binary dependency),因此即使是要建立應用,亦可散佈 (disseminate) 新修正的類別。類別資料封裝具有多個優點,而 NI 想要鼓勵工程師設計具有這些優點的軟體。但並非支援公用或受保護的資料,NI 降低可能由程式語言所混亂的電腦科學概念 (至少 NI 去除了需要解釋的概念),並提供更好的架構。
此選擇可刺激存取函式 (Accessor Method) 的發展。若針對各資料值建立「取得 (Get)」與「設定 (Set)」函式,則為極繁瑣的程序。NI 針對未來 LabVIEW 版本的計畫,即是要針對私有資料中的值,能夠迅速建立存取函式的方法。
NI 亦考慮建立 subVI call 所產生的效能耗用,以取代資料分割 (Unbundling)。NI 發現目前 subVI call 的編譯器耗用是可忽略的 (一般 PC 量測到的值為 6 ~ 10 微秒之間)。直接於 VI 與 subVI 中進行的整合/分割作業,其時間相去不遠。在未來的版本中,NI 將加入「嵌入 (Inlining)」功能;此功能將完全移除耗用。NI 認為於長遠來看,此將提升工程師的經驗,與 VI 的編譯器效率;將比公用/受保護資料的入門功能更具優勢。
建構元 (Constructor) 在哪裡?
從另一個程式設計語言了解物件導向,並使用 LabVIEW 物件導向程式設計有數小時的使用者,往往會產生上面的問題。程式語言的某些基礎室無法完全隱藏的,而找不到建構元的使用者往往會質問 NI 為何難以找到建構元。但是答案很簡單:沒有建構元。
先來看看建構元的使用範例:
- 設定類別的初始值。
- 經由參數集計算類別的初始值。
- 經由環境資料計算類別的初始值 (於建構物件時,記錄時間戳記)。
- 保留此物件所使用的系統資源 (記憶體、通訊頻道、硬體等等),以於稍後由解構元 (destructor) 釋放。
LabVIEW 可針對類別設定預設值。使用者於私有資料中設定的值,即為自己類別的預設值。那現在這些為計算的值嗎?不,這些為統計數據。使用者不會有空間可將執行中的程式碼設為預設值的一部分。此相同於其他物件導向語言中的簡易預設建構元。類別的所有範例初始值,將僅為預設值;因此已填滿 Constructor Use Case #1。
應如何設定 C++ 中的整數?
int k = 1;
應如何設定 LabVIEW 中的整數?
僅須拖曳數字 (Numeric) 控制,設定為整數,鍵入一個「1」,並「設定目前的值為預設值 (Make Current Value Default)」。
考慮此 C++ 類別...
class Thing {
私有:
int mx;
double mz;
std::string ms;
公用:
Thing() :mx(1), mz(2.5), ms("abc") { }
Thing(int x) :mx(x), ms("def") {
mz = (x > 1) ? 3.5 : 2.5;
}
~Thing() { }
void Init(int x) {
mx = x; mz = (x > 1) ?3.5 : 2.5; ms = "def";
}
};
應如何於 C++ 中呼叫 (Invoke) 非預設建構元?
Thing item(k+3);
應如何於 LabVIEW 中呼叫 (Invoke) 非預設建構元?
無法呼叫。此對 LabVIEW 來說是無意義的問題。使用者並無法將參數傳送至控制,以初始化其值。他們無法取得從參數至 VI 的值,亦無法取得使用者於人機介面鍵入的值。
在資料流的領域中,建構元的概念確實是一種問題。不論是以值開始 (沒有外接輸入的完整計算),或是值流動至建構元,均以值抵達建構元之後才會開始計算。由資料類型定義,並建立 LabVIEW 的類別時,即具有初始值。此為 Constructor Use Case #1。所有的範例將初始化至相同值。如果想要有進階動作,則可於程式區建立 subVI 以定義之;該 subVI 將進入非類別輸入,並輸出類別。此為 Constructor Use Case #2。
Constructor Use Case #3 為針對軟體工程師的進階概念。LabVIEW 支援此概念,但是支援的方式並不明顯。計算類別的範例數量 (使用類別靜態欄位),或記錄類別最不需要的範例時間戳記。NI 將類似函式應用為更進階的工具。使用者可根據自己的類別類型,建構 XControl。XControl 具有可於編輯時間與執行時間執行的程式碼,以初始化類別的數值。在 Façade VI 中,使用者可包含任何所需的程式碼,以設定資料範例所需的值。類別的設計者可能會覺得此步驟稍嫌煩雜。這也是 NI 希望能夠簡化的地方。但這也是放置此函式的正確位置 – 控制之下的程式碼。
使用 Case #4 的建構元,僅能於下個問題中一併討論。
解構元 (Destructor) 在哪裡?
何謂 C++ 整數的圖?
當設定 (declare) 了點之後該圖就存在,直到右大括弧 (closing brace)。
何謂 LabVIEW 整數的圖?
不清楚。直到接線末端?直到人機介面關閉?直到停止執行 VI?
LabVIEW 並不具有「space scope」。LabVIEW 具有「temporal scope」。只要有需要,此份資料就會存在;直到不需要此份資料,即會消失。如果將其複製到人機介面控制,則當執行結束之後都會保留在該處。複製的接線會保留到接線的下一次執行。並將複製接線上的探針 (probe)。在完全的理論資料流語言中,由於每個接線為獨立的計算單位 (computation unit),所以個別的接線將具有個別的複製單位。當然,實際建置將可能不具效率,因此 LabVIEW's 編譯器簡化了複製的數量。但原則均相同:資料將存在一段長時間,亦可能超過產生資料的程式。
在此環境中,可參考 Constructor Use Case #4。建構元何時將保留 (reserve) 資源?每次進行複製時,又將發生什麼樣的變化?當使用者針對接線分叉、指示器 (indicator),與探針 (probe) 要進行複製時,將需要不同的複製建構元嗎?如果要回答這些問題,則必須列出許多特殊案例與例外情況。要決定何時應執行這些隱式建構元 (Implicit Constructor) 以保留資源,是非常困難的一件事。而萬一使用者找不到令人滿意的答案,則必須決定解構元釋放這些資源的時間。
LabVIEW 中的資源保留,是由參考數字 (refnum) 的資料類型所運作。參考數字類型將依數值複製參考數字,使用者必須呼叫 (invoke) 函式以「深層複製 (deep copy)」或釋放之。範例的佇列資料類型具有保留參考的 Obtain Queue,與釋放參考的 Release Queue。除非使用者呼叫 Release Queue,否則佇列將保留於記憶體中,直到 VI 完成執行作業。LabVIEW 僅能於 VI 結束執行時,辨識「圖末 (end of scope)」。
若不具有「圖 (scope)」較深入的概念,則亦無法使用 Case #4。
總結來說,LabVIEW 雖為資料流語言,但是不具有變數 (variable)。接線不為變數。人機介面控制不為變數。甚至在其他語言中,本端變數或通用變數亦無法決定變數位置。變數為模型的一部分,易與資料流產生衝突。若沒有變數與圖 (scope) 定義這些變數的期限,則建構 (construction) 與解構 (destruction) 均為無意義的概念;因此 NI 將這部份排除於語言設計之外。
何謂類別的 in-memory 配置?
要能夠有效率地於 LabVIEW 中儲存類別,實為一大挑戰。
類別資料定義為兩部份:
- 從母體繼承而來的資料主體 (Chunk of data)
- 自有的資料叢集
使用者可將任何所給予的資料,想像為叢集群中的一個叢集 – 每個內部叢集均來自於該母體;除了來自於類別本身的私有資料叢集。最初的母體為 LabVIEW Object,並未提供任何資料欄位。

LabVIEW 不具有函式堆疊 (function stack)。資料流,為不同程式圖 (diagram) 節點平行執行的地方,除了可能互相交錯,亦可能發生堆疊架構的問題。反之,當 VI 進行編譯時,LabVIEW 將針對該 VI 分配「資料空間 (dataspace)」。資料空間為所有資料需要執行該 VI 所分配的位置。只要沒有其他執行緒在該處進行寫入,任何執行緒均可於該資料空間的範圍中進行寫入,因此不需要互斥鎖定 (mutex locking)。
若要建置類別,則需要於資料空間中分配類別。屬於母體類型的接線,必須能夠攜帶自有類型或其他後續類型的資料。因此於資料空間中進行的分配,將必須能夠保有任何類別之一。此意謂我們無法直接分配這些叢集的叢集。
若要更進一步進行複雜設計,則必須根據該類別資訊進行物件。物件為資料的「自覺 (self-aware)」部份。物件知道自己的類型 (也因為如此,物件可執行如 To More Specific Class 與動態配置的作業)。類別資訊在某些點上,必須為物件的一部分。
所以記憶體中的最後架構將如:

LabVIEW 將分配類別預設值的單一範例。只要是預設值的物件,均共享該指標 (Pointer)。此處的共享,如果有極大型的陣列作為類別的預設值,則記憶體初始將僅具有該陣列的一個複製。當使用者寫入至物件資料時。如果該物件目前由預設的資料指標所共享,則 LabVIEW 將複製預設值,接著於複製上執行寫入。若共享預設範例,將可讓整個系統使用較少的記憶體,並更迅速地開啟 VI。
我能夠遞迴 (recursively) 定義類別嗎?
私有資料控制中的資料,應為完美定義的 LabVIEW 類型,包含其他 LabVIEW 類別。類別的叢集甚至可能為空白,僅留定義函式,並不具有任何本身的資料。
遞迴 (recursive) 類別為使用本身定義作為自己一部份的類別。此種類別將出現於其他物件語言中。舉例來說,在 C++ 中可見到:
class XYZ {
int data;
XYZ *anotherInstanceOfThisType;
};
因此,很明顯的,類別使用自己的類型來定義本身。此並不完全正確。技術上來說,欄位類型實際為「pointer to XYZ」,並非「XYZ」本身。若要了解其他語言分配記憶體至遞迴式定義物件的方法,此技術特徵將極為重要。其他語言實際上並不會為物件保留空間。但是會為該物件的參考保留空間。
LabVIEW 為依數值(by-value) 的語言,並不具有「類別參考(reference to class)」的控制。如果類別控制包含在私有資料叢集中,則整個類別值亦將包含於私有資料中。如果想要實際例示 (instantiate) 包含了本身的類別,則將遞迴資料類型定義 – 於每個層級依數值分配,直到記憶體被填滿。僅限完全未使用 XYZ 定義的類別,可以使用為 XYZ 的欄位。
針對進階的 LabVIEW 物件導向工程師,NI 提供違反直覺的函式;當初考慮移除的東西,最後卻保留之,看看使用者會激盪出怎樣的火花。若將母體類別作為子類別的資料成員 (data member),則使用者將造成意想不到的結果。由於母體類別完全未以子類別進行定義,因此不需建立遞迴類型的定義,即可使用母體類別為子類別資料叢集的一部分。在執行時間中,使用者可將任何值儲存至位置,包含子類別本身的範例。理論上來說,使用者可建立相同類型的物件鏈 (chain of object),並由母體類別的範例來決定。透過某些聰明的 VI 設計,即可用多重參考,依理論建立連結清單、樹狀圖,或複雜圖形結構。這種結構可能不甚穩定。若建構單一接線上更改數值的 VI,則有可能更改另一條接線上的值;於 LabVIEW 中違反資料流規則,則很可能造成系統當機。然而,並非所有的圖都會造成這種問題,而使用者應可建立穩定的樹狀圖或 API 圖表。此為尚未完全測試或分析的領域。NI 考慮到必須防止母體類別遭使用為子類別的資料成員,以避免系統當機。因此 NI 開放的系統,想讓使用者能夠以此種語法進行有趣的事情。這即有可能建立穩定的圖表資料類型;類別之內為依參考的 VI,但類別之外為依數值的 VI – 因此為安全執行緒與一致的資料流。
使用者應如何做出類別資料 (即暫態資料)?
類別資料為不屬於物件範例的資料。記憶體中僅複製一份類別資料,並且單一範例可能為公用資料、私有資料,或受保護的資料。有多種建立類別資料的方法。
最簡單的方法,即為通用 VI。新增通用 VI 至類別,並於稍後將圖 (scope) 設定為私有或受保護的資料,則可限制其他 VI 能否存取該資料。但是即便是於類別中形成的通用 VI,一般亦為無法接受的 VI – 因為這些 VI 在資料流之外進行作業,所以不具有執行緒安全性,並且將產生大量的效能耗用 (針對唯讀作業則需要複製)。
較佳的解決方案,即為包含未初始化移位暫存器(shift register) 的VI,即為線上輔助說明的「LV2-style global」。從 LabVIEW 2.0 以來,建立通用資料的方法已包含於 LV 中 (名稱由此而來)。使用者可讓此種 VI 成為類別的成員,並於稍後將圖設定為私有或受保護的資料。此種通用 VI 可讓使用者定義執行緒安全的集 (Set),可用於資料中。若需要更多詳細資訊,請參閱 LabVIEW 輔助說明或範例 VI。NI 附註特別範例,可協助了解應如何建置工業級標準的「單例 (singleton)」設計形式。
類別函式的設計
哪個接線為「此」物件?使用者應如何進行類別暫態函式?
C++ 與 Java 均具有「此」物件的概念。當史用者於這些語言中定義物件函式,可針對函式指定外顯參數 (explicit parameter)。程式語言將假設額外的編譯參數,讓此函式上的物件可進行作業。已編譯的參數即稱為「此」物件,而使用者可透過特殊語法,以存取該物件的部份。
在這些程式語言中,「類別暫態函式」為此類別所屬部分的函式,卻不具有編譯的參數。由於不具有已編譯的物件,使用者無法於類別暫態函式中使用特殊的「此」語法。
LabVIEW 並不具有這些概念。使用者可於接頭面板 (connector pane) 中,將所有輸入設定至成員 VI。LabVIEW 從未新增已編譯的輸入。因此不具有隱性 (implicit) 輸入,所以具有輸入與不具有輸入的 VI 之間並無差異。
LabVIEW 的確具有 NI 所謂「暫態」的函式,但與其他語言相較,NI 使用該詞彙於不同的意義。LabVIEW 區分出 2 種類型的函式:動態函式與暫態函式。暫態函式為簡易的 subVI call,LabVIEW 從一開始即具有此概念。因為 subVI 節點僅呼叫相同的 subVI,所以稱函式為「暫態」。相反而言,動態函式即為 VI 集。動態 subVI 節點即使用動態配置,以呼叫集 (Set) 中的 VI 之一;但是要到執行時間才會知道所呼叫的是哪個 VI。
動態配置 (Dispatching) 如何作用?此配置的耗用 (overhead) 與一般 subVI call 相較為何?
動態配置為一功能,可讓看起來像 subVI call 的節點,根據動態配置輸入終端的接線數值,於執行時間實際呼叫多個 subVI 之一。概念上而言,這比較近似於多型 (polymorphic) VI,可於執行時間選擇所要執行的 VI,而非於編譯時間選擇。
由於 VI Server 可支援任何 VI 的動態呼叫,測試者通常認為動態配置的速度較 subVI call 來得慢。然而,編譯器具有更多特定 VI 集 (Set) 的相關資訊;動態配置可取得的資訊多於通用 VI Server,因此可提供較佳的效能。
在連線中移動的每個物件,均具有該類別資訊的標記 (請參閱此份文件的「何謂類別的記憶體配置?」)。 此類別資訊包含「動態配置表」,即為 VI 參考的表格。每個類別將確實複製其母體的表格。接著將透過本身的最高優先 (overriding) VI,取代 VI 參考為任何母體函式。類別若附加至本身動態配置 VI 的表格,將不會取得高於自己母體類別的優先順序。
- Parent.lvclass 將定義 2 個動態配置 VI:A.vi 與 B.vi。
- Child.lvclass 將繼承母體。Child.lvclass 將定義 2 個動態配置 VI:B.vi 與 C.vi。B.vi 將優先於表格中的第二個輸入項。
- Junior.lvclass 將繼承子 VI。Junior.lvclass 將定義 2 個動態配置 VI:A.vi 與 D.vi。A.vi 將優先於表格中的第一個輸入項。
當進行編譯時,圖表中的動態配置 subVI 節點,將記錄特殊的檢索號碼 (index number)。舉例來說,代表調用 (invocation) A.vi 的節點,則記錄為 index 0。代表調用 B.vi 的節點則記錄為 index 1。在執行時間中,不論物件何時出現於接線中,節點均將存取該物件的動態配置表。並將於記錄指數中檢索 VI,接著呼叫 (invoke) 該 VI。節點並未引起任何稱為 Lookup 的耗用,或搜尋清單以找出所要呼叫的 subVI。不論繼承樹狀圖的範圍有多長,不論類別定義了多少動態配置 VI,時間的複雜性均為O(1)。
以此而言,幾乎僅只是一個 subVI call。由於 LV 無法最佳化原地算法 (inplaceness);即為記憶體複製,亦無法跨越動態配置呼叫與靜態配置呼叫。LabVIEW 最佳化了最初母體的原地算法,將此情況降至最低 – 因此調用 (invocation) B.vi 時應使用 B.vi 的 Parent.lvclass 版本,以決定是否需要複製輸入。在大部分的情況中,NI 發現由於子類別使用相同的接頭面板,優先順序超過 VI,並且保留與母體 VI 相同的函式,因此需要相同的原地算法 (inplaceness) 形式。當原地算法形式符合時,則耗用將等同於 subVI call。此情況為,即便使用者並不想要直接呼叫母體建置 (ancestor implementation),亦會在母體建置的輸入至輸出接線中,得到效能優勢 (就如將母體類別作為抽象類別而使用)。
使用者可於類別中超量使用 (overload) VI 名稱嗎?
否。在其他的物件導向語言中,超載 (Overloading) 為一函式;在不同的參數清單中,有兩個函式具有相同的名稱。編譯器將根據函式調用 (invocation) 中所給予的參數,找出工程師所要呼叫的函式。舉例來說,此函式意指使用者可擁有稱為「初始 (Init)」的多重函式;其一可從檔案中進行物件初始化 (因此將路徑作為唯一的參數),另一個則可從其他物件進行初始化 (因此將物件作為參數)。
特別當牽涉到動態配置時,此函式即為其他程式語言中可能的錯誤來源。如果某人更改母體類別中的參數清單,卻忘記更改子類別中的參數清單,則編譯器將視此兩種不同的函式為宣告 (declaration),而非屬編譯器錯誤。如此將難以追溯於執行時間所產生的錯誤與問題。NI LabVIEW 不允許在任何情況下,記憶體中有兩個具有相同名稱的 VI。新增過載 (overloading) 可建立難以進行除錯的類別;對 NI 來說,最好少碰此函式為妙。
進階的物件導向函式支援
有任何方法宣告一個類別為另一個類別的Friend Class 嗎?
此版本中無此功能。針對不熟悉此概念的使用者,宣告一個類別為另一個類別的 Friend Class,將讓其他類別可存取該類別的私有部份。針對某些 API 而言,「Friend of」的語法極為重要。典型範例為矩陣 (Matrix) 類別與向量 (Vector) 類別 – 若函式多次將矩陣乘以向量,則需要存取兩個類別的內部私有部份。
因為發表此版本的時間有限,NI 去除了此函式。未來版本若要有此函式,則必須根據客戶的反應而定。如果建置此函式,NI 將考慮此「Friend」宣示應有別於其他的物件導向語言。設定 Friend 類別之後,不需另外定義 VI,即可存取全部的私有函式與資料。在大部分時間中,Friend 類別僅需存取特定函式;但在其他語言中,則必須開放存取全部類別,否則就完全封鎖。NI 計畫將「Friend」製成圖 (Scope),讓使用者可設定函式 – 就像公用、受保護,與私有。經宣示為 Friend Class 的類別,即可存取 Friend 圖示的函式。但是這些 Friend class 仍無法存取受保護或私有的函式,亦無法存取斯有資料。NI 衷心期待使用者或客戶給予此概念的回應。
使用者可以建立內部類別嗎?
此版本中無此功能。類別程式庫並不包含任何其他的程式庫類型 (Plain 程式庫、XControl,或 LabVIEW 類別)。此為 NI 將於未來提供的功能,但是亦將以需求情況為優先考量。
NI 支援私有繼承(private inheritance) 嗎?
任何母體類型的控制,均包含任何衍生類型(descendent type) 的資料。從母體接線出現的資料,可能為任何的衍生類型。此即為動態配置的關鍵,也因此物件導向可協助寫入通用演算式;針對每個類別類型進行一般步驟。於開發期間,一位測試版客戶向 NI 反應,若將 LabVIEW 定位為強型別語言 (strongly typed language),則將位於弱勢。該名客戶希望能夠從母體類別繼承子類別,但是必須避免子類別向上成為母體類型。依此方法,如果意外將子接線連至母體終端,則將得到斷線。
同時,該名客戶要求其他語言中所謂的「私有繼承 (Private Inheritance)」功能。透過私有繼承,類別將繼續繼承該母體類別的所有屬性,但是不會有類別以外的 VI 將知道該繼承,因此無法連接多個類型。LabVIEW 僅具有公用繼承。私有繼承為物件導向程式設計極少使用的概念,並可能增加不必要的複雜度。若要支援此功能,可能必須先解決私有繼承要於 NI 語言中作用的問題。使用者必須能夠將子資料連接至母體終端,以從母體類別呼叫函式繼承。但是此方法會將母體控制中的子資料,放置於母體 VI 的 FP 之上。若以非類別中的 VI,透過屬性節點來存取母體控制的值,又將發生什麼樣的情況呢?會遇到錯誤情況嗎?或是沒人知道類別之外的母體與子體具有任何關係,而將退回子資料嗎?像這些有關私有繼承的問題,實在難以處理。LabVIEW 可能永遠不會包含這些功能。
NI 支援多重繼承 (multiple inheritance) 嗎?
多重繼承為重要的理論,在實作中所產生的問題與其解決的問題一樣多。「我想要將所有 A 與 B 的屬性整合至 C」聽起來很不錯,但若開始先解決名稱衝突、混合資料、解決鑽石狀繼承 (B 與 C 繼承 A,D 再繼承 B 與 C),與其他不妥案例。LabVIEW 不具有多重繼承,並可能永遠不會增加此功能。NI 針對 Java-style 的介面有很多想法,並可解決大部份多重繼承的實際使用案例。但是該介面為軟體的進階架構,未受過某種程度的電腦科學架構訓練,仍無法了解該介面。因此,建置此種架構為低程度優先。在擴充程式語言的功能之前,NI 將先專注於提升單一繼承與資料封裝的基礎功能。
為何 LabVIEW 禁止 LV-built DLL (共享程式庫) 介面中的 LabVIEW 類別?使用者可以從 C++、.NET,或其他物件導向語言匯出,或匯入自己的類別至以上語言嗎?
當使用 Application Builder 建立 DLL (或於其他平台共享程式庫) 時,如果 VI 的接頭面板使用任何 LabVIEW 類別,LabVIEW 將不允許任何自 DLL 匯出的 VI。LabVIEW 與外接程式碼之間的介面,必須為平坦 (plain) 的資料類型。
如果回顧類別範例的 in-memory 配置,則將看到許多 LabVIEW-specific 的標示 (pointer) 與分配。沒有其他的 EXE 或 DLL 可以使用資料結構,或存取類別的片斷 (piece),以支援動態配置或強制類別製圖 (scoping)。同樣地,類別的 C++ 建置亦因編譯器而有所不同。由於其多樣性,大多數的 C++ 使用者不建議於 DLL 介面中放置 C++ 類別。物件導向的本質,即為若建置架構適於一個編譯器,則將不適於另一個編譯器;因此之間無法互動。
在此版本中,並無法將任何語言中的類別轉換為 LabVIEW 的類別,亦不支援轉換 LabVIEW 類別至其他語言類別。單就一點來說,LabVIEW 類別使用「依數值 (by-value)」語法;而轉換此語法為「依參考 (by-reference)」語法 (如 JAVA 與 .NET),則需要 Human implementation。透過自動化,「依參考」轉換為「依數值」較為可行,但仍需要許多時間解決問題。除非 NI 收到來自於客戶或使用者的需求,否則亦為低優先度的功能。
結論
LabVIEW 物件導向程式設計 (LVOOP) 將掀起所有使用者 VI 的革命,並於某天稱為「LabVIEW 的最佳功能」嗎?
NI 可能會夢想物件導向的革命。但事實為,物件導向不是屬於所有人的。物件導向是工具,是 LabVIEW 給予使用者可持續擴充的工具集。若想以快速、單一的 VI 進行量測,則 LabVIEW 類別的功能過強。但是針對完整的應用而言,即便是小型公用程式,物件導向程式設計可協助組織程式碼、提升維護性,並可輕鬆使用 LabVIEW 的相關經驗。在此第一個版本中,的確有窒礙難行之處,NI 也希望進階功能的使用者可以儘量嘗試所有功能。過了一段時間後,NI 也希望 LabVIEW 使用者開始針對專案使用物件導向,就如同其他的程式設計語言一樣。
LabVIEW 物件導向程式語言 (LVOOP) 團隊將專注於「最佳功能」的部份。請讓 NI 知道你的想法!
相關連結
合法
此教學由美商國家儀器 (以下簡稱 NI) 開發。此教學受 NI 技術支援,但未經完整測試及檢驗。NI 不保證品質,亦不為其更新版本、相關產品及驅動程式等後續支援負責。此教學不具任何形式保證,且不受任何特定用途規範。(http://ni.com/legal/termsofuse/unitedstates/us/)
