2008年11月29日土曜日

PtrToStructureArray(): 配列のマーシャリング

Marshal.PtrToStructure()を使うと、メモリの内容を元に構造体などを復元することができます。これでC言語などとデータをやりとりできるようになります。
構造体の配列に関してもMarshalAs属性を使用すれば扱えますが、この場合、コンパイル時にサイズを決定した固定長配列に限られます。
そこで、実行時にサイズを決定できるPtrToStructureArray()を作ってみました。どれほど使い道があるやら。

public class Marshal{
static ModuleBuilder moduleBuilder;
static ModuleBuilder ModuleBuilder {
get {
if( moduleBuilder == null ) {
var name = new AssemblyName( "Sayuri.Dynamic" );
var ab = AppDomain.CurrentDomain.DefineDynamicAssembly( name, AssemblyBuilderAccess.Run );
moduleBuilder = ab.DefineDynamicModule( name.Name );
}
return moduleBuilder;
}
}
public static T[] PtrToStructureArray<T>( IntPtr ptr, int size ) {
var type = typeof(T);
var typeName = string.Format( "{0}Array{1}", type.Name, size );
var arrayType = ModuleBuilder.GetType( typeName );
if( arrayType == null ){
var tb = ModuleBuilder.DefineType( typeName, TypeAttributes.SequentialLayout );
var fb = tb.DefineField( "array", type.MakeArrayType(), FieldAttributes.Public|FieldAttributes.HasFieldMarshal );
var ctype = typeof( MarshalAsAttribute );
var cb = new CustomAttributeBuilder( ctype.GetConstructor( new[] { typeof( UnmanagedType ) } ),
new object[] { UnmanagedType.ByValArray }, new[] { ctype.GetField( "SizeConst" ) }, new object[] { size } );
fb.SetCustomAttribute( cb );
arrayType = tb.CreateType();
}
var obj = SystemMarshal.PtrToStructure( ptr, arrayType );
var fi = arrayType.GetField( "array" );
return (T[])fi.GetValue( obj );
}
}
やってることはT型とサイズ20が与えられた場合に
[StructLayout(LayoutKind.Sequential)]
class TArray20{
[MarshalAs(UnmanagedType.ByValArray, SizeConst=20)]
public T[] array;
}
というクラスをその場で生成して使っています。

2008年11月28日金曜日

IComparable<T>とその派生

以前、Dictionary<TKey, TValue>とIEquatable<T>で実行時エラーもなくハマる問題を書きましたが、今度は類似で実行時エラーでわかる問題がありました。

class Base: IComparable<Base>{
}
class Derived: Base{
}
とした場合、DerivedはIComparable<Base>を実装するため、通常の比較は行えます。しかしIComparable<Derived>は実装していないため、List<Derived>.Sort()などは行えません。

class Base<T>: IComparable<T> where T: Base{
}
class Derived: Base<Derived>{
}
とかやり出すか、素直にIComparableに切り替えるか。

2008年11月4日火曜日

swprintf()とwsprintf()

Cランタイムライブラリにsprintf()があります。同様の機能を持つwsprintf()がUSER32.dllにもあります。実はsprintf()はntdll.dllにもあります。

これらの関係を整理すると

宣言DLLTCHAR.HのルーチンANSI版Unicode版
tchar.hMSVCRT90.dll_stprintf()sprintf()swprintf()
windows.hUSER32.dllwsprintf()wsprintfA()wsprintfW()
-ntdll.dll-sprintf()swprintf()
となります。

ちなみに注意点がいくつか。
swprintf()の第2引数はバッファサイズで書式指定文字列は第3引数にずれています。他のものは第2引数が書式指定文字列です。代わりに他のものと互換のある_swprintf()も第2引数が書式指定文字列になっています。
wsprintf()は指定できる書式に制限があります。例えば浮動小数(%f)がサポートされていません。
ntdll.dllに含まれるsprintf()は使用方法が公開されてなく、サポートされている機能も公開されていません。ただし、全てのプロセスにロードされるDLLなので何かの役に立つかも。

2008年11月3日月曜日

CallNextHookEx()のHHOOK引数

SetWindowsHookEx( WH_CBT, CBTProc, ... )でDLL injectionしています。このCBTProc()のドキュメントによると、正確な型宣言は

LRESULT CALLBACK CBTProc( int nCode, WPARAM wParam, LPARAM lParam );
です。そしてこのhookプロシージャの中ではnCodeが負のときはCallNextHookEx()を呼び更にその戻り値を返すこととあります。

さてCallNextHookEx()のドキュメントによると、正確な型宣言は
LRESULT CallNextHookEx( HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam );
です。ちなみに記述が矛盾していて、CallNextHookEx()の呼び出しはoptionalだが、呼び出すことを強く推奨するそうです。CBTProc()側では負の時呼べばいいのに、こう書かれてしまうと0または正の時にも呼ばざるを得なくなります。仕方がないので呼ぶことにしましょう。

問題はここからです。CallNextHookEx()の第一引数hhk。hookプロシージャは進入した各プロセスで実行されます。にもかかわらずCBTProc()には存在しないパラメータが要求されています。とても面倒くさいですが、SetWindowsHookEx()の戻り値を共有メモリに格納しておき、それを読み出して使っていました。

ところが今日ふと気がついたら、
Windows NT/XP/2003: Ignored.
などと書かれているではありませんか。うーん…今まで共有メモリにわざわざ準備していたhhkは無駄でしたか…。

2008年11月1日土曜日

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

ウィンドウプロシージャとメンバ関数を書いてみたものの、1つのthreadに複数のWindow Procedureが混在する場合、うまく振り分けできませんね。
どうすればいいんだろう…? まぁ、複数Windowを扱ったことがないからいいけど…。