CODESYS Example Projectを調べてみた #5 (Element Collections Library)
Element Collections Examples
Connpassの勉強会のまとめ記事です。
今回は、Elements Collections Examplesを調査します。このExampleでは、リスト(List)、キュー(Queue)、スタック(Stack)、ハッシュテーブル(HashTable)、ツリーノード(TreeNode)など、データ構造の利用方法を学ぶことができます。
セットアップ
プロジェクトファイルは、以下のように9つのApplicationで構成されています。
プログラムを実行するには、最初に以下の準備を行います:
- デバイスのアップデート: プロジェクト内で使用しているデバイスを最新バージョンに更新します。
- 不足ライブラリの追加: 必要なライブラリを追加します。
Applicationの切り替えは、ビルドアイコンの横にあるドロップダウンメニューから行えます。各Applicationを選択してビルドし、ダウンロードを実行してください。1つのApplicationでログインしている状態で別のApplicationに切り替えても、ログイン状態は維持されます。
プログラムの内容
Element Collections Examples.project
には、以下の9つのApplicationが含まれています。数が多いので、今回はNo.1, No.2, No.4に焦点を当てます。
- DynamicListExample
- Element Example
- OnlineChangeSafeLinkedListExample
- SimpleHashTableExample
- SimpleListExample
- SimpleQueueExample
- SimpleSortedListExample
- SimpleStackExample
- SimpleTreeNodeExample
1. DynamicListExample
このExampleでは、COLライブラリのListFactory
、IntElementFactory
、IList
などを使用して、動的なリスト操作を学びます。
COLライブラリとは?
COLはElementCollections
ライブラリのNamespaceで、リストやキューなどのデータ型を提供するFunction Blockを含んでいます。ライブラリの詳細はCODESYS Online Helpから確認できます。
動的リストの生成
以下のコードは、動的リストを生成する例です。データ型に対応したFactoryとインターフェース型の変数を用意して、FactoryのCreateメソッドでインスタンスを生成し、インターフェース型変数に持たせるというのが他のExampleでも共通する操作の流れです。
VAR
・・・
ListFactory : COL.ListFactory; // リスト生成用Factory
itfList : COL.IList; // リスト操作用インターフェース
・・・
END_VAR
// 動的リストの生成
itfList := ListFactory.CreateDynamicList(
udiInitialSize := 10, // リストの初期サイズ
uiGrowFactor := 2 // リストが満杯になった場合の増加倍率
);
- udiInitialSize: リストの初期容量。適切に設定することでメモリアロケーションの回数を削減します。
- uiGrowFactor: 容量が不足したときの成長係数。適切な値を選択することで、メモリ使用量とパフォーマンスのバランスを調整します。
リスト内の要素を巡回
リストの内容を巡回するには、COL.IList
インターフェースとCOL.ListIterator
(イテレーター)を使用します。イテレーションの基本手順は以下の通りです:
-
イテレーターの生成
ElementIterator
メソッドを使用してリストのイテレーターを取得します。このイテレーターは、リストの現在の状態に基づいて操作を行います。 -
次の要素の確認 (
HasNext
)
HasNext
メソッドを使用して、リスト内にさらに巡回できる要素があるかを確認します。 -
次の要素の取得 (
Next
)
Next
メソッドを使用して、現在の位置から次の要素を取得します。
以下はコード例です。
VAR
・・・
iterator : COL.ListIterator; // イテレーター
itfElement : COL.IElement; // リスト内の要素
itfIntElement : COL.IIntElement; // INT型要素
・・・
END_VAR
// イテレーターの初期化
itfList.ElementIterator(itfIterator := iterator);
// 要素の巡回と処理
・・・
IF iterator.HasNext() THEN
iterator.Next(itfElement => itfElement); //IElemnet型の変数にイテレーターの次の要素を受け取る。
// itfElementを用いて要素に関連する処理
END_IF
要素の追加と解放
リストに要素を追加し、エラーが発生した場合はインスタンスを解放します。
METHOD AddProductToShow
VAR_INPUT
iProductID : INT;
END_VAR
VAR
itfElement : COL.IElement;
eError : COL.COLLECTION_ERROR;
itfInstance : FBF.IInstance;
END_VAR
// 要素の生成と追加
itfElement := IntElementFactory.Create(iValue := iProductID);
eError := itfList.AddElement(itfElement := itfElement);
IF eError <> COL.COLLECTION_ERROR.NO_ERROR THEN
__QUERYINTERFACE(itfElement, itfInstance);
IF itfInstance <> 0 THEN
itfInstance.Dispose();
END_IF
END_IF
2. Element Example
このExampleでは、独自のデータ型MyData
を定義し、それをIMyElement
インターフェースを通して管理します。また、動的生成を可能にするためにMyElementFactory
を使用します。
実装の流れ
-
MyData
の定義
データ型MyData
をFBF.InstanceData
を基に定義します。これにより、リストやハッシュテーブルなどのコンテナで管理できる要素型を作成します。 -
インターフェースの定義
COL.IElement
を拡張したIMyElement
インターフェースを定義します。このインターフェースでは、カスタム要素に特化したメソッドを宣言します。 -
インターフェースの実装
MyElement
というFunction BlockでIMyElement
を実装します。これにより、カスタムデータ型に対する具体的な操作が可能になります。 -
Factoryの定義
動的生成を可能にするためにMyElementFactory
を定義します。
インターフェースの定義
以下は、IMyElement
インターフェースの定義です。COL.IElement
を拡張し、2つのプロパティTestIntValue
とTestIntValue2
を追加しています。
INTERFACE IMyElement EXTENDS COL.IElement
PROPERTY TestIntValue : INT
PROPERTY TestIntValue2 : INT
インターフェースの実装
以下は、MyElement
というFunction BlockでIMyElement
を実装する例です。このFunction Blockに対しては、Elementを操作するためのメソッドがいくつか定義されています。
{attribute 'no_explicit_call' := 'Explicit calls not allowed.'}
FUNCTION_BLOCK MyElement EXTENDS FBF.InstanceBase IMPLEMENTS IMyElement
VAR_INPUT CONSTANT
iInt1 : INT;
iInt2 : INT;
END_VAR
メソッドの例としてElementEqualsを見ると以下のような実装になっています。
METHOD ElementEquals : BOOL
VAR_INPUT
(* The element to compare*)
itfElement : COL.IElement;
END_VAR
VAR
itfIntElement : IMyElement;
xResult : BOOL;
END_VAR
xResult := __QUERYINTERFACE(itfElement, itfIntElement);
IF xResult THEN
ElementEquals := TestIntValue = itfIntElement.TestIntValue AND TestIntValue2 = itfIntElement.TestIntValue2;
ELSE
ElementEquals := FALSE;
END_IF
Factoryの定義
以下は、MyElementFactory
を定義し、MyElement
のインスタンスを動的に生成する例です。Factoryは、必要に応じてMyElement
のインスタンスを生成し、メモリ管理を自動化します。
{attribute 'enable_dynamic_creation'}
FUNCTION_BLOCK MyElementFactory EXTENDS FBF.FactoryBase
VAR
END_VAR
SUPER^();
プログラムでの利用
以下は、PLC_PRG
でMyElementFactory
を利用してリストに要素を追加する例です。動的に生成したElementとStaticに生成したElementをListに追加しています。異なるデータ型をListに追加できるため、PythonのListのように利用できます。
PROGRAM PLC_PRG
VAR
IntElementFactory : COL.IntElementFactory;
itfIntElementOnHeapMemory : COL.IIntElement := IntElementFactory.Create(iValue := 100);
intElementStatic : COL.IntElement := (iValue := 101);
MyElementFactory : MyElementFactory;
itfMyElement : IMyElement := MyElementFactory.Create(iValue1 := 299, iValue2 := 733);
myElement : MyElement := ( iInt1:= 776, iInt2 := -23);
aElements : ARRAY[0..9] OF COL.IElement;
list : COL.List := (paElements := ADR(aElements), udiMaxElements := 10);
xFirst : BOOL := TRUE;
END_VAR
// リストに要素を追加
IF xFirst THEN
// Add elements to the list.
list.AddElement(itfElement := itfIntElementOnHeapMemory);
list.AddElement(itfElement := intElementStatic);
list.AddElement(itfElement := itfMyElement);
list.AddElement(itfElement := myElement);
xFirst := FALSE;
END_IF
4. SimpleHashTableExample
このExampleでは、ハッシュテーブルを作成し、Key-Valueペアを管理します。FactoryでHashTableのインスタンスを生成し、インターフェース(ここでは、IMap)に設定し、IMap型の変数でKeyValueの追加やKeyでの検索を行います。
ハッシュテーブルの生成と操作
以下は、ハッシュテーブルの生成とKey-Valueペアの追加、検索の例です。KeyやValueで使う値はElementを生成するFactoryからインスタンスを生成して使用します。
PROGRAM PLC_PRG
VAR
HashTableFactory : COL.HashTableFactory;
itfMap : COL.IMap := HashTableFactory.Create(udiMaxElements := 100);
StringElementFactory : COL.StringElementFactory;
state : ActionState := ActionState.IDLE;
sKey : STRING := 'Enter a unique key';
sValue : STRING := 'Enter a value';
sKeyFind : STRING;
itfStringKey : COL.IStringElement;
itfIInstance : FBF.IInstance;
sValueFound : STRING;
eError : COL.COLLECTION_ERROR;
aStringValues : ARRAY[0..99] OF STRING;
iterator : COL.ListIterator;
itfElement : COL.IElement;
itfStringElement : COL.IStringElement;
i : INT;
xResult: BOOL;
END_VAR
CASE state OF
ActionState.IDLE:
// Idle
ActionState.ADD_ELEMENT:
// Add key value pair
eError := itfMap.AddKeyValuePair(itfKey := StringElementFactory.Create(sValue := sKey), itfValue := StringElementFactory.Create(sValue := sValue));
IF eError = COL.COLLECTION_ERROR.NO_ERROR THEN
state := ActionState.UPDATE_VISU;
ELSE
state := ActionState.IDLE;
END_IF;
ActionState.FIND_ELEMENT_BY_KEY:
// Finds a value by key
itfStringKey := StringElementFactory.Create(sValue := sKeyFind);
eError := itfMap.GetElementByKey(itfKey := itfStringKey, itfValue => itfElement);
xResult := __QUERYINTERFACE(itfStringKey, itfIInstance);
IF xResult THEN
// Remove the temporary key
itfIInstance.Dispose();
END_IF
// Set the found value
sValueFound := '';
xResult := __QUERYINTERFACE(itfElement, itfStringElement);
IF xResult THEN
sValueFound := itfStringElement.StringValue;
END_IF
IF eError = COL.COLLECTION_ERROR.NO_ERROR THEN
state := ActionState.UPDATE_VISU;
ELSE
state := ActionState.IDLE;
END_IF;
ActionState.UPDATE_VISU:
// Clear visu array
FOR i := 0 TO 9 DO
aStringValues[i] := '';
END_FOR
eError := itfMap.Keys(itfIterator := iterator);
i := 0;
WHILE iterator.HasNext() DO
iterator.Next(itfElement => itfElement);
// Cast IElement to IStringElement
__QUERYINTERFACE(itfElement, itfStringElement);
aStringValues[i] := itfStringElement.StringValue;
i := i + 1;
END_WHILE
state := ActionState.IDLE;
END_CASE
学びと気づき
- 動的データ構造の使い方:リストやハッシュテーブルなどを動的に生成して管理する方法を習得。
- インターフェースの活用:汎用性を持たせるプログラミング設計の重要性を理解。
-
リソース管理の重要性:
Dispose()
を適切に使用してリソースを解放する必要性を再確認。 -
属性値の設定:
{attribute 'enable_dynamic_creation'}
など属性値の設定はあまり調べられていないので今後の課題。
まとめ
COLライブラリを使用する際の一般的な手順は以下の通りです:
-
ファクトリによる生成
- コンテナ(リストやハッシュテーブル)や要素をFactoryで生成。
-
インターフェースを利用した操作
- コンテナや要素を操作するための抽象化されたインターフェースを使用。
-
イテレーターで要素を巡回
-
ListIterator
を用いてリストやハッシュテーブルの要素を順に処理。
-
-
__QUERYINTERFACE()によるデータ型の確認
-
__QUERYINTERFACE
で、データ型のElementがIIntElementなど、より具体的なデータ型のインターフェースを持っているか確認して処理を行う。
-
Discussion