概要‎ > ‎

Friendlyの基本(.NetFramework4.0が使えない場合)

Friendlyは対象プロセスを別のプロセスから操作するためのライブラリです。
C++のfriendクラスが名前の由来です。他プロセスの内部のメソッドなどをFriendlyに呼び出せます。
以下の操作が可能です。

ここでは基本である操作呼び出しを説明します。

シンプルな例を用意しました。
以下のSampleFormクラスを表示するだけのアプリを操作します。

//プロダクトプロセス(操作対象)。
using
System;
using System.Windows.Forms;

namespace
ProductProcess
{
    public partial class SampleForm : Form
    {
        int testValue;

        private void SetTestValue(int value)
        {
            testValue = value;
        }
    }
}


このプロセスを起動し、SampleFormのSetTestValue呼び出し、フィールドtestValueの値を評価するテストです。

//テストプロセス(VisualStudioの単体テストで実行)。
using
System;
using System.Diagnostics;
using Codeer.Friendly;
using Codeer.Friendly.Windows;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace
TestProcess
{
    [TestClass]
    public class BasicSample
    {
        [TestMethod]
        public void TestSetValue()
        {
            //プロセスを起動。
            Process process = Process.Start(TargetPath.ProductProcessPath);

            //
プロセスを操作するためにアタッチ。メインウィンドウが作成されるまで同期します。
            using (WindowsAppFriend app = new WindowsAppFriend(process))
            {
                //起動した直後のprocessにはメインウィンドウのハンドルが設定されていないのでここで再取得。
                process = Process.GetProcessById(process.Id);

                //static
メソッド呼び出し。
                AppVar sampleForm = app["System.Windows.Forms.Control.FromHandle"](process.MainWindowHandle);

                //SetTestValue
を呼び出し。
                sampleForm["SetTestValue"](3);

                //testValue
の値を取得 3であること。
                AppVar testValue = sampleForm["testValue"]();
                Assert.AreEqual(3, (int)testValue.Core);
           }

           //
プロセスを終了する。
           process.CloseMainWindow();
        }
    }
}


実はこの例でFriendlyの基本操作の大部分が使われています。
Friendlyは憶えなければならないことは少ないのです。
しかし、二つのプロセスを同時に扱うため通常のプログラミングと考え方が多少異なります。
慣れるまでは少し戸惑うかもしれませんが、いくつかテストを作ると馴染んでくると思います。

プロダクトプロセスにアタッチします。
アタッチすると次の操作が可能になります。
・static操作の呼び出し。
・プロダクトプロセス内に変数の宣言。
・操作実行スレッドの選択

この例ではstatic操作の呼び出しが使われています。

AppVar sampleForm = app["System.Windows.Forms.Control.FromHandle"](process.MainWindowHandle);
System.Windows.Forms.ControlクラスのFromHandleメソッドが呼び出されます。

この呼び出しはFriendlyOperationと言ってFriendlyの根幹をなす操作です。後で詳細に説明させていただきます。

アタッチ時の注意事項として以下3点に気を付けてください。
・権限
テストプロセスの権限をプロダクトプロセスと同等以上にしてください。
プロダクトプロセスを管理者権限にしている場合はテストプロセスも管理者権限にしてください。

・プラットフォームターゲット
x86かx64かです。
プロダクトプロセスと合わせてください。
(VisualStudioのテストプロジェクトを使用する場合、実行環境はデフォルトではx86になっています。これはビルドの設定ではなく、テストホストの設定を変更する必要があります。詳細はこちらを参照お願いします。)

プロダクトプロセス内部の変数を操作します。
ここがポイントのですが、実体はプロダクトプロセスに存在します。
呼び出す処理はプロダクトプロセスで実行されます。

sampleForm["SetTestValue"](3);
SampleFormクラスに定義したSetValueメソッドを呼び出しています。

AppVar testValue = sampleForm["testValue"]();
SampleFormクラスのtestValueフィールドを取得しています。

(int)testValue.Core;
testValueの値をシリアライズしてテストプロセス側で取得します。

この呼び出しもFriendlyOperationです。
またAppVarの大事な機能としてCoreというプロパティーがあります。
public object Core { get; set; }
シリアライズ可能であれば、テストプロセスから値を取得、設定できます。
object型なので期待の型にキャストして使用します。

FriendlyOperation
Friendlyの根幹をなす重要な呼び出しです。
これによって、メソッド、フィールド、プロパティーを呼び出せます。
WindowsAppFriendとAppVarで使用することができます。
staticな操作を呼び出す場合は、WindowsAppFriendを使用し、オブジェクトのメンバを呼び出す場合はAppVarを使用します。


操作名称にはメソッド、フィールド、プロパティーを入れることができますが、
その際のそれぞれの動作に関しては、FriendlyOperationのリファレンス参照してください。
それから、この呼び出し方法は別のバージョンもありますので、WindowsAppFriendAppVarのリファレンスも参照お願いします。

ここでポイントは、操作が実行されるのはプロダクトプロセスの指定のスレッドということです。
そのため引数は本来はプロダクトプロセスのメモリでなければなりません。
この例ではIntPtr型の数値を渡しています。
呼び出した時点ではテストプロセスのメモリですが、実行時にはシリアライズされて、
プロダクトプロセスに転送されプロダクトプロセスのメモリになって、FromHandleが実行されます。
そして、戻り値はプロダクトプロセスの変数に格納され、それにアクセスするAppVarが返ります。
この処理は同期で実行されます。


ちなみに、非同期で実行する場合は、Asyncを使います。
AppVar sampleForm = app["System.Windows.Forms.Control.FromHandle", new Async()](process.MainWindowHandle);

この場合、sampleFormには処理が終わった時点でオブジェクトが格納されます。
これは、引数がoutやrefの場合も同様です。
多くの操作はテスト時には同期をとりながら実行するのが望ましいのですが、呼び出した操作によってモーダルダイアログが表示される場合にはこれを使用します。


いかがでしょうか?
これで基本の大部分は説明できました。
残りを説明するために、もう一例お付き合いお願いします。

//テストプロセス。
using System;
using System.Diagnostics;
using Codeer.Friendly;
using Codeer.Friendly.Windows;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
 
namespace TestProcess
{
    [TestClass]
    public class BasicSample
    {
        [TestMethod]
        public void TestDictionary()
        {
            Process process = Process.Start(TargetPath.ProductProcessPathd);
            using (WindowsAppFriend app = new WindowsAppFriend(process, "4.0"))
            {
                //アプリケーション内部に変数を生成。
                string name = typeof(Dictionary<int, string>).FullName;
                AppVar dic = app.Dim(new NewInfo(name));
 
                //メソッド呼び出し。intstringがシリアライズされ、転送される。
                dic["Add"](1, "1");
 
                //変数を宣言。初期値をシリアライズして転送。
                AppVar key2 = app.Dim(2); //int型の2
                AppVar value2 = app.Dim("2"); //string型の"2"
               
                //プロダクトプロセス内の変数を使ってメソッド呼び出し。
                dic["Add"](key2, value2); 

                //メソッド呼び出し out, refで値を取得したい場合は、AppVarを使用する。
                AppVar checkValue = app.Dim();//nullで宣言された変数。
                AppVar isSuccess = dic["TryGetValue"](1, checkValue); 

                //値を検証。
                Assert.IsTrue((bool)isSuccess.Core);
                Assert.AreEqual("1", (string)checkValue.Core);
            }
            Process.GetProcessById(process.Id).CloseMainWindow();
        }
    }
}


この例では、二つの点がポイントになっています。
・プロダクトプロセスに変数を宣言する。
・out引数のメソッドを呼び出す。

どちらも、プロダクトプロセスに処理を実行させるときは、プロダクトプロセス内部のメモリを使うというルールが根本にあります。
また、FriendlyOperationは引数にAppVarをとることができます。
実行時に解決され、指し示すメモリがメソッドの引数に渡されます。
Dimに関しましては、複数タイプがあるのでAppFriendのリファレンスを参照お願いします。

これで処理呼び出しに関しては、一通り説明できました。
プロダクトプロセスのメソッドを直接呼び出すことにより、きめ細やかなテストが実装できると思います。

しかし、テストによっては、もっときめ細やかな操作が必要になる場面もあります。
そのような場合に対して、「プロダクトプロセスの拡張」という方法を提供しております。
次項ではそれを説明します。