挙動を書き換えてみる
//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での定義 | 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まで定義済みです。
