2008年10月26日日曜日

ウィンドウプロシージャとメンバ関数

コールバック関数の1つウィンドウプロシージャWindowProc()をクラスメンバ関数にしたいという話。
調べたところ、ATLはlinked listでクラスインスタンスを管理しておき、atlbase.inlファイルのAtlWinModuleExtractCreateWndData()関数でthread idをキーにして検索をしていました。これウィンドウメッセージが届くたびにやってる気がします。
読んで勉強になったのは、CreateWindowを呼び出したスレッドにウィンドウメッセージが届くのね。だからthread idがキーとして使える、と。

なら1歩進めてthread local storageに情報を置いてしまえばいいのでは?

class Window{
LRESULT WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam,LPARAM lParam ){
// ここが本体
...;
}
static __declspec(thread) Window* window;
static LRESULT CALLBACK StaticWindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ){
return window->WindowProc( hwnd, uMsg, wParam, lParam );
}
void CreateWindowOnThisThread(){
Window::window = this;
CreateWindowEx( .../* 適切に */ );
}
};
Window* Window::window = NULL;

こうすれば無難な関数に仕上がりました。ちなみにStaticWindowProc()はかなり負担の少ない実装になっていました。
mov eax, DWORD PTR _lParam$[esp-4]
mov ecx, DWORD PTR _wParam$[esp-4]
mov edx, DWORD PTR fs:__tls_array
push ebx
mov ebx, DWORD PTR _hwnd$[esp]
push eax
mov eax, DWORD PTR _uMsg$[esp+4]
push ecx
mov ecx, DWORD PTR [edx]
mov ecx, DWORD PTR Window::window[ecx]
call Window::WindowProc
pop ebx
ret 16
; 一部構文に矛盾がありますが、読みやすさのためのデマングルですので…
呼び出し規約がCALLBACKから__thiscallに変わるので、無意味なスタックの積み直しが行われてますが、まぁ仕方がありませんね。

2008年10月22日水曜日

DateTimeと時差情報

DateTime構造体は2.0からKind情報を持つようになり、UTCとローカル時間を区別できるようになりました。これによってどんなことが起きるかというと…

DateTime now = DateTime.Now;
DateTime utcNow = now.ToUniversalTime();
Console.WriteLine( "now {0} utcNow", now == utcNow ? "==" : "!=" );
// => now != utcNow
ダメじゃん。epoch秒に慣れてる人は確実にはまりますね。
MSDNを読むと…あー書いてある書いてある
DateTime オブジェクトの計算および比較では、対象となる複数のオブジェクトが同じタイム ゾーンの時刻を表している場合にのみ、意味のある結果を得ることができます。
裏返せば、タイムゾーンが違ったら意味がない…。

この場合どうすればいいかというと、DateTimeOffset構造体を使うそうです。ただし、こちらは2.0SP1から。

2008年10月21日火曜日

Data Entity FrameworkとSQL Server Compact 3.5 SP1

Visual Studio 2008 SP1の新機能Data Entity Frameworkを使ってみたくなりました。ついでにずっと気になっていたSQL Server Compact 3.5 SP1と組み合わせて試してみることにしました。
SQL Server Compactはプロセス内で動作する軽量なSQLエンジンです。SQLiteみたいなものと言えばわかりやすいでしょうか。

Wizardに従って作っていきいざ実行すると…

ハンドルされていない例外: System.ArgumentException: 指定されたストア プロバイダが構成内に見つからないか、無効です。
---> System.ArgumentException: 要求された .Net Framework データ プロバイダが見つかりません。これは、インストールされていない可能性があります。
何を言っているのかわからない例外になります。
「指定されたストア プロバイダ」とか言われてもWizardの生成したデフォルトそのままだし。「データ プロバイダが見つかりません」と言われてもサーバエクスプローラでもWizardでも表示できてるし…。

いろいろググりましたが見つかりません。ならばまずSQL Server Compact 3.5 SP1を単体で動かしてみましょう…
ハンドルされていない例外: System.DllNotFoundException: DLL 'sqlceme35.dll' を読み込めません: 指定されたモジュールが見つかりません。 (HRESULT からの例外: 0x8007007E)
見つからないってどういうこと?

ならばプライベート ファイル ベースの配置をしてみましょう…
ハンドルされていない例外: System.BadImageFormatException: 間違ったフォーマットのプログラムを読み込もうとしました。 (HRESULT からの例外: 0x8007000B)
…やっとっわかった気がする。

Microsoft SQL Server Compact 3.5 ReadmeSQL Server Compact 3.5 と Visual Studioに色々書いてありました。
  • SQL Server Compact 3.5 SP1 の 64 ビット リリースは、Microsoft ダウンロード センターからダウンロードできます (Web からの提供のみ)。
  • Visual Studio と SQL Server Compact 3.5 で 64 ビット開発を行う場合は、[コンパイラの詳細設定][ターゲット CPU] オプションを明示的に [x86] に設定する必要があります。

ええ、私はVista x64で開発してますよ…。Visual Studio自身が32bitで動作していることを忘れてましたよ。普通、開発環境なんだから64bit版もインストール済みと思うよっ!

2008年10月20日月曜日

ArraySegment classと拡張メソッド

C#で部分配列を扱いたくなったとき、unsafe fixedしてpointerを使うこともできますが、ArraySegment classでがんばってみようと思いました。ところがArraySegmentにはインデクサなどのアクセッサが用意されてなく扱いづらいです。

var array = new int[10];
var segment = new ArraySegment<int>( array, 5, 5 );
segment.Array[ segment.Offset + 3 ]; // array[8] 相当


ここで拡張メソッドを思い出しました。といっても拡張インデクサは実装できません。気を取り直してArray.GetValue()やArray.SetValue()の真似をしてみました。
public static class ArraySegmentUtility{
public static T GetValue<T>( this ArraySegment<T> segment, int index ){
return segment.Array[ segment.Offset + index ];
}
public static void SetValue<T>( this ArraySegment<T> segment, T value, int index ){
segment.Array[ segment.Offset + index ] = value;
}
}
こうすれば最初の例は
segment.GetValue( 3 );
おおいい感じ。
早速使ってみよう…
segment.Array[ segment.Offset + i ] ^= x;
ぽか~ん。アクセッサが用意されてない理由がわかった気がする…。

2008年10月19日日曜日

演算子の優先順位

自分でも呆れるくらいのミスを…
演算子の優先順位を勘違いしてました。
シフト演算<<や>>よりも先に二項演算子+や-が結合されるんですね。