概要‎ > ‎

DLLインジェクション

以下のサンプルはこちらからダウンロードできます。

Friendlyを使ったDLLインジェクション

テスト時にプロダクトプロセス自身に追加でアセンブリやネイティブDLLをロードさせます。
WindowsAppExpanderクラスを使用します。
このクラスを使ってアセンブリをロードさせると、そのアセンブリがプロダクトプロセスの通常解決できるパスになくても
依存関係を解決を解決することができます。
これを使うと、テストコードをプロダクトプロセスで実行させることができます。

拡張する動機は?
一括でプロダクトプロセスに処理させたい。」

次の例を見てください。

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Codeer.Friendly.Windows;
using System.Diagnostics;
using System.Collections.Generic;
using Codeer.Friendly.Dynamic;

namespace TestProcess
{
    [
TestClass]
   
public class DllInjectionSample
    {
       
WindowsAppFriend _app;
       
Process _process;

        [
TestInitialize]
       
public void TestInitialize()
        {
           
//プロダクトプロセスを起動し、接続する。
            _app =
new WindowsAppFriend(Process.Start("ProductProcess.exe"));
            _process =
Process.GetProcessById(_app.ProcessId);
        }

        [
TestCleanup]
       
public void TestCleanup()
        {
            _app.Dispose();
            _process.CloseMainWindow();
        }

        [
TestMethod]
       
public void TestDictionary()
        {
           
dynamic dic = _app.Type<Dictionary<int, string>>()();

           
//自身のアセンブリをプロダクトプロセスにも読み込ませる。
           
WindowsAppExpander.LoadAssemblyFromFile(_app, GetType().Assembly.Location);

           
//このアセンブリに定義したAdd10000が使えるようになる。
            _app.Type<
ExpandSample>().Add10000(dic);

           
//値を評価するメソッドでAssertを使用するのでAssertのアセンブリも読み込ませる。
           
WindowsAppExpander.LoadAssemblyFromFile(_app, typeof(Assert).Assembly.Location);

           
//プロダクトプロセス内部で値を評価。
            _app.Type<
ExpandTest>().Check(dic);
        }

       
//プロダクトプロセスで実行されます。
       
static void Add10000(Dictionary<int, string> dic)
        {
           
for (int i = 0; i < 10000; i++)
            {
                dic.Add(i, i.ToString());
            }
        }

       
//プロダクトプロセスで実行されます。
       
static void Check(Dictionary<int, string> dic)
        {
           
for (int i = 0; i < 10000; i++)
            {
               
string val;
               
Assert.IsTrue(dic.TryGetValue(i, out val));
                
Assert.AreEqual(i.ToString(), val);
            }
        }
    }
}


FriendlyOperationはプロダクトプロセス内部の操作を簡単に呼び出すことができますが、
内部的にはアプリケーション間通信が実行されるので、通常の同一プロセス内のメソッド呼び出しと比較すると、非常に低速です。
使用するPCにもよりますが、1ミリ程度かかります。
ループで一万回呼び出そうものなら、それだけで10秒消費してしまいます。

自動テストは毎日のように実施されるので、できるだけ早く終わらせたいところです。
そこで、この例ではテストプロセスに一括して処理を実行させています。
この方法であれば、アプリケーション間通信は一度しか発生しないので、高速に処理されます。

また、一括で処理を実行させると、その間に別の処理が割り込みません。
これは、後ほど紹介しますが、ウィンドウに処理を実行させる場合に途中にメッセージ処理を割り込ませたくない場合に有効です。

ちなみに、この例では値の評価もプロダクトプロセス内部で実施させましたが、失敗した場合、どうなるのでしょうか?
Checkメソッドを以下のように書き換えてわざと失敗させてみます。

static void Check(Dictionary<int, string> dic)
{
    for (int i = 0; i < 10000; i++)
    {
        string val;
        Assert.IsTrue(dic.TryGetValue(i, out val));
        Assert.AreEqual("", val);//わざと失敗させてみる。
    }
}

VisualStudioでは以下のように結果が表示されました。

Friendlyは呼び出し先で発生した例外の情報はテストプロセスの方に転送され、
FriendlyOperationExceptionとしてテストプロセスで例外がthrowされます。
その際に、呼び出し先のスタック情報も取得できるので、実際に失敗した行と、テストプロセスで呼び出した行がわかります。

それから、実はもう一つプロダクトプロセスを拡張したい動機があります。
「ネイティブDLL公開関数」を利用する場合です。

次項で解説します。