初めてtomato playerのような割と本格的なソフトを作って、 例外処理はまじめに考えると頭が痛いということを痛感しました
メモリアロケーションエラーなど頻度が低くて、 しかも発生すると手の打ちようが無いエラーは無視(abort)しておけば良いんですが、 tomato playerはほとんどファイラなので、記録ドライブへのアクセスが多く 開こうとしたファイルが開けないなど、実際に良く起こりそうなエラーを扱う必要がありました
ファイラはファイルシステムを「見える化」したものと言えるでしょうが、 ファイルシステムとファイラは完璧に同期しているわけではないので 何をするにも実行時エラーが付きまといます
実行時エラーの例外をキャッチするのは簡単ですが、 そのあとが問題でして、処理を中断して全てを無かったことにしてプログラムの状態を元の状態に 正しくリカバリーするのは難しいです
そこで、処理を二つのセクションに分けることを考えます
実行時エラーが発生するかもしれない処理は前半部で行い 全ての成功を確認してから後半部でプログラム本体の状態に適用します
もし後半部の処理中に例外が飛んで来たらプログラム続行不可とみてabortします
しかしこの方法には弱点があって、どのように処理を行儀よく「前半」と「後半」に分けるかという事です
関数内で「前半」と「後半」に分けても上手くいきません
void sub1()
{
try
{
//前半部:失敗するかもしれない処理
}
catch(...)
{
//処理失敗
throw;
}
//後半部:前半部の処理を適用
}
void sub2()
{
try
{
//前半部:失敗するかもしれない処理
}
catch(...)
{
//処理失敗
throw;
}
//後半部:前半部の処理を適用
}
void func()
{
try
{
//前半部:失敗するかもしれない処理
sub1();
sub2();
}
catch(...)
{
//処理失敗
throw;
}
//後半部:前半部の処理を適用
}
このままではだめで、上記のようなコールグラフのプログラムで、sub1()が成功してsub2()が失敗した場合 既にsub1()は成功してプログラムを書き換えた後なので、sub1()の処理を無かったことにして プログラムの状態を元に戻すのはとても大変です
そこでsub1()、sub2()を前半と後半の二つに分解します
void sub1_pre (){ /*前半部:失敗するかもしれない処理*/ }
void sub1_main(){ /*後半部:前半部の処理を適用*/ }
void sub2_pre (){ /*前半部:失敗するかもしれない処理*/ }
void sub2_main(){ /*後半部:前半部の処理を適用*/ }
void func()
{
try
{
//前半部:失敗するかもしれない処理
sub1_pre();
sub2_pre();
}
catch(...)
{
//処理失敗
throw;
}
//後半部:前半部の処理を適用
sub1_main();
sub2_main();
}
ここで、各 _pre()と_main() の間で処理中のデータを受け渡す必要があるので構造体を定義してデータを受け渡すようにします
struct sub1_work_t{};
void sub1_pre ( sub1_work_t *w ){ /*前半部:失敗するかもしれない処理*/ }
void sub1_main( sub1_work_t *w ){ /*後半部:前半部の処理を適用*/ }
struct sub2_work_t{};
void sub2_pre ( sub2_work_t *w ){ /*前半部:失敗するかもしれない処理*/ }
void sub2_main( sub2_work_t *w ){ /*後半部:前半部の処理を適用*/ }
void func()
{
sub1_work_t w1
sub2_work_t w2;
try
{
//前半部:失敗するかもしれない処理
sub1_pre( &w1 );
sub2_pre( &w2 );
}
catch(...)
{
//処理失敗
throw;
}
//後半部:前半部の処理を適用
sub1_main( &w1 );
sub2_main( &w2 );
}
見てわかる通り、もしプログラム全体にわたってこのようなルールを適用すると あっという間にグチャグチャになります
tomato playerは全体で10万行以上もあるので確実に管理不能に陥ります
ところでタイトルの通り別の要求もあります
それは時間のかかる処理の実行中にGUIスレッドが固まるのは嫌だから非同期処理にしたい、というものです
何の工夫もなく非同期処理を書けば
となりますが、これだと、ひとまとまりの処理が複数の関数などに分散されて全体の流れが見にくくなります
また、データの受け渡しをなんらかの形で行う必要があります
ところで、時間のかかる処理中にGUIスレッドが固まらないように非同期処理にするまではいいですが、 複数の別の非同期処理が縦横無尽に同時に走ったらプログラムが壊れるので 非同期処理中は他の処理が走らないようにマウスやキーボードからの入力を受け付けないようにする・・・ というのでは同期処理と同じことなので、そうならないように処理のキャンセルを受け付けるようにしなければなりません
複数の非同期処理が走るのは危ないですが、前の処理をキャンセルしてから新たな処理を走らす分には問題ないわけです
ここで例外安全と非同期処理の二つのトピックをよく考えると、類似性が浮かび上がります
それは
というものです
そこで思い切ってコルーチンを導入します
あまりコルーチンは好きではないのですが、一つのカラクリで二つの別の問題が解決するなら仕方ありません
また、使ってみると(最初は混乱しましたが)ネイティブスレッドと違って明示的なタイミングでしかコンテキストスイッチしないので (ネイティブスレッドと比べて)制御しやすい一面もありました
最終的に大体このような感じに書けるるようにします
void sub1_async()
{
//前半部:失敗するかもしれない処理
//↓非同期処理:別スレッドに処理を投げて、すぐさま制御を返す
a_wait() << [&]
{
//別スレッドで実行
};
barrier();
//後半部:前半部の処理を適用
}
void sub2_async(){ barrier(); }
void func_async()
{
//前半部:失敗するかもしれない処理
a_sync() << [&] //←a_syncでコルーチン作成
{
sub1_async();
};
a_sync() << [&] //←a_syncでコルーチン作成
{
sub2_async();
};
a_wait() << [&]{}; //←非同期処理をしてみたり
barrier();
//後半部:前半部の処理を適用
}
int main()
{
a_sync() << [&]{ func_async(); };
main_thread_message_loop();
return 0;
}
コルーチンツリー内のだれかが実行時エラーかキャンセルすると、 全てのコルーチンツリー内のコルーチンでbarrier()以降が実行されないようになっています
barrierで止まって、正常終了が確定してから
一気に実行
ところで実行時エラーはファイルが存在しないなどですが、キャンセルとは何かを考えます
もともと複数の非同期処理がピンポンのように同時並行で走ると危険なので、 どちらか片方をキャンセルして、常に一つの処理しか走らないようにしたいという事でした
ユーザーにしても複数のタスクの同時実行を許したら自分が何を命令したのか混乱すると思われるのでこれでよいわけです
複数のコルーチンツリーが実行されようとしたとき、どちらか片方のコルーチンツリーをキャンセルしたいので、コルーチンツリーに優先順位を設けます
優先順位が高い方のコルーチンツリーが優先され、同じ優先順位の場合は後から作成されたコルーチンツリーの方を優先します
また、どちらのコルーチンツリーもキャンセルしたくない場合もあります
それは、全く関連性のない別々の処理なのに片方をキャンセルするのはロジックの上でおかしいという事なので コルーチンツリーに互いに全く関係ないことを示すためのグループ番号を設けます
違ったグループ番号同士では、キャンセルしあったりしないようにしますが、 違ったグループ番号といえ同時に走ったらまずいことには変わりないので、 先に走ってる別グループ番号のコルーチンツリーが終了するまで、後から実行しようとしているコルーチンの実行を保留させます
このように、時にはキャンセル、時には保留をし、同時に走るコルーチンツリーを常に一つに制限します
追記: 2018/08/20
プログラムを一部修正
今まではbarrier()以降を処理中も別スレッドでa_waitの処理が走っていたのを 安全面を考慮してa_waitの処理が終了するまでbarrier()の処理を保留するように修正
保留するといってもその間GUIスレッドが固まるというわけではないです
追記: 2018/08/21
プログラムのミスを修正
以下にサンプルプログラムをおいておきます
コルーチンにはWin32のファイバーを利用しています
tomato playerから整理せずに持ってきているので煩雑です
#include <exception>
#include <functional>
#include <vector>
#include <assert.h>
#include <windows.h>
#include <tchar.h>
#include <commctrl.h>
#include <process.h>
#pragma comment(lib, "comctl32.lib" )
struct cs_t
{
CRITICAL_SECTION cs;
cs_t()
{
::InitializeCriticalSection( &cs );
return;
}
~cs_t()
{
::DeleteCriticalSection( &cs );
return;
}
};
struct syncer_t
{
cs_t *cs = nullptr;
syncer_t( cs_t *cs_ )
{
cs = cs_;
::EnterCriticalSection( &cs->cs );
return;
}
~syncer_t()
{
::LeaveCriticalSection( &cs->cs );
return;
}
};
__declspec( thread ) void *g_fiber = nullptr;
static const int FB_GROUP_1 = ( 1 << 0 );
static const int FB_GROUP_2 = ( 1 << 1 );
struct cancel_t :public std::exception{};
struct failed_t :public std::exception{};
struct fiber_t
{
void *fiber = nullptr;
bool is_using = false;
bool is_running = false;
bool is_finished = false;
bool is_cancel = false;
bool is_barrier = false;
bool do_barrier = false;
void *ret_fiber = nullptr;
unsigned group = 0;
int priority = 0;
bool is_sync = false;
int seq = 0;
std::exception_ptr
ep;
DWORD a_wait_thread_id = 0;
bool is_a_waiting = false;
fiber_t *next = nullptr;
fiber_t *prev = nullptr;
std::function a_sync_func;
std::function a_wait_func;
fiber_t()
{
fiber = ::CreateFiber( 0, proc, this );
get_window();
return;
}
~fiber_t()
{
if( fiber )
{
::DeleteFiber( fiber );
}
return;
}
fiber_t *head()
{
auto f = this;
for( ; f->prev; f = f->prev );
return f;
}
fiber_t *tail()
{
auto f = this;
for( ; f->next; f = f->next );
return f;
}
void add( fiber_t *fi )
{
auto fi_head = fi->head();
auto fi_tail = fi->tail();
fi_head->prev = prev;
fi_tail->next = this;
if( prev ){ prev->next = fi_head; }
prev = fi_tail;
return;
}
static void barrier()
{
assert( ::IsThreadAFiber() );
auto f = (fiber_t *)::GetFiberData();
if( f->is_sync )
{
return;
}
if( f->do_barrier ){ return; }
if( !f->is_cancel )
{
if( !cancel_other_a_sync( f->group, false ) )
{
cancel_a_sync();
}
}
f->do_barrier = true;
if( !f->next && !f->prev ){ return; }
f->is_barrier = true;
auto h = f->head();
auto check = [&]() -> bool
{
for( auto ff = h; ff; ff = ff->next )
{
if( !( ff->is_barrier || ff->is_finished ) ){ return false; }
}
return true;
};
if( check() )
{
::PostMessage( get_window(), WM_APP, 0, 0 );
}
yield_return();
f->is_barrier = false;
if( f->is_cancel )
{
f->is_cancel = false;
if( f->ep )
{
std::rethrow_exception( f->ep );
}
else
{
throw cancel_t();
}
}
return;
}
static bool cancel_other_a_sync( unsigned group_mask = FB_GROUP_1, int priority = 0 )
{
fiber_t *fiber = nullptr;
if( ::IsThreadAFiber() )
{
fiber = (fiber_t *)::GetFiberData();
}
for( auto f: *fiber_t::fibers() )
{
if( !f->is_using ){ continue; }
if( fiber && ( f->seq == fiber->seq ) ){ continue; }
if( !( group_mask & f->group ) ){ continue; }
if( priority < f->priority )
{
return false;
}
}
for( auto f: *fiber_t::fibers() )
{
if( !f->is_using ){ continue; }
if( fiber && ( f->seq == fiber->seq ) ){ continue; }
if( !( group_mask & f->group ) ){ continue; }
if( priority < f->priority ){ continue; }
f->is_cancel = true;
}
return true;
}
static void cancel_a_wait()
{
assert( g_fiber );
throw cancel_t();
return;
}
static void cancel_a_sync()
{
assert( ::IsThreadAFiber() );
assert( g_fiber );
auto f = (fiber_t *)::GetFiberData();
assert( !f->do_barrier );
throw cancel_t();
return;
}
static void failed_a_wait()
{
assert( !::IsThreadAFiber() );
assert( g_fiber );
throw failed_t();
return;
}
static void failed_a_sync()
{
assert( ::IsThreadAFiber() );
assert( g_fiber );
auto f = (fiber_t *)::GetFiberData();
assert( !f->do_barrier );
throw failed_t();
return;
}
static void yield_return()
{
assert( ::IsThreadAFiber() );
auto f = (fiber_t *)::GetFiberData();
if( f->ret_fiber )
{
::SwitchToFiber( f->ret_fiber );
}
return;
}
static void switch_to_fiber( fiber_t *f )
{
assert( f->fiber );
fiber_t *from = nullptr;
if( ::IsThreadAFiber() )
{
from = (fiber_t *)::GetFiberData();
}
if( from )
{
f->ret_fiber = from->fiber;
}
else
{
f->ret_fiber = ::ConvertThreadToFiber( nullptr );
}
g_fiber = (void *)f;
::SwitchToFiber( f->fiber );
g_fiber = (void *)from;
if( !from )
{
::ConvertFiberToThread();
}
return;
}
void on_proc()
{
for( ; ; )
{
is_barrier = false;
do_barrier = false;
is_finished = false;
ep = nullptr;
try
{
if( !is_cancel )
{
a_sync_func();
}
}
catch( cancel_t )
{
is_cancel = true;
}
catch( ... )
{
assert( !do_barrier );
is_cancel = true;
}
if( is_cancel )
{
for( auto f = this; f; f = f->prev ){ f->is_cancel = true; }
for( auto f = this; f; f = f->next ){ f->is_cancel = true; }
}
if( next || prev )
{
if( !do_barrier )
{
try
{
barrier();
}
catch( ... )
{
}
}
}
if( next )
{
next->prev = prev;
next = nullptr;
}
if( prev )
{
prev->next = next;
prev = nullptr;
}
is_finished = true;
is_using = false;
is_running = false;
::SwitchToFiber( ret_fiber );
}
return;
}
void static CALLBACK proc( void *inst )
{
return ( (fiber_t *)inst )->on_proc();
}
static std::vector< fiber_t * > *fibers()
{
struct inst_t
{
std::vector< fiber_t * > fibers;
~inst_t()
{
for( auto f: fibers )
{
delete f;
}
return;
}
};
static inst_t inst;
return &inst.fibers;
}
static fiber_t *get_fiber()
{
syncer_t sync( get_cs() );
for( auto f: *fibers() )
{
if( f->is_using ){ continue; }
if( f->next || f->prev ){ continue; }
f->is_using = true;
return f;
}
auto f = new fiber_t();
fibers()->push_back( f );
f->is_using = true;
return f;
}
static cs_t *get_cs()
{
static cs_t cs;
return &cs;
}
static HWND get_window()
{
struct window_t
{
HWND handle = nullptr;
window_t()
{
WNDCLASS winc;
winc.style = CS_HREDRAW | CS_VREDRAW;
winc.lpfnWndProc = ::DefWindowProc;
winc.cbClsExtra = 0;
winc.cbWndExtra = 0;
winc.hInstance = ::GetModuleHandle( nullptr );
winc.hIcon = nullptr;
winc.hCursor = LoadCursor( nullptr, IDC_ARROW );
winc.hbrBackground = (HBRUSH)GetStockObject( NULL_BRUSH );
winc.lpszMenuName = nullptr;
winc.lpszClassName = L"fiber_window_t";
::RegisterClass( &winc );
handle = ::CreateWindow(
L"fiber_window_t", L"fiber_window",
WS_OVERLAPPEDWINDOW,
0, 0,
1, 1,
nullptr, nullptr,
::GetModuleHandle( nullptr ), nullptr
);
::SetWindowSubclass( handle, subclass_proc, 1, (DWORD_PTR)this );
return;
}
LRESULT on_message( UINT msg, WPARAM wp, LPARAM lp )
{
switch( msg )
{
case WM_APP:
{
for( auto f: *fiber_t::fibers() )
{
if( f->is_a_waiting )
{
return 0;
}
}
auto func = [&]( fiber_t *head ) -> void
{
for( auto f = head; f; f = f->next )
{
if( !( f->do_barrier || f->is_finished ) ){ return; }
}
bool is_cancel = false;
for( auto f = head; f; f = f->next )
{
is_cancel |= f->is_cancel;
}
for( auto f = head; f; f = f->next )
{
if( is_cancel ){ f->is_cancel = true; }
}
for( auto f = head; f; )
{
auto next = f->next;
if( f->is_barrier )
{
switch_to_fiber( f );
}
f = next;
}
};
for( auto f: *fiber_t::fibers() )
{
if( !f->is_using ){ continue; }
if( f->prev ){ continue; }
func( f );
}
break;
}
case WM_APP + 1:
{
fiber_t::switch_to_fiber( (fiber_t *)lp );
break;
}
default:
break;
}
return ::DefSubclassProc( handle, msg, wp, lp );
}
static LRESULT CALLBACK subclass_proc(
HWND wnd,
UINT msg,
WPARAM wp,
LPARAM lp,
UINT_PTR subclass_id,
DWORD_PTR inst
)
{
return ((window_t *)inst)->on_message( msg, wp, lp );
}
};
static window_t window;
return window.handle;
}
};
static int message_loop()
{
MSG msg;
for( ; 0<::GetMessage( &msg, nullptr, 0, 0 ); )
{
{
::TranslateMessage( &msg );
::DispatchMessage( &msg );
}
}
return (int)msg.wParam;
}
static void message_loop( std::function< bool(void) > func )
{
MSG msg;
for( ; 0<::GetMessage( &msg, nullptr, 0, 0 ); )
{
{
::TranslateMessage( &msg );
::DispatchMessage( &msg );
}
if( func() ){ break; }
}
if( WM_QUIT == msg.message )
{
::PostQuitMessage( (int)msg.wParam );
}
return;
}
struct a_sync
{
unsigned group = FB_GROUP_1;
int priority = 0;
bool is_sync = false;
a_sync( unsigned group_ = FB_GROUP_1, int priority_ = 0, bool is_sync_ = false )
{
group = group_;
priority = priority_;
is_sync = is_sync_;
return;
}
void operator <<( std::function func )
{
static int seq = 1;
auto f = fiber_t::get_fiber();
f->is_cancel = false;
f->next = nullptr;
f->prev = nullptr;
f->ep = nullptr;
f->a_sync_func = func;
f->group = group;
f->priority = priority;
f->is_sync = is_sync;
if( g_fiber )
{
auto ff = (fiber_t *)g_fiber;
assert( !ff->do_barrier );
f->seq = ff->seq;
f->group = ff->group;
f->is_sync = ff->is_sync;
f->priority = ff->priority;
ff->add( f );
}
else
{
++seq;
f->seq = seq;
}
auto check = [&]() -> bool
{
syncer_t sync( fiber_t::get_cs() );
if( f->is_cancel )
{
f->is_running = true;
return true;
}
auto fs = fiber_t::fibers();
for( auto ff: *fs )
{
if( !ff->is_using ){ continue; }
if( !ff->is_running ){ continue; }
if( f->group != ff->group ){ return false; }
}
f->is_running = true;
return true;
};
if( !check() )
{
assert( !::IsThreadAFiber() );
auto old_fiber = g_fiber;
g_fiber = nullptr;
message_loop( [&]() -> bool{ return check(); } );
g_fiber = old_fiber;
}
fiber_t::switch_to_fiber( f );
return;
}
};
struct a_wait
{
void operator << ( std::function func )
{
if( !::IsThreadAFiber() )
{
func();
return;
}
auto fiber = (fiber_t *)::GetFiberData();
assert( fiber );
if( fiber->is_sync )
{
func();
return;
}
assert( !fiber->do_barrier );
auto ret = fiber_t::cancel_other_a_sync( fiber->group, fiber->priority );
if( !ret )
{
fiber_t::cancel_a_sync();
}
fiber->a_wait_func = func;
fiber->is_cancel = false;
fiber->is_a_waiting = true;
//↓デモなので手抜き
//↓ちゃんとしたスレッドプールにしたほうが良いです
unsigned id;
auto thread = (HANDLE)::_beginthreadex( nullptr, 0, thread_main, fiber, 0, &id );
fiber_t::yield_return();
::CloseHandle( thread );
if( fiber->is_cancel )
{
fiber->is_cancel = false;
if( fiber->ep )
{
std::rethrow_exception( fiber->ep );
fiber->ep = nullptr;
}
else
{
throw cancel_t();
}
}
return;
}
static unsigned __stdcall thread_main( void *data )
{
auto fiber = (fiber_t *)data;
g_fiber = fiber;
try
{
fiber->a_wait_func();
fiber->is_a_waiting = false;
}
catch( ... )
{
fiber->is_cancel = true;
fiber->is_a_waiting = false;
fiber->ep = std::current_exception();
}
::PostMessage( fiber_t::get_window(), WM_APP + 1, 0, (LPARAM)fiber );
::PostMessage( fiber_t::get_window(), WM_APP, 0, 0 );
return 0;
}
};
HWND g_window = nullptr;
HWND g_log_window = nullptr;
void output_log( const wchar_t *text )
{
int len = ::GetWindowTextLength( g_log_window );
::SendMessage( g_log_window, EM_SETSEL, len, len );
::SendMessage( g_log_window, EM_REPLACESEL, FALSE, (LPARAM)text );
::SendMessage( g_log_window, EM_REPLACESEL, FALSE, (LPARAM)L"\r\n" );
return;
}
LRESULT WINAPI main_window_proc( HWND wnd, UINT msg, WPARAM wp, LPARAM lp )
{
switch( msg )
{
case WM_CLOSE:
{
::PostQuitMessage( 0 );
break;
}
case WM_COMMAND:
{
switch( LOWORD( wp ) )
{
case 100:
{
a_sync( FB_GROUP_1, 0 ) << [&]
{
output_log( L"starting task A" );
a_wait() << [&]{ ::Sleep( 5000 ); };
fiber_t::barrier();
output_log( L"finished task A" );
};
break;
}
case 101:
{
a_sync( FB_GROUP_1, 0 ) << [&]
{
output_log( L"starting task B" );
a_sync() << [&]
{
output_log( L"starting task BB" );
a_wait() << [&]{ ::Sleep( 1000 ); };
fiber_t::barrier();
output_log( L"finished task BB" );
};
a_wait() << [&]{ ::Sleep( 5000 ); };
fiber_t::barrier();
output_log( L"finished task B" );
};
break;
}
case 102:
{
a_sync( FB_GROUP_1, 1 ) << [&]
{
output_log( L"starting task C" );
a_wait() << [&]{ ::Sleep( 2000 ); };
fiber_t::barrier();
output_log( L"finished task C" );
};
break;
}
case 200:
{
a_sync( FB_GROUP_2, 0 ) << [&]
{
output_log( L"starting task D" );
a_wait() << [&]{ ::Sleep( 1000 ); };
fiber_t::barrier();
output_log( L"finished task D" );
};
break;
}
case 201:
{
a_sync( FB_GROUP_2, 0 ) << [&]
{
output_log( L"starting task E" );
a_sync() << [&]
{
output_log( L"starting task EE" );
a_sync() << [&]
{
output_log( L"starting task EEE" );
fiber_t::barrier();
output_log( L"finished task EEE" );
};
a_wait() << [&]{ ::Sleep( 1000 ); };
fiber_t::barrier();
output_log( L"finished task EE" );
};
a_sync() << [&]
{
output_log( L"starting task EE2" );
fiber_t::barrier();
output_log( L"finished task EE2" );
};
a_wait() << [&]{ ::Sleep( 2000 ); };
fiber_t::barrier();
output_log( L"finished task E" );
};
break;
}
case 202:
{
a_sync( FB_GROUP_2, 1 ) << [&]
{
output_log( L"starting task F" );
a_wait() << [&]{ ::Sleep( 1000 ); };
fiber_t::barrier();
output_log( L"finished task F" );
};
break;
}
default:
break;
}
break;
}
default:
break;
}
return ::DefWindowProc( wnd, msg, wp, lp );
}
int WINAPI _tWinMain(
HINSTANCE inst, HINSTANCE prev_inst,
wchar_t *cmd_line, int show_cmd )
{
WNDCLASS winc;
winc.style = CS_HREDRAW | CS_VREDRAW;
winc.lpfnWndProc = main_window_proc;
winc.cbClsExtra = 0;
winc.cbWndExtra = 0;
winc.hInstance = ::GetModuleHandle( nullptr );
winc.hIcon = nullptr;
winc.hCursor = LoadCursor( nullptr, IDC_ARROW );
winc.hbrBackground = (HBRUSH)( COLOR_3DFACE + 1 );
winc.lpszMenuName = nullptr;
winc.lpszClassName = L"main_window_t";
::RegisterClass( &winc );
DWORD style = WS_OVERLAPPEDWINDOW;
RECT rect = { 0, 0, 800, 600, };
::AdjustWindowRect( &rect, style, FALSE );
g_window = ::CreateWindow(
L"main_window_t", L"except_and_async",
style,
CW_USEDEFAULT, CW_USEDEFAULT,
rect.right - rect.left, rect.bottom - rect.top,
nullptr, nullptr,
::GetModuleHandle( nullptr ), nullptr
);
::CreateWindow( L"STATIC", L"group: 1", WS_VISIBLE|WS_CHILD,
100, 0, 100, 30, g_window, nullptr, ::GetModuleHandle( nullptr ), 0 );
for( int i = 0; i < 3; ++i )
{
wchar_t tmp[ MAX_PATH ] = {};
::swprintf_s( tmp, MAX_PATH, L"task %c, pri: %d", 'A' + i, i / 2 );
::CreateWindow( L"BUTTON", tmp, WS_VISIBLE|WS_CHILD,
100, i * 50 + 50, 150, 30,
g_window, (HMENU)( 100 + i ), ::GetModuleHandle( nullptr ), 0 );
}
::CreateWindow( L"STATIC", L"group: 2", WS_VISIBLE|WS_CHILD,
300, 0, 100, 30, g_window, nullptr, ::GetModuleHandle( nullptr ), 0 );
for( int i = 0; i < 3; ++i )
{
wchar_t tmp[ MAX_PATH ] = {};
::swprintf_s( tmp, MAX_PATH, L"task %c, pri: %d", 'D' + i, i / 2 );
::CreateWindow( L"BUTTON", tmp, WS_VISIBLE|WS_CHILD,
300, i * 50 + 50, 150, 30,
g_window, (HMENU)( 200 + i ), ::GetModuleHandle( nullptr ), 0 );
}
g_log_window = ::CreateWindow( L"EDIT", L"",
WS_VISIBLE|WS_CHILD|ES_AUTOHSCROLL|ES_AUTOVSCROLL|ES_MULTILINE|ES_READONLY,
500, 0, 200, 600, g_window, nullptr, ::GetModuleHandle( nullptr ), 0 );
::ShowWindow( g_window, SW_SHOW );
message_loop();
return 0;
}