何気なくInterlockedCompareExchangeなるWinAPI関数を使っていたのですが、これWindows 98からですね。しまったという思いです。でもgrepしたら、2ヶ所でしか使っておらず、ほかの方法で代替できるので大きな問題にはなっていないと思います。ほっとしました。

これ、x86だと中身はどう考えてもCMPXCHG命令1個です。これは486で搭載されたので、API関数を通さずこの命令を直接使えればWindows 95でもいけるはずです。

ちなみに、Windows 95は日本語版が486以上が必要だったと聞いていますが、英語版だと386も対象に入っていたそうです。しかし、それくらい昔のCPUなので、もし自分がアプリケーションを作る側にいてCMPXCHGが欲しくなったら迷わず使います。

Windows APIでは、拡張された追加の関数には末尾にExがつくというのは法則といって過言でないと思います。さて、更に拡張したくなったら、こうなるようです。

  • EnumCalendarInfoExEx
  • EnumDateFormatsExEx
  • もちろん、EnumCalendarInfoとEnumCalendarInfoExがあって、今回VistaでEnumCalendarInfoExExが増えました。EnumDateFormatsExExも同じです。

    MSDNライブラリで引いてみると、無印が95/NT 3.5、Exが98/2000からだそうです。息の長い関数ですね。まあ、こんなんだから建て増しを重ねて複雑怪奇にすぎるだなんて言われても仕方ないわけですが。

    ウィンドウ周りからちょっと離れて、そうだ放置していたストリームをどうにかしようと思い立ってから、けりをつけた今日で2週間。しかもこれで終わらず、あと文字コード変換、せめてデフォルトCP(日本語だとCP932≒Shift_JIS)、UTF-8/16に対応させないと話になりません、というより自分が使う気になれません。

    そこで、MultiByteToWideCharの説明を見ていて思ったのが、入力文字列の不完全な部分を検出できたら良かったということです。例えば、”あいう(「え」の1バイト目)”という7バイトを与えると、6バイト変換して、余り1バイトと返してほしいです。これがファイルを読み込みつつ変換という状況なら、更に1バイト読み込み、さっきのと合わせて「え」が変換できるという具合です。実際のMultiByteToWideCharでは、1バイト余ったかどうかを教えてくれないので、別途自分で調べないといけません。

    元々はファイルから数KiBずつ読み込んだバイト列を丸ごとMultiByteToWideCharに渡して、最後に上で書いたようなマルチバイト文字が係る状況に遭遇したら、そこだけうまく対処してやればと考えていましたが、これだと1文字ずつ変換したほうが良さそうな気がします。

    Shift_JISが文字列先頭から見ていかないと1バイト/2バイトの区別がつけられないという点が不便ですね。ただ、それに依存したコードを書くと、今度は別の言語の文字コードで駄目だとなりそうなので、迂闊にはやれないですけど。

    Vistaには「タスクダイアログ」なる新種のダイアログの形態が登場しました。

    これには、TaskDialogとTaskDialogIndirectという2つの関数があるのですが、今回は簡単な方のTaskDialogを使ってみました。

    まあ大したことありません。MeesageBox/MsgBoxとあまり変わったことはできません。

    さて、こんなにMessageBoxと似たようなことしかできないのならと思い、暇だったのでTaskMsgが使えるときはそれを、そうでないときはMessageBoxを表示するなんて関数を作ってABライブラリに入れちゃいました。上記画像は、次のようなコードで表示できます。

    TaskMsg(hMainWnd, "タイトル", "メインの説明部分", "補助の説明部分", MB_OK, MB_ICONINFORMATION)

    名前のTaskMsgは想像通り、TaskDialogとMessageBoxのあいのこというわけです。”補助の説明部分”とそれより後ろは省略可能です。AB5の次のCPには搭載しているはずなのでよければ使ってやってください。


    TaskDialogIndirectのほうなら、MSDNライブラリのWindows Vitta > Wath’s New > Task Dialogsで紹介されているようにいろいろ出来るみたいです。まあ、Vistaからでないと使えないので、少なくとも今後数年は(少なくともXPまでの対応を考えないと)使えないでしょう。

    見えないクラス群 - こたつつきみかん

    逆に、.NET Frameworkでは中身を見せすぎなのではないかというきらいがあります。例えば、リストボックスの要素を読み書きするにはListBox.Itemsを使うのですが、こいつが返すのはListBox.ObjectCollectionというクラスだと記載されています。IListでは何がいけなかったのでしょうか。

    話は逸れますがさらに1つ別の例を挙げます。こっちは、.NET Framework 1.0にジェネリックがなかったなど多少は仕方ない面もあることですが。


    .NET Frameworkで、全てのファイルに対して何かを行うコードを書くとして、次のように書きたいとします。

    foreach fd in new FindFile("*.*")
    {
    	//fdを参照する
    }

    これをWindows APIのFindFirstFile, FindNextFile, FindCloseで実装することを考えます。こう書けるようにするためには、FindFileのコンストラクタか、GetEnumeratorでFindFirstFile、MoveNext(2回目以降)でFindNextFile、DisposeでFindCloseを呼ぶようにすれば実現できます。そのようなコードは、あたかも次のように直接API関数を使うの同じ構造になります。

    HFILE hFind = FindFirstFile("*.*", ref fd);
    while (FindNextFile(hFind, ref fd)
    {
    	//fdを参照する
    }
    FindClose(hFind);

    ところが、現実の.NET FrameworkのBCLの中でnew FindFile(”*.*”)の部分にあたるDirectory.GetFilesやGetDirectoriesなどは、配列を返します。つまり、直接APIを使うコードに展開すると次のようになります。

    List<WIN32_FIND_DATA> files;
    HFILE hFind = FindFirstFile("*.*", ref fd);
    while (FindNextFile(hFind, ref fd)
    {
    	files.Add(fd);
    }
    
    foreach fd in files
    {
    	//fdを参照する
    }

    このコードではバッファに蓄えるという構造に変形されてしまっています。もちろん、みんなが大多数の場合でバッファに蓄えるこういうコードを書いてきたというのなら、そういう風にクラスで包み込むのは適切かもしれません。しかし、最初の例ではFindFirstFileはforeachができれば十分です。そして、現実にもそういう場合が多々あったと思います。だから、Directory.GetFilesやGetDirectoriesはIEnumerableを返して、最初に挙げたようなバッファを使わない実装だったら良かったのに、と思っています。

    もちろん、バッファに蓄えたいときには必要に応じてそうできるようになっているのが望ましいです。幸いにも最新の.NET Framework 3.5にはEnumerable(IEnumerableに非ず)にToListとToArrayがあります。それぞれIEnumerableからListとT型の配列を作成してそれを返す拡張メソッドです。

    なお、この文章においてIEnumerableとT型の配列の違いは、[]演算子でインデックスを指定して要素アクセスできるかどうかだと思って差し支えありません。

    もちろん、これから作っていくABライブラリでは同じ轍を踏まないようにするぞと思っているわけです、少なくとも自分の書くコードでは。

    文字コード変換に関係するAPI・ライブラリを並べておきます。パッと思いついたのだけですが。

    WideCharToMultiByte/MultiByteToWideChar

    CP_ACPを指定してA関数が扱うマルチバイト文字列とW関数が扱うワイド文字列の相互変換によく使う。
    IMultiLanguage
    要IE4以上。ConvertString*メンバ関数が変換に関するもの。IEが使うだけあってEUC-JPなども使える。
    iconv
    Unix系という印象(Windows DLL版あり)。
    ICU
    そもそもABから使えるのか?対応言語にCじゃなくてC++(とJava)というあたりが懸念。
    nkf32
    日本語限定に絞ってシンプル。

    高水準ファイル入出力ともなれば当たり前のようにバッファリングが行われます。

    つまりバッファの内容とファイルとの間で読み書きするということです。これを非同期IO(もしくは単に別スレッドでも)で行うという話はあまり聞かないのですがなんででしょう。

    入力バッファが空になってからではなく、少なくなったら。出力バッファが満杯になってからではなく、満杯に近づいたら。そういう時点になったら裏で次のバッファの用意を開始する用にするという具合です。理想的に機能すれば、途中のファイル読み書きにかかる時間が見かけ上0になってしまうはずなんですけど。そう上手くはいかないということでしょうか。

    Vistaでは、従来のGetOpenFileName/GetSaveFileNameに代わってFile Dialogというものが導入されています。せっかくなので試してみました。

    #console
    
    Function GetSaveFileDialog() As String
    	Dim fd As IFileDialog
    	Dim hr = CoCreateInstance(
    		CLSID_FileSaveDialog, 0, CLSCTX_ALL,
    		IID_IFileDialog, fd)
    	If FAILED(hr) Then Exit Function
    
    	Try
    		hr = fd.Show(0)
    		If FAILED(hr) Then Exit Function
    		Dim si As IShellItem
    		hr = fd.GetResult(si)
    		If FAILED(hr) Then Exit Function
    		Dim psz As PWSTR
    		hr = si.GetDisplayName(SIGDN_FILESYSPATH, psz)
    		If FAILED(hr) Then Exit Function
    		GetSaveFileDialog = New String(psz)
    	Finally
    		fd.Release()
    	End Try
    End Function
    
    CoInitialize(0)
    Print GetSaveFileDialog()
    System.Console.ReadLine()
    CoUninitialize()

    ファイルダイアログ(保存)とりあえず名前を付けて保存ダイアログを表示し、入力されたファイル名をコンソールにPrintしているだけです。

    さて、ここで1つ重要なお知らせがあります。ABプログラムでは、通常IFileDialogを使う必要はありません。GetOpenFileName/GetSaveFileNameを直接呼ぶので、そのままでFile Dialogと同じ外観になります。なお、OFN_ENABLEHOOKを使うとだめになるので、そのときがやっとIFileDialogの出番です。

    もしかしたら、そのうちGetOpenFileName/GetSaveFileNameをラップしたクラスを作るかもしれませんが、その際もVistaで新スタイルになるよう留意して製作します。もしダメだったら、Vistaではファイルダイアログを使うというコードを書きますけどね。

    最後にこれに必要な宣言類を置いておきます。

    (more…)

    IME関連のメモです。使うときが来るかどうかは分かりませんが念のためです。

    Japanese Microsoft IME Specification - Microsoft IME Application Interface
    MS-IME固有のAPI。MSDN Library左側のツリーから辿れなくなっています。
    IME Share
    こっちはツリーから辿れます。MSDNライブラリ for VS2008ではIME Share Exported Functionsというタイトルです。MS-IME以外のIMEでも使えそうな名前をしていますがどうなんでしょう。

    こうしてURLだけ残していても、そのうちNot foundになっていそうで怖いです。

    忘れないうちにメモです。 題目のとおりのことについて調べると、 よく見かけるのが「http://www.microsoft.com/japan/support/kb/articles/J041/6/32.htmを見ろ」という記述です。しかし、そのURLにアクセスすると404のようでサイトマップが表示されるだけです。URLからJ041632と切り出してググってみても見つからないから困りものです。

    あれこれ探した結果、現在はhttp://support.microsoft.com/kb/175030/jaで通じるようです。特にこういう知識ベースやそれに類するものでは、URLを変えたなら一定期間などではなく永久的にリダイレクトするようにしてほしいです。

    CP5がどんな感じなのかはOverTakerさんにお任せして、こっちはポストCP5を行きます。はい、今日もCP5やSVN最新でもコンパイル・実行できないようなコードを載せていきます。そのうちできるようになると信じています。


    まずは空っぽのウィンドウを作ります。

    Imports ActiveBasic.Windows.UI
    Control.Initialize(GetModuleHandle(0))
    
    Dim f = New Form
    f.Create()
    Application.Run(f)
    
    End

    フォーム(画像のウィンドウは小さくしてあります)中心となる処理は3行で済むようになります。これくらいは簡単でないと後が思いやられることになりますからね。

    次にHello worldです。まずはPaintイベント (WM_PAINT) の処理を行う関数を書きます。

      Dim hdc = e.Handle
      Dim hfnt = CreateFont(
        MulDiv(24, GetDeviceCaps(hdc, LOGPIXELSY), -72),
        0, 0, 0, FW_REGULAR, FALSE, FALSE, FALSE, DEFAULT_CHARSET,
        OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
        FF_SWISS, "Trebuchet MS")
    
      Dim hfntOld = SelectObject(hdc, hfnt)
      TextOut(hdc, 10, 10, "Hello world!", 12)
      SelectObject(hdc, hfntOld)
      DeleteObject(hfnt)

    いろいろあって、AB4までのRADのときと違ってHDCをe.Handleで得るようになっています。それ以外の中身は何の変哲もないコードです。そしてさっきの部分を次のように変えます。

    Dim f = New Form
    f.Create()
    f.Text = "Hello world"
    f.AddPaintDC(AddressOf(Paint))
    Application.Run(f)
    End

    フォームPaint関数をAddPaintDCでfに「登録」しています。すると、以後Paintイベントが起こると先ほど書いたPaint関数が呼ばれるという具合です。ほかにもAddClick, AddKeyPressなどAB4のRADでイベントとして存在したものは同様に登録可能です。なお、f.Textはウィンドウタイトルです。

    これ(getSingle関数の使い方? - プログラミング質問版)見ていて、ふとそのコードをAB5でコンパイルしてみました。やっぱりだめでした。そのGetSingle関数のバグも直っていませんが、まず最初に出くわしたのがsingle/double/byteがだめだったことです。

    案の定、Single/Double/Byteにするとコンパイル通ります(それ以外にもいろいろ直しましたが)。バグ報告出そうかと思いましたが、CP4のところでそうしたと書いてあります。というわけで、これは仕様のようです。

    同じようなことはまた起こりそうです。今後のためと思って、CP4以降の変更をSVNの記録から抜き出してみました。BasicCompiler32.exe, x86/abc.exeがコミットされたときのコミットログから、コンパイラ・デバッガの変更を私の独断と偏見に基づいて選んでいます。デリゲートや例外処理など新機能のバグ修正、コンパイラ・デバッガへの言及がないコミットログなどは取り除いてあります。重要なものもそうでないのもごちゃまぜ(およそ下から上へ時系列)なので見づらいですが、ご容赦ください。

    • 静的リンクライブラリ機構を実装した。
    • デリゲートの共変戻り値、反変引数に対応した。
    • 共変戻り値のオーバーロードをサポートした。
    • 関数の戻り値の構造体など、一時メモリに保持された構造体のメンバに直接アクセスした場合、その一時メモリの解放が正常に行われないバグを修正。
    • 代入演算時の左辺に関数呼び出しの戻り値を評価してメンバを取得するようなコードが存在するとき、エラーになってしまっていたので改修した。
    • Select Caseに指定された値でエラーが起こったとき、スコープ処理に不具合が生じてしまう問題を修正。
    • 列挙型メンバの初期値に十進数リテラル以外の定数や列挙型メンバを指定できないバグを修正。
    • 関数の戻り値がクラス型のとき、直接インデクサ指定できるような対応を行った。
    • デバッガ変数リストのローカル変数のスコープ判定が間違っていたため、修正。
    • コンストラクタ内でGetTypeメソッドを呼び出すと戻り値がNothingになってしまうバグを修正。
    • 動的型情報にメンバ情報を持たせた。
    • Catch、Finally節を持たないTryスコープを警告の対象とした。
    • Foreachを試験的に実装。
    • ジェネリクスインターフェイスをサポートした。
    • COMインターフェイスが扱えないデグレを修正。※COMインターフェイスの定義では必ずIUnkownを継承してください。
    • 例外処理機構実装。
    • メソッドの実装がある場合(抽象メソッドでない場合)にのみ”override”修飾子を必要とする仕様へと変更。
    • インターフェイス実装時に基底クラスのメソッドの再実装を可能にした。
    • AbstractメソッドはOverride修飾子を指定しなくても良い仕様へと変更。
    • インターフェイス機構の実装。
    • 基底クラスのメソッドを派生クラスで実装するインターフェイスのメソッドに置き換える仕様へと変更。
    • デリゲートを実装。
    • 構造体をクラスメソッドの戻り値にしたときにThisポインタが正常に引き渡されないバグを修正。
    • Protectedメソッドを派生クラス内のメソッドでSuperと指定するとエラーになるバグを修正。
    • クラスメソッド内でImportsされた名前空間内の情報が扱えないバグを修正。
    • 非仮想関数のオーバーライドを禁止した(エラーになります)。
    • インクルードパスに’/'文字を含めたときに’\\’として判断するようにした。

    以下、CP5 (rev.524)以降、現在 (rev.539)までの変更点です。

    • 値渡しの構造体パラメータが正常に引き渡されない不具合を修正。
    • If/While/Doなどのステートメントに引き渡す式の戻り値がクラス型の場合はBoolean型へのキャストを試みるようにした。
    • キャスト演算子が存在せずに型変換できなかった場合のエラーメッセージを変更した。

    たぶんまとめ:cho45のブックマーク / ゆの in languageゆの in Scala。いろいろあるので、AB5でもやってみました。ただの演算子多重定義です。

    #console
    
    Class Yuno
    Public
    	Sub Yuno(y As String)
    		ume = y
    	End Sub
    
    	Function Operator /(m As Long) As Yuno
    		ume += Str$(m) + " "
    		Return This
    	End Function
    
    	Function Operator /(h As Yuno) As Yuno
    		Return This
    	End Function
    
    	Function Operator <(s As String) As String
    		Return ume + s
    	End Function
    Private
    	ume As String
    End Class
    
    Dim X = New Yuno("ひだまりスケッチ")
    Dim _ = 365
    
    Print X / _ / X < "来週も見てくださいね!"
    'System.Console.WriteLine = X / _ / X < "来週も見てくださいね!"
    
    System.Console.ReadLine() 'コンソールが即座に消えるのを防ぐためだけ

    CP5だとPrintではエラーになるので、その場合はSystem.Console.WriteLineのコメントアウトを外して使ってください。

    あまりひねったことはしていない、というかできないです。最初はPrintなしのX / _ / X < “来週も見てくださいね!”で1行にしようとしたのですが、それだとステートメントとして認められないよう(後から考えれば当然でしたが)でエラーでした。事情は異なりますが、C#版D版も見た目同じように1文にできなかった(2つともreturnを前に置いている)あたり親近感を覚えます。

    Ruby始めることになりました。Hello Worldがp "Hello World"というあたり、なかなか期待どおりの言語です。

    ループにforもwhileも書かないのが久々な感じがします。

    5.times do |i|
            print i, " "
    end

    まあ、for使った書き方もありましたけどね。

    for i in (5 ... 10) do
            print i, " "
    end

    まだ軽くしか触っていないので、今日はここまでです。

    ニコニコ大会議2008行ってきました、なんて書こうかと思っていたらCP5が出てきてそれどころではなくなりましたね。

    ああ゛あ゛ー、CP5出荷のときもOLE2関連のヘッダを無効にしたままでした。すみません、今回のコードはCP5だと動きません。雰囲気を味わうということで勘弁してください。あるいはSVNを追っかけてください。


    今回、自分が一番の改良点だと思っているのは、ようやくCOM互換のVTBLをコンパイラが作ってくれるようになったことです。以前のOLEドラッグアンドドロップのコード(IEからのD&Dの受け入れ方
    - プログラミング質問板
    の自分の最初に書いたもの)を書き直してみました。

    ’MainWnd.sbp抜粋
    Sub MainWnd_Destroy()
        RevokeDragDrop(hMainWnd)
        OleUninitialize()
        DragDrop5_DestroyObjects()
        PostQuitMessage(0)
    End Sub
    
    Sub MainWnd_Create(ByRef CreateStruct As CREATESTRUCT)
        If OleInitialize(0) <> S_OK Then Debug
    
        dropTargetImpl = New DropTargetImpl(hMainWnd)
        dropTargetImpl.AddRef()
        Dim hr = RegisterDragDrop(hMainWnd, ObjPtr(dropTargetImpl))
        If FAILED(hr) Then Debug
        dropTargetImpl.Release()
    End Sub

    RegisterDragDropを呼ぶときにObjPtr関数を噛ますのは、将来的には不要になってほしいです。だから、今はおまじないということにしておきます(ObjPtr自体の解説はいつかどこかできちんとやりますが)。

    2つのDimはともに型推論形式です。=の右側から変数の型が決定されます。Dim hr As HRESULTなどといちいち書く必要がなくなって、自分は手放せないです。

    CP5と関係ない変更点として、RegisterDragDropをAddRefとReleaseで挟んだことがあります。これは、Effective COMでそうやって参照カウントを「安定な」状態にしてから、インスタンスを使うべきというような話を見た覚えがあるからです。このプログラムでは問題なさそうですが、念のため従いました。

    'OleDragDropImpl.abp
    Imports System.Runtime.InteropServices
    
    TypeDef ULONG = DWord
    
    Class DropTargetImpl
        Implements IDropTarget
    Public
        Sub DropTargetImpl(hwndTarget As HWND)
            refCount = 0
            hwnd = hwndTarget
            handle = GCHandle.Alloc(This)
        End Sub
    
        Virtual Function QueryInterface(ByRef iid As IID, ByRef pv As Any) As HRESULT
            OutputDebugString(Ex"QueryInterface - ImplUnknownrn")
            If IsEqualIID(iid, IID_IUnknown) <> FALSE Then
                Set_LONG_PTR(VarPtr(pv), ObjPtr(This) As LONG_PTR)
            ElseIf    IsEqualIID(iid, IID_IDropTarget) <> FALSE Then
                Set_LONG_PTR(VarPtr(pv), ObjPtr(This) As LONG_PTR)
            Else
                Set_LONG_PTR(VarPtr(pv), 0 As LONG_PTR)
                QueryInterface = E_NOINTERFACE
                Exit Function
            End If
            QueryInterface = S_OK
            AddRef()
        End Function
    
        Virtual Function AddRef() As ULONG
            OutputDebugString(Ex”AddRef - ImplUnknownrn”)
            refCount++
            AddRef = refCount
        End Function
    
        Virtual Function Release() As ULONG
            OutputDebugString(Ex”Release - ImplUnknownrn”)
            refCount–
            Release = refCount
            If refCount = 0 Then
                handle.Free()
            End If
        End Function
    
        Virtual Function DragEnter(
            /* [unique][in] */ ByVal pDataObj As IDataObject,
            /* [in] */ ByVal grfKeyState As DWord,
            /* [in] */ ByVal x As Long, ByVal y As Long,
            /* [out][in] */ ByRef effect As DWord) As HRESULT
            OutputDebugString(Ex”DragEnter - ImplDropTargetrn”)
            DragEnter = S_OK
        End Function
    
        Virtual Function DragOver(
            /* [in] */ ByVal grfKeyState As DWord,
            /* [in] */ ByVal x As Long, ByVal y As Long,
            /* [out][in] */ ByRef effect As DWord) As HRESULT
            OutputDebugString(Ex”DragOver - ImplDropTargetrn”)
            DragOver = S_OK
        End Function
    
        Virtual Function DragLeave() As HRESULT
            OutputDebugString(Ex”DragLeave - ImplDropTargetrn”)
            DragLeave = S_OK
        End Function
    
        Virtual Function Drop(
            /* [unique][in] */ ByVal pDataObj As IDataObject,
            /* [in] */ ByVal grfKeyState As DWord,
            /* [in] */ ByVal x As Long, ByVal y As Long,
            /* [out][in] */ ByRef effect As DWord) As HRESULT
            OutputDebugString(Ex”Drop - ImplDropTargetrn”)
            MessageBox(hwnd, “ドロップされました”, “”, MB_OK)
            Drop = S_OK
        End Function
    Private
        refCount As ULONG
        hwnd As HWND
        handle As GCHandle
    End Class

    GCHandleはGCされないための対策で、AllocしてからFreeするまでGCされなくなります。以前のコードのごたごたがすっかりなくなりました。多少ByVal/ByRefを変えていますが、このコードでは影響ありませんでした、実質何もやっていないも同然なので。

    そして、Interface.sbpとThunk.abpは不要になりました。前者はライブラリに宣言を一通りぶち込んだためで、後者は冒頭に書いたようにコンパイラがやってくれるからです。我ながらいい時代になったと思います。

    IE7やOffice 2007には表題のような設定項目があります。共に初期設定から有効で、コントロールパネルでの設定より優先して(無視とも言う)、ClearTypeを使用するというものです。

    これは、MSの特権でも何でもなく、自分のアプリケーションでも同じことをやれます。方法は単純で、CreateFontまたはCreateFontIndirectでフォントを作るときに、CLEARTYPE_QUALITYを指定するだけです。これはXPで追加されたフラグです。

    Const CLEARTYPE_QUALITY = 5

    CreateFontなら後ろから3番目の引数、CreateFontIndirectで用いるLOGFONTでは、lfQualityメンバで指定します。ちなみに他のフラグは次のような意味です。

    DEFAULT_QUALITY(何も考えないときに使うやつ)
    コントロールパネルの設定に従う。
    NONANTIALIASED_QUALITY
    常にアンチエイリアスを使わない。
    ANTIALIASED_QUALITY
    常に標準アンチエイリアスを使う。

    本当は比較画像の1つでも挙げたいところですが、上げるのが最近うまくいかないので画像を置きませんでした。興味があれば各自試してみましょう。

    ちなみに、これを使っても低サイズのMS ゴシックなどビットマップを持つフォントやPostScriptなOpenTypeまでClaerType適用になるということはありません。そこはコントロールパネルでClearType使用としたときと同じ基準です。

    以前の記事、Vistaになった記念で、「最初、SetWindowLongPtrを行わないでいたら、ガラス効果のクライアント領域への侵食も起こらずうまくいかないのはなぜだろうと悩みました」と書きましたが、最近になってMSDNライブラリにその理由が書かれているのを見つけました。場所はCustom Window Frame Using DWM“Extending the Client Frame”です。

    ようするに「ガラスフレームを食い込ませる部分は、アルファ値が0でないといけないのだが、標準の白いブラシはそうではない」とのことだそうです。そこの記事では、BLACK_BRUSHを使う方法を提示しています。BLACK_BRUSHはアルファ値が0なのでうまくいくそうです。

    学校の課題でも、真面目にエラーチェックなんてやっていたら、肝心のロジックが埋もれてしまいそうな具合です。といっても、それでもmallocやfopenがNULLを返したら即座にエラーメッセージかつexit(1)というお気楽路線ですが。

    そのお気楽路線のために、こんな感じのマクロを作っています。

    #define die(msg) (fputs((msg), stderr), fputc('n', stderr), exit(1))

    これを作ったとき、すごくこう使いたいと思いました(そのためには上の定義だと若干問題がありますが、もちろんそれを直すという仮定の下です)。

    void *p = malloc(n);
    p != 0 || die("malloc failed!");

    Perlのdieのノリです。p == 0のときだけ、ORの右側、dieを評価しに行くという具合です。さすがにトリッキーかなと思って、結局ifと組み合わせて使うようにしましたが。

    if (p != 0)
        die("malloc failed!");

    この手のエラー処理をするものは、結局ことごとくラップしているので、dieを使う場所は限られてしまい、ifでもいいやと思っている状態です。そこかしこでdieの出番が出るようなら、やっぱり簡潔に書ける||(論理OR)を使おうかなと思うようになる気がします。そもそも、きちんとした例外処理機構があればそれに任せてしまうのですが……。

    どういう使い道があるか分かりませんが、空のステートメントというものはBASICでも認められているようです。念のためですが、空行ではありません。

     : : 

    ABでもVBScriptでも全くエラーを起こしませんでした。もちろんコロンの数は好きなだけ、1つでも構いません。コロンを使ったマルチステートメントで中身を空にすることができるとは思っていませんでした。最初に書いたように使い道は分かりませんが。

    CやC++では次のコードがエラーにならずコンパイルできます。

    // ←一行コメント 行連結文字 → 
    ここもコメント

    2行目もコメントになります。これは行連結文字が1行コメントの除去より先に処理されるためです。もちろん、直観的な挙動ではないので、VC++もGCCも警告を出します。

    ABではどうでしょうか。

    '  _
    コメント?

    試してみればすぐにわかるとおり、コンパイルエラーです。1行コメントの処理が先のようです。ちなみに、VBScriptでも試しましたが、やはりコンパイルエラーでした。Cに慣れるとこれもかえって違和感を覚えます、僅かながらですが。

    Visual C++でちょっとハッシュ関数が欲しくなりました。VC++はhash_mapなどを持っているので、当然ハッシュ関数もあるだろうと睨んで正解、stdext名前空間にhash_value関数がありました。こんな感じに使えます。

    #include <iostream>
    #include <string>
    #include <hash_set>
    
    void print_hash(std::string const& s)
    {
    	std::cout << s << ": " << std::hex << stdext::hash_value(s) << std::endl;
    }
    
    int main()
    {
    	print_hash("ABC");
    	print_hash("あいうえお");
    }

    このhash_value関数はstd::size_t型を返します。少なくともstd::string, std::wstring, char const*, wchar_t const*, 整数型などに使えるようです。

    アクティベーションコンテキストのときのコードでは、SystemParametersInfo(SPI_GETNONCLIENTMETRICSでメッセージボックスの表示に使うフォントを取得していました。何のためと言えば、日本語Vistaでメイリオを使うためです(日本語XP以前ではMS UI Gothicが得られます、特に設定をいじっていなければ)。直接メイリオと指定するよりは、設定の値を読み取るほうがスマートです。XPでもメイリオを入れて設定している人もいれば、私みたいにそれ以外の人、日本語以外の人、いろんな人がいるというのが言い訳理由です。

    それに関連して、今日は.NET Frameworkではどうやるという話です。

    といっても、ずばりSystem.Drawing名前空間のSystemFonts.MessageBoxFontを読み取るだけです。これでFontインスタンスが得られます。これで話は終わってしまいました。

    いえいえ、ここからが本題。さらにそこからLOGFONT構造体を取り出します。

    (more…)

    C++界隈では、最新のドラフトでコンセプトが導入され標準ライブラリ総書き換えと騒がしい感じがしますが、そんな世間には構いもせず地味な話をします。

    Visual C++ 2008で、std::tr1::と打つと、インテリセンス(入力候補の表示)がhexfloatと候補に出してきます。というわけで、軽く試してみました。

    予想通り、これは浮動小数点数を十六進表示する指定のマニピュレータでした。TR1にそういえば載っていた気がします。

    hexfloatが定義されているヘッダ<ios>を見てみましたが、やはり//TR1とコメントがありました。

    #include <iostream>
    #include <cstdio>
    
    int main()
    {
    	double x;
    	std::cout << "Input number> " << std::flush;
    	std::cin >> x;
    	std::cout << x << 'n';
    	std::cout << std::tr1::hexfloat << x << " : hexfloat" << std::endl;
    	std::printf("%a : %%a\n", x);
    }

    printfの%aもやはり浮動小数点数の十六進変換で、こちらはC99です。VC++には2005から入っています。ちなみに、AB5でもSPrintfで%aが使えます。

    おっと、ここまで書きあげてから、hexfloatでググったら、先駆者を見つけてしまいました: TR1のカケラ - Faith and Brave - C++で遊ぼう。こんなぽつんとした機能、良くも悪くも目に留まりますよね。

    前回書いたアクティベーションコンテキストAPIの使い道の1つとして、RAD (IDE)でコントロールごとにXPスタイルを使うかクラシックスタイルを使うか選べるようにしたらどうだろうことを考えています。

    XPスタイルの標準コントロールだと、たとえA系API使っていてもバイト数で数えていたものが文字数単位になってしまうなどそれ以前と仕様変更があります(たしかバイト・文字数の問題は質問板に出たことがあったと思います)。そういうとき、問題のコントロールだけをクラシックスタイルに設定するという具合です。

    そこまでしてXPスタイル使いたいかと聞かれれば答えに窮することですが、それでも言います。見た目は大事ですよ。ぱっと見が変わるだけでだいぶ印象は変わります。

    私は、XPが自宅に着た頃、使っていたフリーウェアに対して片っ端からmanifest付けてXPスタイルにして喜んでいました。そんな単純な人間は意外と多いのかもしれませんよ。WinFAQにも従来のアプリケーションを、XP の新 UI に対応させるには?と載っていたくらいですし。

    話が逸れました。とはいえ、普通のアプリケーションがわざわざ使う機能でもないので、使う状況を考えるとなると、どうしてもこういう一般的でない状況になってしまうんですよね。だいたい、バージョン違いのDLLをわざわざ選択する必要があるという事態がなかなかないでしょうし。ほかに考えた例にはバージョン番号が同じで言語違いを明示的に選択するなんてこともできるのではというのがありますが、そもそもABではそんな国際化機能なんて100年早いなんて状況ですし。

    (more…)

    とりあえず、manifestファイルでどうのこうのする技術のことは「サイドバイサイドアセンブリ」 (side-by-side assemblies)と呼べばよいようです。そして、.localなどそれ以外の関連技術を含んだ広い言葉が「サイドバイサイドコンポーネント共有」 (Side-by-Side Component Sharing)であるようです。

    XPスタイルとクラシックスタイルの混在XPスタイルとクラシックスタイルのコントロールを1プロセスで混ぜて両方使う方法がようやく分かりました。図中のボタン1、ボタン3、ボタン5はSystem32にあるcomctl32.dllバージョン5を使い、ボタン2、ボタン4はマニフェストを指定してWinSxS以下にあるcomctl32.dllバージョン6を使っています。

    これにはアクティベーションコンテキストAPIを使っています。これを使うと、後からマニフェストを読み込んだり、それを好きなときに使用したりなどといったことが可能です。説明は要らないというのであれば、直接ソースコードをどうぞ。

    (more…)

    Windows XPからのcomctl32.dllのバージョン6系列は、共有side-by-sideアセンブリとしてWinSxSフォルダに置かれています。ようするにマニフェストファイルを書かないと使えないというやつです。マニフェストでcomctl32.dllのバージョン6を使うよう指定すればバージョン6(XPスタイルの標準コントロール)が使われ、指定しないと、C:\WINDOWS\SYSTEM32などにある5.82系列のcomctl32.dll(クラシックスタイル)が使われます。以上おさらいです。

    今日気づいたのですが、Windows Server 2003/Vista以降では、comctl32.dllバージョン6.0系列(XPスタイルが適用されるほう)だけでなく、
    5.82系列もWinSxSに置かれています(もちろんSystem32にも健在です)。つまり、マニフェストで5.82を使うと明示できるということです。

    特に便利というわけではないですが、バージョンと動作が大きく異なる同名の共有アセンブリがほかに見当たらないので説明に好都合だと思ったのですが、XPは残念ながら当てはまらなかったというだけのことです。説明というのは次回、次回に先送りです。2つのcomctl32.dllを混ぜて使う話です。

    ABライブラリソースから統計風に数値データを取ってみました。

    ○個引数を持つ関数はどれだけあるかというグラフです。

     0:   227 ====================
     1:   510 ==============================================
     2:   721 =================================================================
     3:   422 ======================================
     4:   255 =======================
     5:   144 =============
     6:    59 =====
     7:    28 ==
     8:    24 ==
     9:    13 =
    10:     9
    11:     5
    12:     4
    13:     2
    14:     1

    これは、引数が0個の関数が227個あるという風に読み取ってください。最多は引数2個の721関数です。それにしても一番多い14個も引数がある関数とはいったい何者でしょう。

    (more…)

    最近、見直しているのが環境変数です。Cではchar const *s = getenv(”hoge”);のようにきわめて簡潔に値を取得できるので、ほとんどコードの追加なしに外から設定変更を可能できるようになることに味をしめたというわけです。自分専用ツールにはちょうどいい感じでした。

    ちなみに、AB5ではこんな感じのことができるようになります。

    #console
    
    Imports System
    
    '環境変数COMSPECの値をcomSpecへ代入
    Dim comSpec = Environment.GetEnvironmentVariable("COMSPEC")
    Print comSpec
    
    Sleep(10000)
    End

    Environment.GetEnvironmentVariableという名前が長いことがやや嬉しくないですが、ほかはいい感じです。環境変数の一覧を取得する方法も提供できればよいのですが、具体的な形が定まっていないので残念ながらまだお預けです。

    ふと、コンパイラ・リンカ・インクルードパスにVisual C++ 2005のものを指定しながら、2005のCRTにリンクさせたらどうなるだろうと思い、試してみました。

    環境変数LIBにVisual C++ 2005のLIBフォルダを指定して、次のCプログラムをコンパイル・実行してみました。

    #include <stdio.h>
    
    int main(void)
    {
    	puts("hello, world");
    	return 0;
    }

    すると、何事もなく実行ファイルが出来上がりました。実行させても全くエラーなくhello, worldを出力します、結構びっくり。

    Dependency Walkerで見てみるとVC++ 2005のmsvcr80.dllを必要とするEXEになっています。しかし、dumpbinで見てみると、オプショナルヘッダのリンカバージョンはVC++ 2008で作られたことを示すように9.0となっています。不思議な感じです。

    まあ、こんなに単純なプログラムだから問題なく動いただけで、もっと付属のC/C++ライブラリを使う大抵のプログラムは、おそらくこうも上手くいかないでしょうね。

    Boost.Spiritというアレなもの使ってみたのですが、1つ気になったことがあります。int_pとかch_pに対するセマンティックアクションはintやcharを受け渡しするように、自作のパーサからも好きなデータを渡せないのかということです。試行錯誤してGrammar<>派生の自作クラスでできるようになりましたが、これよりもっとスマートな方法があるのでは、という思いがまだ頭から離れません(実際、これ書き始めてからもっとスマートな方法を思い付きました)。

    というわけでサンプル2つです。「これよりもっとスマートな方法があるのでは」と言っていた頃のものがspirit1.cppで、その後に思いついたのがsprit2.cppです。自分もやりたいというだけならspirit2.cppだけ見れば十分です。ろくな解説ではありませんが、ソースコードから適当に汲み取ってください。

    pair_grammerが今回の対象で、これにセマンティックアクションを使うとstd::pair<std::string, std::string>が渡されます。dictionary_grammarがそれを使っている側です。insert_mapがセマンティックアクションでstd::pair<>を受け取っています。定義済みのアクションにinsert_aがありますが、分かりやすく示すため、使っていません。


    セマンティックアクションがどのような引数で呼ばれるかが決まるまでの過程を追った結果、次のことがわかりました。まず、grammarのテンプレート引数は、派生クラスを表す1つ目のほかに、2つ目があります。これはcontext<nil>という既定引数があるので省略可能だったのです。これがnilだとセマンティックアクションを呼ぶときにイテレータのペアが引数になります。そして、nilでなければ、その型のインスタンスがセマンティックアクションの引数になります。つまり、例えばstruct g : grammar<g>としていたところをstruct g : grammar<g, context<T> >とすれば良さそうです。しかし、これではまだいけません。

    さて、セマンティックアクションで渡すデータ型を決めたところで、どうやってデータを渡すのかという壁が立ちはだかります。Spiritの文書を眺めていると、パーサコンテキストに気が付きました。解析前と後にコールバックを受け取れるようです。そして、解析後に呼ばれるときの引数で、セマンティックアクションへ渡すデータを与えられるようです。そのためのクラスparser_transfer_contextを作ってできたのがspirit1.cppです。

    しかし、そももそもparse関数を上書き(オーバーライド)すればと思い付いたのがspirit2.cppです。だいぶすっきりしました。


    さて、公式の文書でこのようなことが全く触れられていないのはどうしてでしょう。作るのが多少面倒かもしれないですが、例に使ったpairや日付・時刻などほどほどに使う分には良さそうに思えるんですけどね。

    Next Page »