トップ 検索 一覧 差分 ソース ヘルプ RSS ログイン

mainとスレッドの前後処理(3)

この記事はmainとスレッドの前後処理(2)の続きです。

この記事は早くも古いものとなりました。詳細はMinGW+pthread2010.03?を参照。

mainとスレッドの前後処理(3)

前回までで、やっとプロセスとスレッドの開始時・終了時に処理を割り込ませる方法に見当がついた。理論重視とは言いつつも、結局実装の検証に過ぎない部分も多々あったことは否めないが、とにかくもなんとか納得してコードが書けそうだ。

 サンプルコード

TLSコールバックはプロセス・スレッドの開始・終了を捉えるので、これ一つで事足りる。そしてその為のコードは以下のようなものだ。確認はMinGW猫科研究所パックa004で行った。GCCは4.4.1-tdm-2だ。

#include <windows.h>
#include <winnt.h>
static NTAPI void flpthread_tls_callback(PVOID hinstDLL, DWORD fdwReason, PVOID lpvReserved){
    switch(fdwReason){
    case DLL_PROCESS_ATTACH:
        puts("-> DLL_PROCESS_ATTACH");
        break;
    case DLL_PROCESS_DETACH:
        puts("-> DLL_PROCESS_DETACH");
        break;
    case DLL_THREAD_ATTACH:
        puts("-> DLL_THREAD_ATTACH");
        break;
    case DLL_THREAD_DETACH:
        puts("-> DLL_THREAD_DETACH");
        break;
    default:
        puts("-> UNKNOWN");
        break;
    }
}

static void*               flpthread_xla __attribute__((section(".CRT$XLA")))       = 0;
static PIMAGE_TLS_CALLBACK flpthread_xly __attribute__((section(".CRT$XLY"), used)) = flpthread_tls_callback;
static void*               flpthread_xlz __attribute__((section(".CRT$XLZ"), used)) = 0;

static DWORD  flpthread_tls_data  __attribute__((section(".tls"))) = 0;
static HANDLE flpthread_tls_index                                  = NULL;

const IMAGE_TLS_DIRECTORY _tls_used __attribute__((section(".rdata$T"))) =
{
    (DWORD)&flpthread_tls_data,
    (DWORD)&flpthread_tls_data,
    (DWORD)&flpthread_tls_index,
    (DWORD)(&flpthread_xla+1),
    (DWORD)0, // unused (not supported)
    (DWORD)0
};

あとは煮るなり焼くなり、好きにすればよい。

プロセス開始時・終了時のみ

スレッド側の処理が不要な場合は、より簡単な方法を取ることができるので、例示しておく。当初の目的が上記のTLS-Callbackで上手く行ってしまったため、あまりしっかりとは検証していないので注意。

まず、繰り返しになるが、標準C++の範囲で行う場合。

class Foo {
public:
    Foo(){
        cout << "cpp constructor!" << endl;
    };
    ~Foo(){
        cout << "cpp destructor!" << endl;
    };
};

Foo foo;

先に書いた通り、グローバル変数のコンストラクタ・デストラクタを利用する。

次に、GCCの機能を使用する場合。

static void c_ctors(){
    puts("c_ctors!");
}
static void c_dtors(){
    puts("c_dtors!");
}
void* startup     __attribute__((section(".ctors"))) = c_ctors;
void* termination __attribute__((section(".dtors"))) = c_dtors;

MinGWは.ctorsと.dtorsを適切にPEのセクションに割り振ってくれるようだ。

しかし、同じGCCのattribute機能でも、以下のコードは動作しなかった。

static void c_ctors(){
    puts("c_ctors!");
}
static void c_dtors(){
    puts("c_dtors!");
}
void* startup     __attribute__((constructor)) = c_ctors;
void* termination __attribute__((destructor))  = c_dtors;

理由は良く分からないが、"warning: 'constructor' attribute ignored"と言われる。個人的には、ELFの機能である.ctorsと.dtorsよりも、こちらに対応すべきだと思うのだが。

…と書いていたら、別の猫研メンバから一言、「それって変数じゃなく関数のattributeなんじゃない?」と指摘された。………おぉぉ、その通りだ(汗

__attribute__((constructor)) static void c_ctors()  {
	puts("c_ctors!");
}
__attribute__((destructor)) static void c_dtors()  {
	puts("c_dtors!");
}

で無事通る。「セクション+関数ポインタ」に気を取られすぎてその形式しか頭になかったが、一般的に言えば関数のattributeであるのは当然だ。間違っているのは大抵人間の方だというのを体現してしまった。

最後に、以下のように直接セクションに配置するコードも動作しなかった。

static void c_ctors(){
    puts("c_ctors!");
}
static void c_dtors(){
    puts("c_dtors!");
}
void* xi_a __attribute__((section(".CRT$XIA"))) = 0;
void* xi_b __attribute__((section(".CRT$XIB"))) = c_ctors;
void* xi_z __attribute__((section(".CRT$XIZ"))) = 0;
void* xt_a __attribute__((section(".CRT$XTA"))) = 0;
void* xt_b __attribute__((section(".CRT$XTB"))) = c_dtors;
void* xt_z __attribute__((section(".CRT$XTZ"))) = 0;

こちらに関しては動作しない理由が全く分からない。何か間違っているのだろうが、詳しく検証してもいない。いずれにしても綺麗な方法ではないので、使うべきではないと思う。

 リンク集

今回の件では、かなりのサイトを見て回った。その一部のリンクをここに置いておく。

gccとELF

VC(Visual C++)とMicrosoftのCOFF/PE(Portable Executable)

このほか、boostのMLで非常に多くの議論が交わされていて読みきれないほど。「Windows MSVC thread exit handler for staticly linked Boost.Thread」で検索すればすぐ見つかる。たとえばコードの例示がなされてる。

gccとVCのハイブリッドな解説

  • C++ - OSDev Wiki
    • ctors/dtors, .CRT$X??, #pragma init_seg等の解説がなされている。
    • ItaniumのABIとして触れられている?

pthreads-w32関連

その他

この他、マイクロソフトのサイトにVCの標準ライブラリのソースらしきものがあった。が、EULAの関係で見ない方が良さそうなのでここでは表示しない。今回の記事でもその内容に触れない範囲にしているし、MinGWでTLSコールバックさえ扱えれば必要ないだろう。


この記事はmainとスレッドの前後処理(2)の続きです。

最終更新時間:2010年02月18日 21時24分59秒