SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

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

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

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

ダウンロード 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まで定義済みです。

次のページ
クラスメソッドをフックしたい場合

修正履歴

この記事は参考になりましたか?

「SEXYHOOK」 とある関数の接合部連載記事一覧
この記事の著者

rti(あーるてぃーあい)

働いたら負けだと思っていた元ニートのプログラマ歌って踊れてさくらたんにもハァハァできます。love:C++,アセンブラ,PHP,javascript好きのOO厨房低レイヤープログラムやネットワークサーバからサーバサイドプログラム、ソフトウェア設計、インフラ設計運用までと幅広くやってます。多分設計信者。 あんまり得意ではないけど 人工知能(学習エンジン)とかシステム...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/4882 2010/02/23 13:15

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー