Chromium Sandbox を用いた保護モード

Chromium Sandbox は,乱暴に言えば

  1. 対象プロセスの権限や動作をとにかく制限する
  2. それだと目的の動作もできなくなることがあるので,本当に必要な処理についてはより強い権限を持ったプロセスで代理処理する

という二段構成になっています.

2. の代理処理は,Sandbox 化されたプロセス内での API 呼び出しをフックして行います*1.悪意のあるコードが API フックを回避し,本来のシステムコールに到達した場合,プロセス本来のアクセス権限によるチェックが行われます.このケースまで想定して,前者の「とにかく」のレベルが決まります.

1. の制限は強ければ強いほど良いため,Chromium Sandbox では,先ほど出てきた整合性レベルに加え,プロセスのアクセストークンからの権限削除,Job を利用した制約,デスクトップ分離などさまざまなテクニックが使われています.実際には,アプリケーション本来の目的を阻害するような制約は諦めざるをえないため,アプリケーションごとにどの制約を組み合わせるかを選択します.Adobe Reader のケースでは,一部の設定について PC 管理者がカスタマイズすることを許可しています.

Adobe Reader に関しては,次の文献などで詳細な解析が行われています.
http://media.blackhat.com/bh-us-11/Sabanal/BH_US_11_SabanalYason_Readerx_WP.pdf

Chromium Sandbox のような特殊な環境で IME が動作しているとき,正常に動作しているかどうかを判断するのは難しいものがあります.多くの場合,プロセスを起動するような操作しばしば失敗します.これは,辞書ツールや文字パレットが別プロセスとして実装されているケースでは明らかになるでしょうが,これらのツールが対象プロセス内に表示されるウィンドウとして実装されている場合には表面化しないかもしれません.IME の確定履歴がディスク上に書き込めないというケースもあります.Process Monitor でのログを見るだけで問題が明らかになることもありますが,いずれにせよ IME についての理解は必要です.

*1:AdHoc なルールを透過的に適応するという点では Windows の互換性スイッチに似ているとも言えます

整合性レベルに基づく保護モード

整合性レベル導入による互換性上の影響は,大まかに次の 2 つに分けることができます.
1. アクセス権不足によるリソースアクセスの失敗
2. ユーザーインターフェイス特権の分離 (UIPI) による,プロセス間通信の失敗
前者について,整合性レベルは OS 標準の ACL の上に実装されており,その実装についても詳細なドキュメントが存在します.
後者については,主にウィンドウシステムとウィンドウ間の通信に関する仕様変更で,こちらも詳細にドキュメント化されています.

互換性上の問題に直面したとき,開発者は,整合性レベル Low のプロセス用に専用の設定ファイルを用意したり,カーネルオブジェクトの ACL を変更して整合性レベル Low のプロセスからもオープンできるようにしたり,UIPI の例外を登録したりといった回避策をとることができます.

保護モードと IME

Windows アプリケーションに,統一的な "Protected Mode" (保護モード)の仕様や基準といったものはありません.保護モードと呼ばれている各技術の実装は製品ごとに異なります.A という製品の「保護モード」を有効にした状態で IME が動く(ように見える)からといって,B という製品の「保護モード」を有効にしても同じ IME が動く(ように見える)とは限りません.

例えば,Google Chrome は,ウィンドウ処理を行うブラウザプロセスと,HTML のレンダリングを行うレンダラプロセスを分けており,Chrome の保護モードが適応されるのはレンダラプロセスの方です.IME はウィンドウ処理が行われるブラウザプロセスに読み込まれるため,IME にとっての Chrome は,(プラグイン周りをいったん忘れることにすると) メモ帳などと同じただのデスクトップアプリケーションです.

UAC が有効な環境での Microsoft Internet Explorer は,整合性レベルが Low に設定されたプロセスでウィンドウ処理と HTML のレンダリングを実行します.IME はウィンドウ処理を行うプロセス内に読み込まれるので,IME は整合性レベル Low のプロセスで動作する必要があります.なお,Internet Explorer は,整合性レベルを Low にする以外ほとんどプロセスの設定を変更しないため*1,メモ帳を PsExec の -l オプションで起動して IME のテストをするだけでも十分有用です.

Adobe Reader や Firefox 版 Adobe Flash Player の「保護モード」はというと,Chrome の Sandbox 関連の技術を利用していることが Adobe から公表されています*2.Chrome とは異なるのは,ウィンドウ処理を行うプロセス自体を Sandbox 内で実行するということです.このケースでは,IME は Sandbox 化されたプロセス内で動作することになります.

アプリケーション IME の動作環境
Google Chrome メモ帳等と変わらず
Microsoft Internet Explorer 整合性レベル Low
Adobe Reader X Sandboxed process

なお,ブラウザプラグイン特有の IME 関連の問題についてに関しては (基本的に) 今回は扱いません.

*1:他には,HKCU のマッピング が行われたり

*2:たとえば http://blogs.adobe.com/asset/tag/sandbox 内にもいくつか言及が見られます

IWordBreaker とファイル検索

「『プリキュア』で検索したら『ハートキャッチプリキュア』にマッチしない」という Windows Search の話.

Windows7に深刻なバグを発見したので、警鐘を鳴らすために晒してみます。
再現に使用したOSはWindows7 Home Premium x64です。

バグの再現手順

 
!!! 悪用厳禁 !!!
 
●1.適当にフォルダを作る 名前は何でもOK


 
●2.作ったフォルダーを開いて、
ハートキャッチプリキュア
ふたりはプリキュア
プリキュア
の3つのフォルダを新規作成する

 

●3.検索窓に「プリキュア」と入力してみる


 

●4.「ハートキャッチプリキュア」が無かったことにされる

ちくしょう!誰がこんなことを!メディーック!!メディーーーーック!!

対処方法

検索窓に「*プリキュア」と入れると全部ヒットするみたい。

でも、XPの頃は「プリキュア」で全部ヒットしてたのでなんか腑に落ちないアレが。

ちなみに検索インデックスの有無は関係ないみたいです。

#2010/10/30 11:05 追記
VistaやMacOSでも再現するとか。
Windowsの人は、「Everything」を使うと幸せになれるらしいです。

「従来何も考えずにファイル名の部分文字列で検索できていたのものを,どうしてアスタリスクが必要にしちゃったの?」という方向の話のような気もしますが,その辺は置いておいて久しぶりに IWordBreaker とか.
Windows 7 に標準で付いてくる日本語向け IWordBreaker 実装に「ハートキャッチプリキュア」等を食わせてみます.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using Microsoft.Win32;
using WordBreaker;

namespace WordBreakerTest
{
  using HRESULT = System.UInt32;
  public struct HResults
  {
    public const HRESULT S_OK = 0x00000000;
    public const HRESULT S_FALSE = 0x00000001;
    public const HRESULT E_FAIL = 0x80004005;
    public const HRESULT WBREAK_E_END_OF_TEXT = 0x80041780;
    public const HRESULT LANGUAGE_S_LARGE_WORD = 0x00041781;
    public const HRESULT WBREAK_E_QUERY_ONLY = 0x80041782;
    public const HRESULT WBREAK_E_BUFFER_TOO_SMALL = 0x80041783;
    public const HRESULT LANGUAGE_E_DATABASE_NOT_FOUND = 0x80041784;
    public const HRESULT WBREAK_E_INIT_FAILED = 0x80041785;
  }

  public enum WORDREP_BREAK_TYPE
  {
    WORDREP_BREAK_EOW = 0,
    WORDREP_BREAK_EOS = 1,
    WORDREP_BREAK_EOP = 2,
    WORDREP_BREAK_EOC = 3
  }

  [SuppressUnmanagedCodeSecurity]
  [ComImport, Guid("CC907054-C058-101A-B554-08002B33B0E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  public interface IWordSink
  {
    [PreserveSig, MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    HRESULT PutWord(
        uint cwc,
        [In][MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0, ArraySubType = UnmanagedType.U2)] char[] pwcInBuf,
        uint cwcSrcLen,
        uint cwcSrcPos);
    [PreserveSig, MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    HRESULT PutAltWord(
        uint cwc,
        [In][MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0, ArraySubType = UnmanagedType.U2)] char[] pwcInBuf,
        uint cwcSrcLen,
        uint cwcSrcPos);
    [PreserveSig, MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    HRESULT StartAltPhrase();
    [PreserveSig, MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    HRESULT EndAltPhrase();
    [PreserveSig, MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    HRESULT PutBreak(WORDREP_BREAK_TYPE breakType);
  }

  [SuppressUnmanagedCodeSecurity]
  [ComImport, Guid("CC906FF0-C058-101A-B554-08002B33B0E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  public interface IPhraseSink
  {
    [Obsolete("Not supported.")]
    [PreserveSig, MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    HRESULT PutSmallPhrase(
        [In][MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1, ArraySubType = UnmanagedType.U2)] char[] pwcNoun,
        uint cwcNoun,
        [In][MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3, ArraySubType = UnmanagedType.U2)] char[] pwcModifier,
        uint cwcModifier, uint ulAttachmentType);
    [PreserveSig, MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    HRESULT PutPhrase(
        [In][MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1, ArraySubType = UnmanagedType.U2)] char[] pwcPhrase,
        uint cwcPhrase);
  }

  public class WordSink : IWordSink
  {
    public Action<string, uint, uint> OnWord { get; set; }
    public Action<string, uint, uint> OnAltWord { get; set; }
    public Action<WORDREP_BREAK_TYPE> OnBreak { get; set; }
    #region CWordSink Members
    public HRESULT PutWord(
        uint cwc,
        [In][MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0, ArraySubType = UnmanagedType.U2)] char[] pwcInBuf,
        uint cwcSrcLen,
        uint cwcSrcPos)
    {
      if (OnWord != null)
      {
        OnWord(new string(pwcInBuf), cwcSrcLen, cwcSrcPos);
      }
      return HResults.S_OK;
    }
    public HRESULT PutAltWord(
        uint cwc,
        [In][MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0, ArraySubType = UnmanagedType.U2)] char[] pwcInBuf,
        uint cwcSrcLen,
        uint cwcSrcPos)
    {
      if (OnAltWord != null)
      {
        OnAltWord(new string(pwcInBuf), cwcSrcLen, cwcSrcPos);
      }
      return HResults.S_OK;
    }
    public HRESULT StartAltPhrase()
    {
      return HResults.S_OK;
    }
    public HRESULT EndAltPhrase()
    {
      return HResults.S_OK;
    }
    public HRESULT PutBreak(WORDREP_BREAK_TYPE breakType)
    {
      if (OnBreak != null)
      {
        OnBreak(breakType);
      }
      return HResults.S_OK;
    }
    #endregion
  }

  public class CPhraseSink : IPhraseSink
  {
    #region CPhraseSink Members
    public HRESULT PutSmallPhrase(
        [In][MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1, ArraySubType = UnmanagedType.U2)] char[] pwcNoun,
        uint cwcNoun,
        [In][MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3, ArraySubType = UnmanagedType.U2)] char[] pwcModifier,
        uint cwcModifier,
        uint ulAttachmentType)
    {
      return HResults.S_OK;
    }
    public HRESULT PutPhrase(
        [In][MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1, ArraySubType = UnmanagedType.U2)] char[] pwcPhrase,
        uint cwcPhrase)
    {
      return HResults.S_OK;
    }
    #endregion
  }

  [UnmanagedFunctionPointer(CallingConvention.StdCall)]
  public delegate uint FillTextBufferDelegate(ref TEXT_SOURCE pTextSource);

  [StructLayout(LayoutKind.Sequential)]
  public struct TEXT_SOURCE
  {
    [MarshalAs(UnmanagedType.FunctionPtr)]
    public FillTextBufferDelegate pfnFillTextBuffer;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string awcBuffer;
    public uint iEnd;
    public uint iCur;
  }

  [SuppressUnmanagedCodeSecurity]
  [ComImport, Guid("D53552C8-77E3-101A-B552-08002B33B0E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  public interface IWordBreaker
  {
    [PreserveSig, MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    HRESULT Init(
        [MarshalAs(UnmanagedType.Bool)] bool fQuery,
        uint maxTokenSize, [MarshalAs(UnmanagedType.Bool)] out bool pfLicense);
    [PreserveSig, MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    HRESULT BreakText(
        ref TEXT_SOURCE pTextSource, [MarshalAs(UnmanagedType.Interface)] IWordSink pWordSink,
        [MarshalAs(UnmanagedType.Interface)] IPhraseSink pPhraseSink);
    [PreserveSig, MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    HRESULT GetLicenseToUse([MarshalAs(UnmanagedType.LPWStr)] out string ppwcsLicense);
  }

  public static class Program
  {
    public static void BreakText(string text, bool forQuery)
    {
      const string kWordBreakerKey =
          @"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ContentIndex\Language\Japanese_Default";
      var guid = new Guid(Registry.GetValue(kWordBreakerKey, @"WBreakerClass", string.Empty) as string);
      var wordBreakerType = Type.GetTypeFromCLSID(guid);

      // A newer wordbreaker shipped with MS Office 2010.
      // wordBreakerType = Type.GetTypeFromProgID("NLG.Japanese Wordbreaker.4.1");

      var wordBreaker = default(IWordBreaker);
      try
      {
        wordBreaker = Activator.CreateInstance(wordBreakerType) as IWordBreaker;

        var license = true;
        wordBreaker.Init(forQuery, 4096, out license);

        var filler = (FillTextBufferDelegate)((ref TEXT_SOURCE _) => HResults.WBREAK_E_END_OF_TEXT);
        var pTextSource = new TEXT_SOURCE()
        {
          pfnFillTextBuffer = filler,
          awcBuffer = text,
          iCur = 0,
          iEnd = checked((uint)text.Length),
        };

        var dictionary = new Dictionary<WORDREP_BREAK_TYPE, string>
        {
          {WORDREP_BREAK_TYPE.WORDREP_BREAK_EOC, "[EOC]"},
          {WORDREP_BREAK_TYPE.WORDREP_BREAK_EOP, "[EOP]"},
          {WORDREP_BREAK_TYPE.WORDREP_BREAK_EOS, "[EOS]"},
          {WORDREP_BREAK_TYPE.WORDREP_BREAK_EOW, "[EOW]"},
        };

        var words = new List<string>();
        var altWords = new List<string>();
        wordBreaker.BreakText(ref pTextSource, new WordSink
        {
          OnWord = (word, _, __) => words.Add(word),
          OnAltWord = (word, _, __) => altWords.Add(word),
          OnBreak = type => { words.Add(dictionary[type]); altWords.Add(dictionary[type]); },
        }, new CPhraseSink());
        GC.KeepAlive(filler);
        Console.WriteLine("Words: " + string.Join("/", words));
        Console.WriteLine("Alt Words: " + string.Join("/", altWords));
      }
      catch
      {
        if (wordBreaker != null)
        {
          Marshal.ReleaseComObject(wordBreaker);
          wordBreaker = null;
        }
      }
    }

    [MTAThread]
    static void Main(string[] args)
    {
      BreakText("プリキュア", false);
      BreakText("ふたりはプリキュア", false);
      BreakText("ハートキャッチプリキュア", false);
      BreakText("マイコンピューター", false);
      BreakText("情シス", false);
    }
  }
}
Words: プリキュア
Alt Words:
Words: ふたり/は/プリキュア
Alt Words:
Words: ハトキアッチプリキュア
Alt Words: ハートキャッチプリキュア
Words: マイコンピュタ
Alt Words: マイコンピューター
Words: 情/シス
Alt Words:

さすがに "プリキュア" で分割してくれたりはしないようですね.というかそもそも,「欧文地名以外の複合語をカタカナ表記するときは分かち書き」という Microsoft のスタイルガイド が遵守されているのが前提なのか,カタカナの連続は何も考えずにくっつけているだけのような挙動にも見えました.あんまりちゃんと実験してませんが.
ちなみに,SharePoint に付属する WordBreaker では,以下のようにユーザ辞書ファイルを使うことが出来るようです.

4. 以下に従い、ファイルを保存します。
場所 "C:\Program Files\Microsoft Office Servers\12.0\Bin"
(日本語ワードブレーカ nlsdata0011.dllが存在する場所)
ファイル名 "Custom0011.lex" (0011 は言語 ID)
文字コード "Unicode"

さらにこの nlsdata0011.dll というファイルですが,手元の Windows 7 Ja 環境では同名のファイルがシステムディレクトリに存在します.試しに %SystemRoot%\System32\Custom0011.lex (と %SystemRoot%\SysWOW64\Custom0011.lex) というファイルを作り,以下の内容を入力し,BOM 付き UTF-16 ファイルで保存してみます.

#CUSTOMER_WB
情シス
プリキュア

改めて最初のコードを実行すると,結果は以下のようになりました.

Words: プリキュア
Alt Words:
Words: ふたり/は/プリキュア
Alt Words:
Words: ハトキアッチプリキュア
Alt Words: ハートキャッチプリキュア
Words: マイコンピュタ
Alt Words: マイコンピューター
Words: 情シス
Alt Words:

少なくとも「情シス」の方は 1 word として認識されるようになりました.また,実行中に Custom0011.lex が読み込まれていることも,Process Monitor のログから確かめられました.
一方,ユーザ辞書に「プリキュア」を追加しても,"ハートキャッチ/プリキュア" と分割されませんでした.これは,以下の SharePoint での事例と同じもののようです.

ワードブレーキング (設定箇所 : サーバー定義ファイル)

こちらは、セミナーの資料では省略していましたが、懇親会でご質問がありましたので記載しておきます (懇親会場でご回答させて頂きました)。

例えば、「ペドロ&カプリシャス」のようなキーワードを検索したい場合、インデックス収集時に、間の記号(アンパサント &)によって、「ペドロ」と「カプリシャス」でキーワードが自動的に区切られます。こうした場合には、カスタムディクショナリー(Custom Dictionary) を設定することで、こうした自動ブレークを阻止し、「ペドロ&カプリシャス」で完全マッチの検索をおこなうことができます。

カスタムディクショナリの設定ファイルを配置する場所は、シソーラスファイルとは異なり、%programfiles%\Microsoft Office Servers\12\bin\CustomLANG.lex です。(日本語の場合は、Custom0011.lex です。) 設定を反映させるには、インデックスの再収集以外に、クエリー時のブレーク箇所も正しく認識させる必要があるため、ファイル編集後は、 Office SharePoint Server Search サービス (osearch) の再起動と、再クロールの双方をおこなってください。

カスタムディクショナリの作成方法については、以下の記事が参考になります。

TechNet : ユーザー辞書を作成する (Office SharePoint Server 2007)
http://technet.microsoft.com/ja-jp/library/cc263242.aspx

実は、懇親会では、「ワードブレークを阻止したい」 というご質問ではなく、逆に 「ワードを分割して認識させられないか」 というものでした。私は、この回答として、「カスタム辞書 (上記の CustomLANG.lex) を編集することで認識させられる可能性があるかもしれない」 とお答えしてしまいましたが、すみません、動作を確認してみたところ、本来分割されていないワードを分割して認識させることは不可能でした。(この予測は誤っておりました。申し訳ありません . . .)

発端の話も,「ワードを分割して認識させられないか」の一種だと思いますが,どうも現世代の Microsoft 製 IWordBreaker 実装ではユーザ辞書を使ってもこの問題を回避できなさそうな感じです. 次なる手段としては,自分で IWordBreaker を実装 して,HKLM\SYSTEM\CurrentControlSet\Control\ContentIndex\Language\Japanese_Default 以下の WBreakerClass を置き換えてしまう,あたりでしょうか.試してはいないので,うまくいくかは分かりませんが.

C# のコードに x86/x86-64 命令を直接組み込む

C# で書かれた将棋の思考ルーチンの高速化のため,(Visual C++ 用の) 組み込み関数 _mm_prefetch 的なものを使うべく,ネイティブコードで書かれた DLL と C# で書かれたメインの思考ルーチンを組み合わせてみた,というお話.ふむふむ.

ざっと眺めて C# のみで書けそうだったので,気分転換も兼ねて書いてみました.個人的には単一の(メタ)言語で完結するプロジェクトが好きです.配布するファイルの数が減るのはインストール・アンインストール作業やバージョン管理が楽になります.Visual Studio で複数言語を混在させると,Express Edition の人にビルドしてもらうとき困ったりするというのもがあります.ビルドシステムは単純な方がいいですよ.ほんと.とまあこの辺りが書いてみようと思った主な理由でしょうか.
さて,以下コードが整理されていないので読みにくいですが,基本的には,

  1. VirtualAlloc で領域を確保し,そこに使いたい関数を書き込む
  2. VirtualProtect で保護属性を PAGE_EXECUTE に変更する *1
  3. Marshal.GetDelegateForFunctionPointer に関数の先頭アドレスを渡して .NET デリゲートに変換する

という流れです.
以下のコードは,ak11 さんの記事 と同じく,Prefetch128, Prefetch256, cpuid の 3 つを作成し,C# コードから呼び出しています.呼び出される関数の内容は事前に作ったものですが,実行環境によって x86 用と x86-64 用の関数を使い分けています.なお,Itanium 等その他の CPU には対応しておりません.
余談ですが,原理上は生成される関数自体をプログラムで制御してしまうことも可能です.今回はそこまでやっていませんが,もしその手の動的コード生成の世界に挑戦するのであれば,Xbyak が参考になるかと思います.

// Windows applications may or may not be originally written in Objective-C,
// C, C++, or JavaScript as executed by the JScript engine, and not only code
// written in C, C++, and Objective-C but also code written in other languages
// can compile and directly link against the Documented APIs.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;

namespace Win32
{
  internal enum ProcessorArchitecture : ushort
  {
    PROCESSOR_ARCHITECTURE_AMD64 = 9,
    PROCESSOR_ARCHITECTURE_IA64 = 6,
    PROCESSOR_ARCHITECTURE_INTEL = 0,
    PROCESSOR_ARCHITECTURE_UNKNOWN = 0xffff,
  }
  internal enum ProcessorType : uint
  {
    PROCESSOR_INTEL_386 = 386,
    PROCESSOR_INTEL_486 = 486,
    PROCESSOR_INTEL_PENTIUM = 586,
    PROCESSOR_INTEL_IA64 = 2200,
    PROCESSOR_AMD_X8664 = 8664,
  }
  [Flags]
  internal enum VirtualAllocType : uint
  {
    MEM_COMMIT = 0x1000,
    MEM_RESERVE = 0x2000,
    MEM_RESET = 0x80000,
    MEM_LARGE_PAGES = 0x20000000,
    MEM_PHYSICAL = 0x400000,
    MEM_TOP_DOWN = 0x100000,
    MEM_WRITE_WATCH = 0x200000,
  }
  [Flags]
  internal enum VirtualFreeType : uint
  {
    MEM_DECOMMIT = 0x4000,
    MEM_RELEASE = 0x8000,
  }
  [Flags]
  internal enum MemoryProtectionType : uint
  {
    PAGE_NOACCESS = 0x01,
    PAGE_READONLY = 0x02,
    PAGE_READWRITE = 0x04,
    PAGE_WRITECOPY = 0x08,
    PAGE_EXECUTE = 0x10,
    PAGE_EXECUTE_READ = 0x20,
    PAGE_EXECUTE_READWRITE = 0x40,
    PAGE_EXECUTE_WRITECOPY = 0x80,
    PAGE_GUARD = 0x100,
    PAGE_NOCACHE = 0x200,
    PAGE_WRITECOMBINE = 0x400
  }
  [StructLayout(LayoutKind.Sequential, Pack = 2)]
  internal struct SystemInfo
  {
    public ProcessorArchitecture ProcessorArchitecture;
    ushort Reserved;
    public uint PageSize;
    public IntPtr MinimumApplicationAddress;
    public IntPtr MaximumApplicationAddress;
    public IntPtr ActiveProcessorMask;
    public uint NumberOfProcessors;
    public ProcessorType ProcessorType;
    public uint AllocationGranularity;
    public ushort ProcessorLevel;
    public ushort ProcessorRevision;
  }
  [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
  internal class VirtualAllocRegion : SafeHandle
  {
    private VirtualAllocRegion()
      : base(IntPtr.Zero, true)
    {
    }
    public override bool IsInvalid
    {
      get { return handle == IntPtr.Zero; }
    }
    protected override bool ReleaseHandle()
    {
      return NativeMethods.VirtualFree(handle, (UIntPtr)0, VirtualFreeType.MEM_RELEASE);
    }
  }
  internal static class NativeMethods
  {
    [SuppressUnmanagedCodeSecurityAttribute]
    [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
    extern static public IntPtr GetCurrentProcess();
    [SuppressUnmanagedCodeSecurityAttribute]
    [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    extern static public bool FlushInstructionCache(IntPtr processHandle, IntPtr address, uint regionSize);
    [SuppressUnmanagedCodeSecurityAttribute]
    [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
    extern static public void GetSystemInfo(out SystemInfo info);
    [SuppressUnmanagedCodeSecurityAttribute]
    [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
    extern static public VirtualAllocRegion VirtualAlloc(
        IntPtr address, UIntPtr size, VirtualAllocType allocType, MemoryProtectionType protectionType);
    [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    extern static public bool VirtualFree(IntPtr address, UIntPtr size, VirtualFreeType allocType);
    [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    extern static public bool VirtualProtect(
        IntPtr address, UIntPtr size, MemoryProtectionType protectionType,
        out MemoryProtectionType oldProtectionType);
  }

  [StructLayout(LayoutKind.Sequential, Pack = 4)]
  public struct CPUInfo
  {
    public uint eax;
    public uint ebx;
    public uint ecx;
    public uint edx;
  }

  public static unsafe class Prefetcher
  {
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    [SuppressUnmanagedCodeSecurityAttribute]
    private delegate void PrefetcherDelegate(void* address);
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    [SuppressUnmanagedCodeSecurityAttribute]
    private delegate void CPUIDDelegate([In, Out] ref CPUInfo info);

    private static void DummyPrefetcher(void* address) { }
    private static void DummyCPUID(ref CPUInfo info) { }
    private static PrefetcherDelegate prefetch128_ = DummyPrefetcher;
    private static PrefetcherDelegate prefetch256_ = DummyPrefetcher;
    private static CPUIDDelegate cpuid_ = DummyCPUID;

    private readonly static VirtualAllocRegion region_ = null;

    private static TDelegate CreateDelegate<TDelegate>(IntPtr base_address, int offset)
      where TDelegate : class
    {
      var address = IntPtr.Add(base_address, offset);
      return Marshal.GetDelegateForFunctionPointer(address, typeof(TDelegate)) as TDelegate;
    }

    static Prefetcher()
    {
      // Use GetSystemInfo API to determine the processor architecture.
      var system_info = default(SystemInfo);
      NativeMethods.GetSystemInfo(out system_info);
      var supported_architectures = new []
      {
        ProcessorArchitecture.PROCESSOR_ARCHITECTURE_INTEL,
        ProcessorArchitecture.PROCESSOR_ARCHITECTURE_AMD64,
      };
      if (!supported_architectures.Contains(system_info.ProcessorArchitecture))
      {
        // Unsupported architecture.
        return;
      }

      var x86 = new
      {
        CPUID = new byte[]
        {
          // void __declspec(noinline) __stdcall CPUID(CPUInfo* info);
          0x53,                    //  push        ebx
          0x57,                    //  push        edi
          0x8B, 0x7C, 0x24, 0x0C,  //  mov         edi,dword ptr [esp+0Ch]
          0x8B, 0x07,              //  mov         eax,dword ptr [edi]
          0x8B, 0x4F, 0x08,        //  mov         ecx,dword ptr [edi+8]
          0x0F, 0xA2,              //  cpuid
          0x89, 0x07,              //  mov         dword ptr [edi],eax
          0x89, 0x5F, 0x04,        //  mov         dword ptr [edi+4],ebx
          0x89, 0x4F, 0x08,        //  mov         dword ptr [edi+8],ecx
          0x89, 0x57, 0x0C,        //  mov         dword ptr [edi+0Ch],edx
          0x5F,                    //  pop         edi
          0x5B,                    //  pop         ebx
          0xC2, 0x04, 0x00,        //  ret         4
        },
        Prefetch128 = new byte[]
        {
          // void __declspec(noinline) __stdcall Prefetch128(void* ptr);
          0x8B, 0x4C, 0x24, 0x04,  // mov ecx, dword ptr [esp+4]
          0x0F, 0x18, 0x19,        // prefetcht2  [ecx]
          0x0F, 0x18, 0x59, 0x40,  // prefetcht2  [ecx+40h]
          0xC2, 0x04, 0x00,        // ret 4
        },
        Prefetch256 = new byte[]
        {
          // void __declspec(noinline) __stdcall Prefetch256(void* ptr);
          0x8B, 0x4C, 0x24, 0x04,                    // mov ecx, dword ptr [esp+4]
          0x0F, 0x18, 0x19,                          // prefetcht2  [ecx]
          0x0F, 0x18, 0x59, 0x40,                    // prefetcht2  [ecx+40h]
          0x0F, 0x18, 0x99, 0x80, 0x00, 0x00, 0x00,  // prefetcht2  [ecx+80h]
          0x0F, 0x18, 0x99, 0xC0, 0x00, 0x00, 0x00,  // prefetcht2  [ecx+0C0h]
          0xC2, 0x04, 0x00,                          // ret 4
        },
      };

      var x64 = new
      {
        CPUID = new byte[]
        {
          // void __declspec(noinline) CPUID(CPUInfo* info);
          0x4C, 0x8B, 0xCB,        // mov         r9,rbx
          0x4C, 0x8B, 0xC1,        // mov         r8,rcx
          0x41, 0x8B, 0x00,        // mov         eax,dword ptr [r8]
          0x41, 0x8B, 0x48, 0x08,  // mov         ecx,dword ptr [r8+8]
          0x0F, 0xA2,              // cpuid
          0x41, 0x89, 0x00,        // mov         dword ptr [r8],eax
          0x41, 0x89, 0x58, 0x04,  // mov         dword ptr [r8+4],ebx
          0x41, 0x89, 0x48, 0x08,  // mov         dword ptr [r8+8],ecx
          0x41, 0x89, 0x50, 0x0C,  // mov         dword ptr [r8+0Ch],edx
          0x4C, 0x89, 0xCB,        // mov         rbx,r9
          0xC3,                    // ret
        },
        Prefetch128 = new byte[]
        {
          // void __declspec(noinline) Prefetch128(void* ptr);
          0x0F, 0x18, 0x19,        // prefetcht2  [rcx]
          0x0F, 0x18, 0x59, 0x40,  // prefetcht2  [rcx+40h]
          0xC3,                    // ret
        },
        Prefetch256 = new byte[]
        {
          // void __declspec(noinline) Prefetch256(void* ptr);
          0x0F, 0x18, 0x19,                          // prefetcht2  [rcx]
          0x0F, 0x18, 0x59, 0x40,                    // prefetcht2  [rcx+40h]
          0x0F, 0x18, 0x99, 0x80, 0x00, 0x00, 0x00,  // prefetcht2  [rcx+80h]
          0x0F, 0x18, 0x99, 0xC0, 0x00, 0x00, 0x00,  // prefetcht2  [rcx+0C0h]
          0xC3,                                      // ret
        },
      };

      var target = Environment.Is64BitProcess ? x64 : x86;

      // Align 8-byte boundary with the specified padding data.
      var align8 = (Func<byte[], byte, byte[]>)(
        (array, paddingData) => array.Concat(Enumerable.Repeat(paddingData, int.MaxValue))
                                     .Take((array.Length + 7) & ~7).ToArray());

      const byte int3 = 0xcc;
      var cpuid = align8(target.CPUID, int3);
      var prefetch128 = align8(target.Prefetch128, int3);
      var prefetch256 = align8(target.Prefetch256, int3);

      var data = prefetch128.Concat(prefetch256)
                            .Concat(cpuid)
                            .ToArray();
      var offset = new
      {
        Prefetch128 = 0,
        Prefetch256 = prefetch128.Length,
        CPUID = prefetch128.Length + prefetch256.Length,
      };

      try
      {
        region_ = NativeMethods.VirtualAlloc(
            IntPtr.Zero, (UIntPtr)data.Length, VirtualAllocType.MEM_COMMIT,
            MemoryProtectionType.PAGE_READWRITE);
        if (region_.IsInvalid)
        {
          return;
        }

        var addr = region_.DangerousGetHandle();
        Marshal.Copy(data, 0, addr, data.Length);
        var oldType = default(MemoryProtectionType);
        var succeeded = NativeMethods.VirtualProtect(
            addr, (UIntPtr)data.Length, MemoryProtectionType.PAGE_EXECUTE, out oldType);
        if (!succeeded)
        {
          GlobalDispose();
          return;
        }

        // GetCurrentProcess returns a pseudo handle.
        // You need not to free a pseudo handle by ClodeHandle.
        var pseudoHandle = NativeMethods.GetCurrentProcess();
        succeeded = NativeMethods.FlushInstructionCache(pseudoHandle, addr, (uint)data.Length);
        if (!succeeded)
        {
          GlobalDispose();
          return;
        }

        prefetch128_ = CreateDelegate<PrefetcherDelegate>(addr, offset.Prefetch128);
        prefetch256_ = CreateDelegate<PrefetcherDelegate>(addr, offset.Prefetch256);
        cpuid_ = CreateDelegate<CPUIDDelegate>(addr, offset.CPUID);
      }
      catch
      {
        GlobalDispose();
        throw;
      }
    }
    public static CPUInfo CPUID(uint type)
    {
      return CPUID(type, 0);
    }
    public static CPUInfo CPUID(uint type, uint sub_type)
    {
      var info = default(CPUInfo);
      info.eax = type;
      info.ecx = sub_type;
      cpuid_(ref info);
      return info;
    }
    public static void Prefetch128(void* address)
    {
      prefetch128_(address);
    }
    public static void Prefetch256(void* address)
    {
      prefetch256_(address);
    }
    // This method is not thread-safe.
    public static void GlobalDispose()
    {
      cpuid_ = DummyCPUID;
      prefetch128_ = DummyPrefetcher;
      prefetch256_ = DummyPrefetcher;
      if (region_ != null && !region_.IsInvalid) { region_.Dispose(); }
    }
  }
}

static class Program
{
  static unsafe void Main(string[] args)
  {
    var info = Win32.Prefetcher.CPUID(0);
    if (info.eax < 1)
    {
      return;
    }

    info = Win32.Prefetcher.CPUID(1);
    var HasMMX = (info.edx & (1 << 23)) != 0;
    var HasSSE = (info.edx & (1 << 25)) != 0;
    var HasSSE2 = (info.edx & (1 << 26)) != 0;
    var HasSSE3 = (info.ecx & (1 << 0)) != 0;

    var buffer = new byte[1024];
    fixed (byte* ptr = buffer)
    {
      Win32.Prefetcher.Prefetch128(ptr);
      Win32.Prefetcher.Prefetch256(ptr);
    }
    Win32.Prefetcher.GlobalDispose();
  }
}

*1:[http://msdn.microsoft.com/en-us/library/aa366599.aspx:title=HeapCreate] + HEAP_CREATE_ENABLE_EXECUTE でも良かったのですが,最終的に書き込み可能属性を落としたかったので今回は VirtualAlloc + VirtualProtect を使いました

数式入力パネルとアプリケーションを連携させる 2 つの方法

Windows 7 では,タブレット PC 向け機能が強化され,数式の手書き入力がサポートされるようになりました.この機能とアプリケーションを連携させるための方法を 2 つほど紹介します.

数式入力パネルからのデータをクリップボード経由で受け取る

『数式入力パネル』は,単体アプリケーションとして動作する数式入力ツールです.このツールは,いわゆるソフトウェアキーボードや文字パレットのように動作し,入力フォーカスを持つアプリケーションに数式情報を送り込みます.
実際には,この機能はクリップボードを利用して実現されています.
『数式入力パネル』は,挿入ボタンが押されると,数式を UTF-8エンコードされた MathML 形式でクリップボードに格納し,Ctrl+V のキーボードイベントを発生させます.このとき,入力フォーカスを持つアプリケーションが,Ctrl+V で貼り付け動作を行い,かつクリップボードに格納された "MathML Presentation" 形式または "MathML" 形式のデータを解釈できることが,『数式入力パネル』と連携するための条件です.

参考

数式入力パネル (Math Input Panel、MIP) は、Tablet PC のタブレットとペンを使用するように設計されています。ただし、タッチスクリーン、外部デジタイザー、あるいはマウスなどの任意の入力デバイスでも使用可能です。MIP は、クリップボードを介して、標準化された数学的なマークアップ言語である MathML フォーマットで認識結果を出力します。MIP で手書きされて認識された数式は、完全に編集可能な形式でレプリケート先アプリケーションに出力されます。テキストを編集したいときは、出力に対して挿入したり編集したりできます。

数式入力パネルでは、数学用マークアップ言語 (MathML) をサポートするプログラムにのみ数式を挿入できます。

数式入力パネルの入力ウィンドウをインプロセスで利用する

COM のインプロセスサーバとして,数式入力パネルの入力ウィンドウを利用することも可能です."%CommonProgramFiles%\Microsoft Shared\ink\micaut.dll" 内に格納された TypeLib から、必要な情報を得ることが出来るでしょう.
参考までに,C# で COM Interop を行ってみたサンプルを置いておきます.

このサンプルは,実行すると数式入力パネルの入力ウィンドウとコンソールウィンドウが表示され,入力ウィンドウの「挿入」を押したときに表示されていた数式が MathML 形式でコンソールに表示されます.

余談: 近年の Microsoft 製品と MathML とクリップボード

近年の Microsoft 製品では MathML の利用が増えています.
Microsoft Word 2007 以降の Microsoft Word や,Microsoft PowerPoint 2010 は,MathML 形式で数式を貼り付けることが可能です.ただし,クリップボードデータ形式については注意が必要でした.手元の Microsoft Office 2010 Beta で試してみたところ,以下のような挙動の違いがありました.

  • Microsoft Word 2010
    • データ形式が "Text" または "Unicode Text" であっても,内容が MathML であれば数式として挿入される.
    • データ形式が "MathML Presentation","MathML" いずれの場合も貼り付け可能
  • Microsoft PowerPoint 2010
    • データ形式が "Text" または "Unicode Text" の場合,内容が MathML であってもただのテキストとして解釈される.
    • データ形式が "MathML" の場合,MathML として解釈する."MathML Presentation" はサポートしない.

実験用の PowerShell スクリプトを以下に示します.

# STA モードで PowerShell を起動する (Windows.Forms.Clipboard のため)
powershell.exe -sta
$null = [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

# E = mc^2
$mathml = '<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mi>E</mml:mi><mml:mo>=</mml:mo><mml:mi>m</mml:mi><mml:msup><mml:mi>c</mml:mi><mml:mn>2</mml:mn></mml:msup></mml:math>'

# UTF-8 で MemoryStream に格納
$ms = New-Object System.IO.MemoryStream(,[System.Text.Encoding]::UTF8.GetBytes($mathml))

# "MathML" という形式名でクリップボードに格納
[Windows.Forms.Clipboard]::SetData("MathML", $ms)

このスクリプトを実行した後で,Microsoft Word 2010 または Microsoft PowerPoint 2010 に貼り付けを行ってみて下さい.数式が貼り付けられるはずです.
これを利用すると,MathML 形式で数式を出力できるソフトウェア,例えば Mathematica から出力した数式を,構造を保ったまま PowerPoint に貼り付ける,といったことが可能です.
また,Microsoft Word 2010 や Microsoft PowerPoint 2010 からクリップボードにコピーする際に,数式を MathML 形式でコピーするように設定することも可能です.この設定を有効にすれば,PowerPoint に書かれていた数式を Mathematica にコピーし,その場で計算を行う,といった使い道も可能です.

MSBuild, 環境変数, Property Functions,ビルド時計算

Visual StudioからBuildしたときに環境変数が取得できない…

MSBuild Extension PackにEnvironmentVariableタスクがあるけれど、標準でついてないなんてありえません。プロパティ式に組み込んでもいいくらいなのに。

$(env:DXSDK_DIR)

みたいな感じで

んー,手元の Visual Studio 2008 でも Visual Studio 2010 でも,$(DXSDK_DIR) で環境変数を取得できているような……
$(PROCESSOR_REVISION) とか,$(OS) とか色々試してみましたが,こちらも特に問題なく取得できました.



というわけで,そもそも何が問題なのか今ひとつよく分からないのですが,MSBuild 4.0 以降では,Property Functions を使って明示的に書くこともできます.

$([System.Environment]::GetEnvironmentVariable("DXSDK_DIR"))

こんな感じで,いくつかの事前定義された .NET クラスライブラリを使用できるようになるわけですね.例えば System.Math を呼び出してビルド時に三角関数を計算,なんてのも可能です.