Bizroboの記事で散々愚痴っていましたが、
Bizroboを使用する事による問題が看過できない状況になった為、新規の案件には極力使用しない方向になりました。
私の会社ではBizroboと相性が悪かったと言うだけの話なので万人に当たる話ではないと思います。
とは言え、メーカー側ももうちょっと向き不向きの話をまともにしてほしかったと言うのが本音ですね。
導入させたいという本音もわかりますが、入れてみてから使えないとわかるとダメージが大きいです。
これまで作成した物をリプレースしていくという作業が余分に発生してしまうからです。
さて、Bizroboを使わないとすると何か他の方法を考えないといけません。
私の会社では、画像認識をベースにしたRPAツールも使用していますが、
画像認識ゆえの問題もある為、オブジェクト認識をしてコントロール操作をする方法を用意しないといけません。
すったもんだした挙句、開発者としての知識、もしくは開発者に適性がある人でないと使う事が難しいと考えていたEXCELのマクロ(VBA)を使用した方法を使ってみようという話になりました。
EXCELのマクロ(VBA)で外部アプリを制御する
手法的には新しくも何ともありません。昔々から外部アプリを無理やり制御する為に行ってきた手法です。
アプリケーションが表示している画面の各項目は、コントロールを含めてすべてウインドウで構成されています。
画面の土台になっているウインドウはもちろん、ウインドウ上に配置されているボタンやコンボボックス、テキストボックス等も全てウインドウです。
ウインドウの制御はウインドウメッセージのやり取りで行われており、対象のウインドウに必要なウインドウメッセージを送る事であたかもユーザーがオペレーションしたかのように制御する事は可能です。
但し、これには表示されている画面を解析して何を使って対象のウインドウを特定するかという部分を自力で行う必要があります。
例えば、土台のウインドウであれば、タイトルバーに表示されている文字列を使ってウインドウを特定する。
ボタンであれば、通常はアプリケーション単位でユニークに指定されているはずのコントロールIDを使って特定する。
Etc.Etc....
画面の解析ツールは色々出ていますし、Microsoftから出ているspy++というソフトを使っても調べる事が出来ます。
制御対象のウインドウを指定するにはウインドウハンドルという値を使用します。
Windowsでは、ウインドウハンドルとメッセージコード、パラメータ2個を使ってウインドウメッセージを送信して対象を制御します。
外部アプリの制御用のモジュールを作成する
VBAでこういった操作を行う場合、必要なWin32APIをDeclare Functionしたり、必要な定数をPublic Constしたりと
必要な作業が結構あります。
また、ウインドウを制御する上での定番の操作群というのはある程度あるのでそういった定番の操作に関しては予めFunctionとして定義しておけば、少しは楽をする事が出来ます。
こういった事前に必要な定義や定番の操作をFunction化した物を集めて外部アプリ制御用モジュールを作成しています。
現状あらかた必要と思えるものは実装された状態です。
このモジュールを使ってマクロから外部アプリの制御を既に行っています。
このモジュールを使用する事でほとんどの場合は対応できるのですが、
操作される側のアプリケーション内の実装によっては定番のウインドウメッセージの組合せではうまく動かないケースがあります。
ListViewやTreeViewを使って特殊な実装で機能を実現している場合、単純にウインドウメッセージを送っただけだとうまく動かないケースがあるのです。
例えば、ユーザーがマウスでクリックすれば、うまく動作するのにウインドウメッセージで同様の動作を再現してもうまく動かない事があります。
こういった場合はSikuliX等のソフトを使って補完するか、Win32APIを使用してマウスやキーボードをエミュレーションして対応します。
マウスやキーボードのエミュレーションは実行中にユーザーがマウスやキーボードを触ってしまうと誤動作する為、なるべく使用したくないのですが、局所的に使用する事でそれ以外の部分の動作は安定するのでRPA特有の不安定さを抑え込む事が出来ます。
コントロールIDが固定でない場合がある
Windowsのネイティブなプログラミングは当初C言語で行っていました。
その後、C++言語へとメイン言語が移り、VisualStudioという統合開発環境が提供されるようになります。
VisualStudioを使用してWindowsアプリをプログラミングする場合、ウインドウ上のボタンやテキストボックスを特定する為に各コントロールに対してコントロールIDを割り振り、このコントロールIDを使って各コントロールのウインドウハンドルを取得する事でコントロールを制御すると言う流れになるのですが、
Javaを使ってWindowsアプリを実装した場合、Java自体は各コントロールをクラスオブジェクトとして制御する為、コントロールIDを使用しません。
但し、Windowsとの互換性を保つ為、内部で適当な値をコントロールIDに割り振っています。
私が見た範囲だとウインドウハンドルと同じ値が設定されているようです。
ウインドウハンドルはアプリケーションが起動される度に設定される値なので起動毎に値が変化します。
この為、コントロールIDが可変になってしまい、コントロールIDで対象の特定が出来なくなってしまいます。
私が作成している外部アプリ制御モジュールでは、この問題を解消するアプローチとして画面上の相対位置を使用して対象の特定を行うようにしています。
例えば、あるダイアログ上のOKボタンを特定する為にダイアログのウインドウハンドルと対象のコントロールクラス名、ダイアログのクライアント領域の原点からの相対位置(ピクセル単位)を使って対象のコントロールを探してヒットした物のウインドウハンドルを使って制御します。
全く同じ位置に同一コントロールが配置されていて表示/非表示を切り替える事で制御しているケースもあり得るので万能ではありませんが、ほとんどの場合はこれで識別が可能です。
対象ツール用の制御支援クラスを作成して利用しやすくする
外部アプリ制御モジュールを使用する事でWin32APIを使用して行う細かい部分を自分で書く必要がなくなるのでVBAから他のアプリケーションを操作する事の敷居はグッと低くなります。
とはいえ、Windowsアプリの基本的な知識は必要です。
ウインドウやコントロール(ボタンやテキストボックス)を制御する為にそれぞれのウインドウハンドルを取得する必要があると言う部分はどうしても必要です。
外部アプリ制御モジュールでかなりサポートしていますが、場合によっては自分でWin32APIを使って制御をする必要が出てくることもあります。
こういった特定のアプリケーションにしか必要が無いような実装部分を集めてクラス化した物を追加で作成しています。
こういった制御支援クラスは、外部アプリ制御モジュールを使っても数十ステップ記述する必要があった部分を数ステップで終わらせる事が出来るのでVBAプログラマの敷居はさらに低くなります。
制御支援クラスを導入する事でVBAを使って業務を自動化する時に対応できる人材をより広く求める事が出来るようになります。
今後、どう進めていくのか
WindowsアプリはMicrosoftが定義した定番の制御方法があります。
このテンプレートに沿って実装している場合、現在用意している外部アプリ制御モジュールで対応可能なのですが、
画面上の特殊な制御(コントロール間の連携等々)をテンプレートから外れた実装で実現してしまうと今の外部アプリ制御モジュールではうまく制御できないケースが出てきます。
アプリを操作するのが人間だけの場合は特に問題になりませんが、アプリからアプリを制御する場合は問題になる事があります。
とは言え、アプリ側の改修が可能なケースはほとんどありません。
業務上、そういったアプリも相手にする必要がある為、状況に対応ができるような実装を追加する必要は今後も出てくると思います。
今後はそういったケースに対応する為の機能拡充と制御支援クラスの対応範囲を広げていく方向で進めていく事になりそうです。