今日は、まともにラムダ関数を使った例でも書いてみることにします。コードはとっさに思い浮かんだウィンドウの列挙です。VC++10 CTP用です。

#include <iostream>
#include <string>
#include <deque>
#include <algorithm>
#include <functional>

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

namespace egtra
{
    typedef std::tr1::function<bool (HWND)> enum_func_t;

    BOOL CALLBACK xEnumWindowsProc(HWND hwnd, LPARAM lp)
    {
        return (*reinterpret_cast<enum_func_t*>(lp))(hwnd);
    }

    inline BOOL EnumWindows(enum_func_t fn)
    {
        return ::EnumWindows(xEnumWindowsProc,
            reinterpret_cast<LPARAM>(&fn));
    }

    std::basic_string<TCHAR> GetWindowText(HWND hwnd)
    {
        std::basic_string<TCHAR> s;
        if (int tmpLen = GetWindowTextLength(hwnd))
        {
            ++tmpLen;
            s.resize(static_cast<size_t>(tmpLen));
            int len = GetWindowText(hwnd, &s[0], tmpLen);
            s.resize(len);
        }
        return s;
    }
}

int main()
{
    std::deque<HWND> hwnds;
    egtra::EnumWindows([&hwnds](HWND hwnd) -> bool {
        hwnds.push_back(hwnd);
    	  return true;
    });
    std::for_each(hwnds.begin(), hwnds.end(), [](HWND hwnd) {
        std::cout << std::hex << hwnd << 't'
            << egtra::GetWindowText(hwnd) << std::endl;
    });
}

日本語で書くとこんな感じです: EnumWindowsでウィンドウハンドルを取得し、hwndsへ蓄え、そしてハンドルとタイトルを出力するためfor_eachで中身を巡回しています。

中心となる部分がmain関数に全て収まりました。このコードでは名前空間egtraに放り込んだ部分に相当するライブラリが充実したら相当おもしろそうですが、どうせこれまでどおりそこも自分で書き続ける羽目になるのでしょう。

egtra::xEnumWindowsProcは独立した関数にせず、egtra::EnumWindows内にラムダ関数で定義しようとしましたが、キャプチャなし(外側の環境の影響を一切受けないもの)でも関数へのポインタに変換するのは駄目でしたね。そのようなこれもできたらできたで良かったのにとは思います。

今現在の時点で最新らしいC++0xのドラフト2008-10-04のN2798 (注意: 12MBのPDF)などを読んだ感想です。

(a) 18.4: abort, exit, quick_exit, 18.9: longjmp, 18.7.2.2: unexpected_handler, 18.7.3.1: terminate_handlerにも、[[noreturn]]を付加してほしいです(すでに18.7.4 nested_exceptionの一部メンバではnothrowが使われています)。あと、18.4 quick_exitでISO Cの_Exitを参照していますが、これもnoreturnを付けて<cstdlib>に引きずり込んだほうが良い気がします。

(b) こうした上で、コメント04. noreturnの関数が返らないようにする方法は、throwするかnoreturnな関数を呼び出すことと定められるのではないでしょうか。ただし、 実行パスが来ないことを示すVC++の__assume(0)などもあるので、「その他処理系定義の方法」を加えるべきかもしれません。

ただ、unexpected/terminate_handlerをnoreturnにして、かつコメント04が通ると、既存のコードがコンパイルできなくなる可能性があります。これはやはり気にかけるべきところでしょうか。

それとは別のことですが、(c) 0xAB.CDp12のようなC99と同じ十六進浮動小数点リテラルがないのが今更ながら意外でした。hexfloatマニピュレータが入るくらいだからあるだろうと思っていました。ユーザ定義リテラルでは、C99と同じものが作れない(ですよね?)ことが気になります。

現在公開されているVC++ 10のCTP(≒開発中体験版)では、ラムダ関数が使えるということで試してみました。ラムダ関数、つまりその場で書ける無名関数ですが、自分は何よりPStade.Ovenと組み合わせて使いたいと思っていました。というわけで、以前PStade.OvenにBoost.Lambdaを使っていたプログラムをラムダ関数に置き換えてみました。


#include <iostream>
#include <boost/range/algorithm.hpp> //range_exの新版
#include <pstade/oven/dropped.hpp>
#include <pstade/oven/dropped_while.hpp>
#include <pstade/oven/as_c_str.hpp>

int main(int argc, char** argv)
{
    using namespace pstade::oven;
    boost::copy(argv[1] | as_c_str | dropped_while([](char c) {return c != '/';}),
        std::ostream_iterator<char>(std::cout, ""));

    std::cout << std::endl;

    return 0;
}

うーん、これくらいだと、下手したらBoost.Lambdaのほうが簡潔かもしれないと思ってしまいます。

まあ、気を取り直して、ほかにも書いてみようと思ったのですが、ほとんどの場合でコンパイルエラーになりました。上のようにうまくいった例のほうが貴重です。例えば、次のプログラムも、functionを使わずに直接ラムダ関数を書くとエラーになってコンパイルできません。

#include <iostream>
#include <cstdlib>
#include <boost/range/algorithm.hpp>
#include <pstade/oven/transformed.hpp>
#include <pstade/oven/taken.hpp>
#include <pstade/oven/generation.hpp>
#include <functional>

int main()
{
    using namespace pstade::oven;

    std::srand(std::time(0));

    std::tr1::function<int (int)> f = [](int x) {return x % 10;};
    boost::copy(
        generation(nonstop(&std::rand)) | transformed(f) | taken(30)
        , std::ostream_iterator<int>(std::cout, " "));
    std::cout << std::endl;
}

もちろん、現時点では動かなくても仕方ないのは当然です。そう考えると、最初の例がけろりと動いたことに驚きです。

Sorting it all Out : FoldString.NET No, but Whidbey has Normalization (which is kinda more cooler)によれば、FoldString関数がフラグの与え方によってはだいたいUnicodeの正規化と同じような変換を行ってくれるそうです。しかも、MSDNライブラリでFoldStringを引くと、Vistaからは正規化に相当する引数を与えればNormalizeStringを中で呼んで処理するとあります。

でもこれ、1アプリケーションならともかく、ライブラリでは使うかどうかというと微妙な存在ですね。結局、Unicode正規化をやりたければ、自分でやるかたのライブラリに頼るかすべきだなという思いです。

手元では着々とUnicode対応を進めています。未だSVNへはコミットしていませんが、NT系では最終的にWriteConsoleWを使うことで、Unicode文字をコンソールに出力させています。

とりあえず、N88にあった記号類を出力させてみました。
Unicode固有の文字を出力させた例1行目の段の文字の内、1箇所へこんでいるのはフォントのせいです。エディタに張り付けて他のフォントを選べば、きれいな段になります。

全部全角で表示されてほしいところですが、一部が半角になるのは仕様でしょう。その差はShift_JIS(大元はJIS X 0208)に入っていたかどうかです。全角なのはどれもそこにある文字ばかりです。詳しく知りたければ、まずは東アジアの文字幅 - Wikipedia辺りをどうぞ。もちろん、これは厳密に固定長で全角・半角の文字幅を決めねばならないときの話です。プロポーショナルフォントなら自由ですし、固定幅を名乗るフォントでも割とこのようになっていません。


以下、ソースコードです。

(more…)

XPからのテーマAPIには、EnableThemeDialogTextureという関数があります。文字通りダイアログの背景をテーマのテクスチャにすることを許可する関数です。実際に絵を見てみたほうが早いでしょう。

使用前 使用後
Simpatico EnableThemeDialogTexture未使用 Simpatico EnableThemeDialogTexture使用
NeowinEX Navblue EnableThemeDialogTexture未使用 NeowinEX Navblue EnableThemeDialogTexture使用
Windows XP Luna 青 EnableThemeDialogTexture未使用 Windows XP Luna 青 EnableThemeDialogTexture使用

Windows XPのLunaはタブと一緒に使うと効果を発揮します。Windows XP Luna 青 タブの例タブの外側が普通のウィンドウの色、タブとその中がEnableThemeDialogTextureによる背景となっています(他のスキンでも同じです)。標準以外のテーマを入れるにはシステムファイル保護を無効にしなければならないなど、危険性の上昇、手順の面倒くささが問題なので、誰にでもお勧めできる機能ではないのが残念です。


最後に今回使ったソースコードをここに置いておきます。EnableThemeDialogTextureをLoadLibraryせず、直接Declareして使っているので、応用しづらいと思います。

’将来的にはこの#requireは不要になる。
#require 
#require 
#require 

‘comctl32.dll v6を指定するマニフェストをリソースへ。
#resource “UI_Sample.rc”

Const ETDT_ENABLE = 2
Const ETDT_USETABTEXTURE = 4
Const ETDT_ENABLETAB = (ETDT_ENABLE Or ETDT_USETABTEXTURE)

Declare Function EnableThemeDialogTexture Lib “uxtheme” (hwnd As HWND, dwFlags As DWord) As HRESULT

Imports ActiveBasic
Imports ActiveBasic.Windows.UI

Declare Function EndDialog Lib “user32″ (hDlg As HWND, nResult As LONG_PTR) As BOOL

Function GetMessageBoxFont() As HFONT
    Dim ncm As NONCLIENTMETRICS
    ncm.cbSize = Len(ncm)
    SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, VarPtr(ncm), 0)
    GetMessageBoxFont = CreateFontIndirect(ncm.lfMessageFont)
End Function

Class MyForm
    Inherits Dialog
Public
    Sub MyForm()
        AddMessageEvent(WM_INITDIALOG, AddressOf(OnInitDialog))
    End Sub

Private
    Sub OnInitDialog(sender As Object, e As MessageArgs)
        Move(100, 100, 80, 100)

        Dim wpFont = GetMessageBoxFont() As WPARAM

        buttonOk = New Button
        With buttonOk
            .Create(This, BS_DEFPUSHBUTTON, 0, IDCANCEL)
            .Move(10, 10, 50, 30)
            .Text = “Close”
            .AddClick(AddressOf(OnOK))
            .SendMessage(WM_SETFONT, wpFont, 0)
        End With

        EnableThemeDialogTexture(This As HWND, ETDT_ENABLETAB)

        e.LResult = TRUE
    End Sub

    Sub OnOK(sender As Object, e As Args)
        EndDialog(This, 0)
    End Sub

    buttonOk As Button
End Class

Control.Initialize(GetModuleHandle(0))
InitCommonControls()

Dim f = New MyForm
f.DoModal(Nothing)

Windows 7 Developer Guideより1つ小ネタでも。35ページ目、Media Platformのところは、H.264, MJPEG, MP3のコーデックを搭載し、新たにMP4, 3GP, MPEG2-TS, AVIの入力、MP4, 3GP, MP3の出力に対応なんて書いてあります。その少し上にはMPEG-4サポートという言葉もあり、DivX/Xvidも再生できるということでしょう。

Windows 7 surprise: DivX built in(本の虫: Windows 7にはDivXのデコーダなども入っているらしい経由)という記事を見て、読み返してみましたが、このとおりきちんと載っていました。よく読めばほかにもいろいろ出てきそうな気がします。

PDCが行われているということで、Microsoftからいろいろ出ています。Visual Stuido 10 CTPが出たりWindows 7 Developer Guideで概略が明らかになったりという具合です。

リボンコントロール(Scenic Ribbon)がWindows 7に搭載って話でも書こうかと思ったのですが、Direct2Dのほうをよく読む結果になったので、そっちの紹介を軽く行います。

ごちゃごちゃに言います。Direct2Dは、新しい描画APIです。Direct3Dを使っているので、ハードウェアアクセラレーションが効きます。GDI/GDI+と相互運用やります。ネイティブ用です。マネージラッパはそのうち出るかもしれません。Vistaでも動きます。NyaRuRuさんのところによくまとまっています。

中の人のブログも興味深いです。古臭くてごちゃごちゃしたGDI/GDI+を今時のGPUで使えるようにするか、Direct3Dの下で新APIを作るか。新しく作ったほうが早いし古いアプリで問題を起こさないし将来を見据えたAPIにできるし、といった具合で新APIを作る方向に決まった(超意訳)というようなことが書いてあります、たぶん。

もしも、XPで使えるなら、今すぐ試さないとと思うところなんですけどね。やっぱり、Vista以上になるんでしょうか。

今さらですけど、2001年のGoogleについてです。参考:創立10周年の米グーグル,2001年を振り返る特設サイト開設:ITpro。ちょうど自分がインターネットをやり始めた頃で、小学生の頃の記憶が甦ってきたような感じです。

まだGooやYahooなんかを使っていたんで、検索結果の順は違和感がありますが、当時ハマっていたはずの言葉を入れると、確かに当時見た覚えのある「ホームページ」がちょくちょく引っかかります。ダイアルアップで画像の読み込みに数十秒かかったり、キリ番ゲットを掲示板に報告する日を夢見たり、JavaアプレットのゲームでWindows 98がフリーズしたり、そんな感じでした。

自分にとってのALWAYS 三丁目の夕日ですね。懐かしいです。

そういえば、ActiveBasicでググると2.11が出たなんて言っています。当時の公式サイトは残念ながら見れないようですが。

たまに、Explorer.exeのプロセスを終了させたいときがあります。タスクマネージャから強制終了させてもいいのですが、たしか終了させる正規の手段があったはず、とTortoiseSVN 1.54のインストール後、探しました。

意外と手こずりましたが、MSのKBにありました:Visual C++ を使用して Windows シェル拡張をデバッグする方法

Windowsの終了ダイアログボックスを表示させ、Alt + Ctrl + Shiftを押しながら「いいえ」をクリックです。

ちなみに、Vistaなんかだとデスクトップまたはタスクバーにフォーカスを当ててAlt + F4でWindowsの終了のダイアログボックスを表示できます。


エクスプローラを復活させるにはタスクマネージャ (Ctrl + Alt + Del) から「ファイル名を指定して実行」など、強制終了させた時と同じ要領で行います。

今年も高専プロコンに参加してきました。いや、するはずでした。システムトラブルで競技部門は中止になりました。詳しく知りたい方は高専プロコンリポート:高専プロコンを襲った魔物の正体とは (1-2) - ITmedia エンタープライズを読んでください。

ビリよりも悪い結果があるとは思っていませんでしたね。せっかくの努力が報われなかった(開発側もそうだったんでしょうが)。

インターネットで再試合をやるという噂ですが、かなりやる気が失せています。もう来年のほうを向きたいです。

話は変わりますが、このブログも3年目突入です(正確には先週がそうでした)。これからもよろしくお願いいたします。

C++のほうでは,Committee Draft(C++0x 言語仕様のβ版みたいなものです by akiraさん)が出たということで、一部では大盛り上がりのようです。本当なら、ここで自分もちょっと読んでみた、とか一方ABは、なんて話をしたいところですが、それどころではないのでこれくらいにしておきます。

サーバダウンにより3日遅れでお送りします。

Visual C++ 2008にはSTL/CLRなるものが存在します。簡単に言えば、STLを.NETマネージ型に対応させたものです。ちょっとそれで遊んでみようと思い、こんなコードを書きました。

#include <cliext/vector>
#include <cliext/algorithm>
#include <cliext/functional>
#include <functional>
#include <boost/functional.hpp>

bool my_eq(int l, int r)
{
    return l == r;
}

int main()
{
    cliext::vector<int> v;
    v.push_back(1); v.push_back(2); v.push_back(3);
    cliext::vector<int>::iterator it =
        cliext::find_if(v.begin(), v.end(), boost::bind1st(my_eq, 2));
}

cliext::bind1st(my_eq, 2)がだめでした。そうかptr_funがいるんだな、とcliext::ptr_fun(my_eq)と書けば、こっちにはptr_funがないとのこと。MSDNライブラリにも載っていなければ実際のヘッダにも存在しませんでした。cliext::bind1st(std::ptr_fun(だと混合型でアウト。std::bind1st(std::ptr_fun(にすると、gcヒープ上の逆参照をint&で受けようとしているとのことでダメ。もうだめかと思えば、なんとboost::bind1st(my_eq, 2)で出来たのでした。

ちなみにstd::tr1::bind, boost::bind, boost::lambda::bind, boost::lambdaで_1 == 2はすべて駄目でした。

同じ機能がWindows APIと.NET Frameworkの両方から使えるということはよくあります。今日は最近のバージョンで追加されたものを少し取り上げます。

さて、.NET FrameworkとWindowsの発売日を並べると、上のAPIはどれもまず.NETで出て、次に直後のWindowsで搭載されるという流れになっているということがわかります。初めは何の関係もないと思っていたので、ビックリでした(逆に、容易に想像できるという人もいるかもしれませんが)。

最近のWindowsと.NET Frameworkの発売日
年月日 製品名
2001/10/25 Windows XP (OEM)
2002/01/05 .NET Framework 1.0
2003/04/01 .NET Framework 1.1
2003/04/24 Windows Server 2003
2005/11/07 .NET Framework 2.0
2006/11/06 .NET Framework 3.0
2006/11/09 Windows Vista
2007/11/19 .NET Framework 3.5
2008/02/05 Windows Server 2008

Vistaの新機能、UACは昇格ダイアログのおかげで権限を(予め低くしたところから)上げるほうばかり目が行きますが、逆に下げる機能もあります。誰がそんな機能を使うかというと、Internet Explorerです。VistaのIE7にある保護モードはそれを使って通常より権限を低くした制限の強い状態で動かすというものです。これはこれで日本語IMEがまともに使えない(IMEで登録した単語が変換できない:Vista & IE7トラブルウォッチング)などの弊害もあるわけですが。

IEがやっているということは、当然自分でそういうプロセスを作ることも可能です。というわけで、ABからコマンドプロンプトを低い権限で動かすサンプルです。あ、申し遅れましたが、この権限の高い低いのことは整合性レベル(Integrity Level)と呼びます。

#console
'XP以上
Declare Function CreateWellKnownSid Lib "advapi32" (
    WellKnownSidType As WELL_KNOWN_SID_TYPE,
    DomainSid As PSID,
    pSid As PSID,
    ByRef cbSid As DWord
) As BOOL
'NT
Declare Function OpenProcessToken Lib "advapi32" (
    ProcessHandle As HANDLE,
    DesiredAccess As DWord,
    ByRef TokenHandle As HANDLE
) As BOOL
'NT4
Declare Function DuplicateTokenEx Lib "advapi32" (
    hExistingToken As HANDLE,
    DesiredAccess As DWord,
    lpTokenAttributes As *SECURITY_ATTRIBUTES,
    ImpersonationLevel As SECURITY_IMPERSONATION_LEVEL,
    TokenType As TOKEN_TYPE,
    ByRef hNewToken As HANDLE
) As BOOL
'NT
Declare Function SetTokenInformation Lib "advapi32" (
    TokenHandle As HANDLE,
    TokenInformationClass As TOKEN_INFORMATION_CLASS,
    ByRef TokenInformation As Any,
    TokenInformationLength As DWord
) As BOOL
'NT
Declare Function GetLengthSid Lib "advapi32" (
    pSid As PSID
) As DWord
'NT3.51
Declare Function CreateProcessAsUser Lib "advapi32" Alias "CreateProcessAsUserA" (
    hToken As HANDLE,
    lpApplicationName As PCSTR,
    lpCommandLine As PCSTR,
    lpProcessAttributes As *SECURITY_ATTRIBUTES,
    lpThreadAttributes As *SECURITY_ATTRIBUTES,
    bInheritHandles As Long,
    dwCreationFlags As DWord,
    lpEnvironment As VoidPtr,
    lpCurrentDirectory As PCSTR,
    ByRef lpStartupInfo As STARTUPINFO,
    ByRef lpProcessInformation As PROCESS_INFORMATION
) As BOOL

Function CreateLowProcess(cmdLine As PCTSTR) As Boolean
    Dim sidSize = SECURITY_MAX_SID_SIZE As DWord
    Dim sidBuf[ELM(SECURITY_MAX_SID_SIZE)] As Byte
    Dim pIntegritySid = sidBuf As PSID
    If CreateWellKnownSid(WinLowLabelSid,
        0, pIntegritySid, sidSize) = FALSE Then

        Return False
    End If

    Dim hToken As HANDLE
    If OpenProcessToken(GetCurrentProcess(),
        MAXIMUM_ALLOWED, hToken) Then

        Dim hNewToken As HANDLE
        If DuplicateTokenEx(hToken, MAXIMUM_ALLOWED,
            0, SecurityImpersonation, TokenPrimary, hNewToken) Then

            Dim til As TOKEN_MANDATORY_LABEL
            til.Label.Attributes = SE_GROUP_INTEGRITY
            til.Label.Sid = pIntegritySid
            If SetTokenInformation(hNewToken, TokenIntegrityLevel, til,
                SizeOf (TOKEN_MANDATORY_LABEL)
                + GetLengthSid(pIntegritySid)) Then

                Dim pi As PROCESS_INFORMATION
                Dim si As STARTUPINFO
                si.cb = Len(si)
                If CreateProcessAsUser(hNewToken, 0, cmdLine,
                    0, 0, FALSE, 0, 0, 0, si, pi) Then

                    CreateLowProcess = True
                    CloseHandle(pi.hProcess)
                    CloseHandle(pi.hThread)
                Else
                    CreateLowProcess = False
                End If
            End If
            CloseHandle(hNewToken)
        End If
        CloseHandle(hToken)
    End If
End Function

If CreateLowProcess("C:\Windows\System32\cmd.exe") = False Then
    Print ActiveBasic.Windows.HResultToString(GetLastError())
    System.Console.ReadLine()
End If

Vista以上で動かすと、NTFSドライブにことごとく書き込みができません。カレントディレクトリをMy Documentsなどにしてもecho Hello > hello.txtなどが「アクセスが拒否されました。」と言われてしまいます。ごく一部のディレクトリだけ書き込みが許可されていたはずですが、省略します。 Proess ExplorerでIntegrity LevelがLowと表示されている様子 このとおり、Process Explorerで見ても整合性レベルがLowであることが確認できます。

ちなみに、CreateWellKnownSidの引数WinLowLabelSidが整合性レベル低の指定です。WinMediumLabelSid(中)とWinHighLabelSid(高)もあります。昇格済みの高で動いているプロセスから、通常の中で動くプロセスを作るなどといった応用も考えられます。もちろん逆はできないですよ。さすがに昇格ダイアログの回避はできません。

この前は、ExExなんてぶっ飛んだ名前の紹介でしたが、今日はそういう新しいものが出たときにどういう命名がなされるかというのを取り上げてみたいと思います。

まずは後ろにExを付ける方式です。CreateWindowEx, GetFileSizeEx, DrawTextEx, OSVERSIONINFOEX, IDispatchExなど最も数が多い印象です。これが普通です。そうでない奴は全部例外ですと言っても過言ではありません。基本すぎて特に言うことはありません。ExExはもう取り上げましたし。

後ろの反対は前ということで、ExtTextOut, ExtSelectClipRgnなど前に Ext を付ける派もいます。Why are some GDI functions named ExtXxx instead of XxxEx? - The Old New Thingによれば、これらが作られた当時、後ろにExを付ける命名習慣がまだ無かったとのことで、つまり古参なんですね。

また、後ろにEx以外を付ける例も結構あります。ようするにそれ以外ごちゃごちゃの筆頭です。GetTextExtentPoint32など32ビット化したんだねというもの、GetWindowWord → GetWindowLong → GetWindowLongPtrとデータ型を表すもの、GetTextExtentPointIのように少しは意味を考えたらしいものなど色々です。TxF (Transactional NTFS)対応用にTransacted系関数の大量導入(CreateFileTransacted, CopyFileTransacted, etc…)などは数が多いので圧巻です。

中でも特にCOM/シェル周りだと数字を付けるというのが特徴的です。IWebBrowser2なんかはよく使われるほうでしょう。たった今、探した中ではIBrowserService4が一番大きい値でした。シェルではありませんが、IDirect3DDevice7 → IDirect3DDevice8 → IDirect3DDevice9 → IDirect3DDevice9Ex / IDirect3DDevice10という例もあります。これはDirectXのバージョンです。

あと、WOWやWOW64(それぞれ16-32, 32-64 bit ブリッジ)もさすがマイナーどころ、独自形式ばかりです。LoadLibraryEx32W, WOWGlobalAlloc16、Global32Alloc(これはWOWではないですが), WOWShellExecute, Wow64GetThreadContextなどという具合です。純粋に拡張といえるかどうかは微妙な気もしますが、基となる関数があってそれに対する変種であるというのはどれも共通です。

あと目立つのは、ソケットでしょうか。send → WSASendなどWSA, WSAAsyncを前置する方式です。

当然、ここまで含まれない GetTextExtentPoint → GetTextExtentExPoint や GetTextExtentExPointI のような例外も多々あるわけです。網羅しきれません。


ようするにWindows APIはごちゃごちゃしているね、という話でした。

MacやPosixなどよそだとこういう話は全くと言って聞かないです。古いのは思い切って捨てるか、新しいのが出てこない(出す必要がない)といったところなんでしょうか。一体どうやっているのか不思議です。

何気なく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型へのキャストを試みるようにした。
    • キャスト演算子が存在せずに型変換できなかった場合のエラーメッセージを変更した。

    2008年10月16日追記:ゆの in languageとは - はてなキーワードがまとめになっているみたいです。

    たぶんまとめ: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

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

    Next Page »