CodeZine(コードジン)

特集ページ一覧

SEXYHOOKで始めるテスト
とある関数の接合部(1)

既存のソースコードに手を加えず、自由に接合部を作成

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2010/02/22 14:00

ダウンロード sexyhook0.8 (80.0 KB)

目次

挙動を書き換えてみる

//include するだけで利用可能です。
#include "sexyhook.h"

//2000年以上か?
bool isOver2000year()
{
        //現在なら間違いなく true. これを falseにするには?
        return time(NULL) >= 946652400; //2000-01-01 00:00:00
}
int main()
{
        {//A
                //現在は2000年以上なので true を返すはず.
                bool r = isOver2000year();
                SEXYHOOK_ASSERT( r == TRUE );   //2000年以上です.
        }
        {//B
                //一時的にtime 関数をフックする。
                SEXYHOOK_FUNC_HOOK_1_BEGIN(time_t,SEXYHOOK_CDECL,time,time_t * a)
                {
                        //time が呼ばれたらここがコールされる。
                        //昔の時刻を返すようにする。
                        return 915116400;//1999-01-01 00:00:00
                }
                SEXYHOOK_FUNC_END();

                //2000年以前では false を返すことを確認する
                bool r = isOver2000year();
                SEXYHOOK_ASSERT( r == FALSE );  //1999年です.
        }
        {//C
                //スコープを抜けたのでtime()関数はもう上書きされていない。
                bool r = isOver2000year();
                SEXYHOOK_ASSERT( r == TRUE );   //2000年以上です.
        }
        puts("ok");
        return 0;
}

 途中のSEXYHOOK_ASSERTで関数が正しい挙動を返したかを確認しています。テストを実行してみると、OKと表示されると思います。

 Bの部分で、time関数の挙動を書き換え、isOver2000year関数はfalseを返しています。

Aの部分では、まだ書き換えを行っていないので、isOver2000year関数はtrueを返しています。
Cの部分では、書き換えを行うスコープを抜けたので、書き換える前の状態に戻り、time関数は現在の時刻を返しますので、isOver2000year関数はtrueを返します。
 

 名著『レガシーコード改善ガイド(Working Effectively With Legacy Code)』では、関数の依存性を下げ、ソースコード上に「接合部」を作り出すことでテストを行いやすくすると述べられています。

 残念なことに、大抵のソースコードは例のようなAPIのハードコーディング、深い依存性の上に作られています。クラスを作成する時インターフェース継承で作成したり、APIをすべてクラスで抽象化して作ることの方が稀だと思います。

 SEXYHOOKを利用すると、ソースコード上に新たな接合部を自由に作成できます。その上、既存のソースコードに一切手を加える必要はありません

さまざまなフック

 SEXYHOOKでは、関数、クラスメソッド、APIの3つのフックを行うことができます(APIフックはWindowsのみ)。

関数をフックしたい場合

//ターゲット
//上の例でもあったtime関数をフックしてみる
time(NULL);

//関数をフックするときは、 SEXYHOOK_FUNC_HOOK_1_BEGIN を使います。
SEXYHOOK_FUNC_HOOK_1_BEGIN(time_t,SEXYHOOK_CDECL,time,time_t * a)
{
        return 915116400;//1999-01-01 00:00:00
}
SEXYHOOK_FUNC_END();
//例 その2
//strstr関数をフックしてみる。
const char * p = strstr("hello","hel");

//strstr をフックする場合
//引数によって定義を変えてください。
//引数が2つなので、 SEXYHOOK_FUNC_HOOK_2_BEGIN になります。
SEXYHOOK_FUNC_HOOK_2_BEGIN(const char*,SEXYHOOK_CDECL,strstr,const char * a , const char * b)
{
        //絶対失敗する strstr 関数にする
        return NULL;
}
SEXYHOOK_FUNC_END();
//例 その3
//自作関数をフックしたい場合
int add(int a,int b)
{
        return a + b;
}

//add関数を書き換えて「引き算」にする。
SEXYHOOK_FUNC_HOOK_2_BEGIN(int,SEXYHOOK_CDECL,add,int a,int b)
{
        return a - b;   //引き算に書き換える
}
SEXYHOOK_FUNC_END();
//例 その4
//オーバーロードされた関数
int uadd(int a,int b)
{
        return a + b;
}
char uadd(char a,char b)
{
        return a + b;
}

//足し算なので「引き算」に変更
SEXYHOOK_FUNC_HOOK_2_BEGIN(int,SEXYHOOK_CDECL,uadd,int a,int b)
{
        return a - b;
}
SEXYHOOK_FUNC_END();
//例 その5
//テンプレート関数
template<typename T> T mul( const T& a,const T& b ){
       return a * b; //掛け算
}

//掛け算を「割り算」に変更
SEXYHOOK_FUNC_HOOK_2_BEGIN( int, SEXYHOOK_CDECL, mul<int>, const int& a , const int& b)
{
        return a  / b;
}
SEXYHOOK_FUNC_END();
//例 その6
//グローバルなオペレーターオーバーロード
class OpClass
{
        int A;
public:
        OpClass(int inA) : A(inA)
        {
        }
        int getA() const
        {
                return this->A;
        }
};

//グローバルなオペレーターオーバーロード
bool operator==(const OpClass& x, const OpClass& y)
{
        return x.getA() == y.getA();
}

//常に false を返す == 演算子
SEXYHOOK_FUNC_HOOK_2_BEGIN(bool,SEXYHOOK_CDECL,operator==,const OpClass& x, const OpClass& y)
{
        return false;
}
SEXYHOOK_FUNC_END();

 書式は、次のようになります。

SEXYHOOK_FUNC_HOOK_引数の数_BEGIN(戻り値,呼出規約,関数名,引数....)
{
        フックする処理
        return 返却したい値;
}
SEXYHOOK_FUNC_END();

 引数の数によって使い分けが必要です(Microsoft Visual C++ 6が可変長マクロに未対応のため)。この中で「呼出規約」は、普段あまり気にすることがないかもしれません。

 SEXYHOOKでは、SEXYHOOK_CDECLとSEXYHOOK_STDCALLの2つを定義しています。これらは、Windowsで言うcdecl、stdcallに対応しています。

SEXYHOOKでの定義
SEXYHOOKでの定義 Microsoft Visual C++ gcc
SEXYHOOK_CDECL cdecl attribute ((cdecl))
SEXYHOOK_STDCALL stdcall attribute ((stdcall))

 詳しい説明は呼出規約(Wikipedia)をご覧ください。よく分からなければ、とりあえずSEXYHOOK_CDECLの方を使ってください。

 引数の数は0~10個まで定義しています。SEXYHOOK_FUNC_HOOK_0_BEGIN~SEXYHOOK_FUNC_HOOK_10_BEGINまで定義済みです。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

修正履歴

  • 2010/02/23 13:15 -Wno-pmf-conversionsオプション関連の一文を削除しました。

  • 2010/02/14 21:59 最新版では-Wno-pmf-conversionsが不要になりました。 gccバージョンを書きました。

バックナンバー

連載:「SEXYHOOK」 とある関数の接合部

著者プロフィール

  • rti(あーるてぃーあい)

    働いたら負けだと思っていた元ニートのプログラマ 歌って踊れてさくらたんにもハァハァできます。 love:C++,アセンブラ,PHP,javascript好きのOO厨房 低レイヤープログラムやネットワークサーバからサーバサイドプログラム、ソフトウェア設計、インフラ設計運用までと幅広くやってます。...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5