#Sample - 19.7 – コードを呼び出し (C#)

東京 2020 オリンピック・パラリンピック競技大会の開催延期がとうとう発表されてしまいました :cry:。残念ですが致し方ありません。いろいろと課題が山積していますが、前を向いて頑張りましょう。

さて、今回のトピックでは「コードを呼び出し」アクティビティで C# コードを呼び出す方法について解説します。

「コードを呼び出し」アクティビティの登場は System パッケージがまだ UIAutomation パッケージと一体化していた Core パッケージ バージョン 17.1 にまでさかのぼります。ですから、アクティビティそれ自身は新しくもありません。何が新しいかというと、従来の Visual Basic .NET (VB.NET) コードに加えて C# コードを使用できるようになったという点です。C# は、System パッケージ バージョン 19.7 以上で、新たに追加された言語 (Language) というプロパティで CSharp を選択すると利用可能になります。

ところで、C# はプログラミング言語の名前です。音楽で言う「半音上げて」のシャープ (♯) と Number Sign~番号記号 (#) は互いに異なる文字ですが、C# はシーシャープと読みます。低級言語の C に Java のような高級言語のフレーバーを加えて半音どころか大幅に機能を上げた C# は Windows プラットフォームのアプリケーションソフトウェアにおける主力の開発言語として不動の地位を築きました。C# を知らない Windows アプリ開発者はいないと言っても過言ではないでしょう。VB.NET の行指向の記述がとても楽だとしても、そして、C# は文末にいちいちセミコロンを打たなければ文法エラーになる等、どんなに煩わしさがあっても、プロフェッショナルとして C# でコードを書くことにこだわりを持っていますので、「コードを呼び出し」アクティビティで C# が利用できるようになったことは福音としか表現できません。そうですよね?

では、C# コードを書くにあたり考え方のポイントをいくつか挙げます。

まず、「コードを呼び出し」アクティビティに書き込むコードを void 型=値を返さないスタティックなクラスメソッドの本体コードのように捉えるということでしょう。実際にコードを入力する場合、メソッド本体のトップレベルの波括弧 {} に相当するものは不要です。

それから、アクティビティの外部とのデータの受け渡しは、引数プロパティを利用するということです。これはクラスメソッドの引数と同様に捉えることができます。アクティビティ外部からデータを取り込みたい場合は、入力型の引数を利用します。また、アクティビティ外部へデータを渡したい場合は、コード中に return ステートメントを書くのではなく、出力型や入出力型の引数を利用すれば良いわけです。それらはちょうど C# で言うところの out 引数や ref 引数に相当するものと考えると理解しやすいと思います。

C# ソースコードの冒頭で using ディレクティブにより参照する名前空間を列挙することについては、Studio のインポートパネルにて名前空間を列挙することに相当します。なお、名前空間を提供するアセンブリの指定については、テキストエディターで .xaml ファイルを開いて、(短い)アセンブリ名を直接追加する必要があります。この作業は手作業になるため、正直申しますと大変煩わしいのですが、C# を使える喜びのほうが勝りますので大丈夫です :sweat_smile:。具体例は後述します。

いよいよ実際に「コードを呼び出し」アクティビティで C# コードを呼び出してみましょう。

今回は、ZIP ファイルを作って、その中にテキストファイルのエントリを追加し、そのテキストファイルに文字列を書き込む C# コードを呼び出します。具体的な C# コードの内容は次のとおりです。

ZipFile クラスの Open メソッドを呼び出して ZipArchive クラスを作成することを考えます。ZipArchive クラスは ZIP ファイルを抽象化したものです。ZipFile.Open メソッドにはファイル名と ZipArchiveMode 列挙型のCreateを指定します。ZipArchive クラスの CreateEntry メソッドは、ZIP ファイルにファイルのエントリを追加します。このエントリは ZipArchiveEntry クラスです。ZipArchiveEntry クラスの Open メソッドを呼び出して、エントリのストリーム (System.IO.Stream クラス) を取得し、StreamWriter クラスで UTF-8 テキストをストリームに書き込みます。

ここで、これらのクラスと列挙型を参照するための名前空間をインポートパネルから入力しましょう。入力する名前空間は System.IO.Compression です。

それから、プロジェクトフォルダーを Windows エクスプローラーで開いておいて、プロジェクトを保存後、閉じます。プロジェクトフォルダーの中の Main.xaml をテキストエディターで開き、ZIP ファイル関連クラスと列挙型が実装されているアセンブリ名を次のように追加します。

クラスなどがどのアセンブリに実装されているかは、Microsoft のウェブサイト~ .NET API ブラウザー を参照すると分かります。以下は同サイトからの抜粋です。


再び Studio からプロジェクトを開きます。

デザイナーパネルに「コードを呼び出し」アクティビティをドラッグ&ドロップして配置してみましょう。

配置したら忘れずに言語 (Language) プロパティの値を「CSharp」に変更しましょう。それから、引数設定をして C# コードを入力しましょう。

まず「引数を編集」ボタンをクリックして引数ウィンドウを開きます。

String 型の入力引数に「ZIPファイル名」という日本語混じりの名前を付けます。(日本語混じりというのは、「日本語も問題なく使えます」アピールです。英語だけでももちろん構いません。)OK ボタンをクリックして閉じましょう。

次に「コードの編集」ボタンをクリックします。するとコードエディターウィンドウが開きますので、C# コードを入力しましょう。

OK ボタンをクリックしてコードエディターを閉じましょう。Process Explorer などで観察すると、コードエディターを閉じた直後に csc.exe(C# コンパイラー)が実行されるのが分かります。入力した C# ソースコードがコンパイルされ、実行可能か否かがチェックされているものと考えます。エラーが検出されると、アクティビティの右肩に青地に白のビックリマーク「!」(エクスクラメーションマーク)が表示されます。マウスをその上にホバーさせると、ツールチップウィンドウがポップアップし、コンパイルエラーが表示されます。もし「!」が表示されたら適宜修正しましょう。

さて、コンパイルエラーがないことが確認できたら、実行可能ということです。実行してみましょう。

ん?実行エラーが発生します。 :sob:

ここまできて、もうお詫びするしかないのですが、Studio でコンパイルエラーがなく正常な状態でも、Robot は .xaml ファイルの AssemblyReference 要素で指定したアセンブリをユーザーが意図したとおりにロードしてくれない場合があります。残念ながら今回の例はそれに該当します。申し訳ありません。

回避策はあるのでしょうか。はい。直接的な回避策と間接的な回避策があります。

直接的な回避策を説明します。

実行エラーはアセンブリがロードされていないことに起因します。ですので、その問題を解決するために明示的にアセンブリをロードするのです。

「コードを呼び出し」アクティビティの前に「メソッドを呼び出し」アクティビティを配置します。そして、ターゲット型 (TargetType) プロパティに「System.Reflection.Assembly」を指定し、メソッド名 (MethodName) プロパティに「Load」を指定します。パラメータープロパティには、入力として String 型の値に不足しているアセンブリ「”System.IO.Compression”」を指定します。

im1

どうでしょうか?うまく実行できたと思います。プロジェクトフォルダーに Foo.zip という ZIP ファイルが作成されて、そこに Bar.txt というテキストファイルが格納されていて、その内容は 1 行 Baz! となっていることでしょう。(例示した C# コードの 2 回目以降の実行は事前に前回作成した ZIP ファイルを削除してください。存在していると作成できない旨のエラーが発生します。)

もし Assembly.Load メソッドの呼び出しが失敗する場合は、短いアセンブリ名ではなく、長いアセンブリ名を指定してください。例えば、短いアセンブリ名

System.IO.Compression

に対する長いアセンブリ名は

System.IO.Compression, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

となります。

では、間接的な回避策とは何でしょうか。

今回の例では、ワークフローに「メッセージをログ」アクティビティを含めるだけで正常に動きます。含める位置は問題になりません。ワークフローの中のどこでも構いません。これが正常に動く理由は、今回ロードされなかったアセンブリを「メッセージをログ」アクティビティ自身か同アクティビティが利用しているクラス等がたまたま参照していたということに由来すると考えます。そのおかげで、ワークフローの実行直前に必要なアセンブリがロードされるため、「コードを呼び出し」アクティビティも問題なく動くということのようです。これが間接的な回避策です。この回避策は特に何もしないため、策という呼び方は正しくないかもしれません。呼び出すコードの内容によって結果が違います。もし、ワークフローでだれも参照しないアセンブリを「コードを呼び出し」アクティビティのコードが使用する場合は、直接的な回避策を講じてください。

ということで、Windows アプリ開発者 “垂涎” の「コードを呼び出し」アクティビティで C# コードを呼び出す方法について解説しました。長い説明にお付き合いいただき感謝いたします。

カスタムアクティビティを作るのは面倒だけど、ちょっと凝ったことをしたい場合、「コードを呼び出し」アクティビティで C# コードを実行するというのは、案外便利なのではと思います。ライブラリプロジェクトで使えば、カスタムアクティビティと遜色ない機能性を発揮するかもしれません。 :vulcan_salute:

サンプルコード: InvokeCode_CSharp_20200327.zip (2.1 KB)

2 Likes