SSD なら動作を変えるアプリケーションを作る

Windows 7 以降の OS では,SSD 上のボリュームに対してデフラグがスケジューリングされません.これと同様に,ドライブ特性に応じた動作戦略の変更をアプリケーションでも行いたいこともあるでしょう.そのような場合にアプリケーションに組み込めるように,簡単なサンプルコードを書いてみます.
元ネタは,
Windows 7 Disk Defragmenter User Interface Overview - The Storage Team at Microsoft - File Cabinet Blog - Site Home - TechNet Blogs
で解説されているアルゴリズムです.

以下,物理ドライブ "\\.\PhysicalDrive0" について,"no seek penalty" かどうかと,"nominal media rotation rate" かどうかの取得を行います.なお,前者はディスクポートドライバの動作に依存するため,Windows 7 以降でなければ使用できませんが,管理者権限なしで動作するというメリットがあります.後者は ATA8-ACS に対応したデバイスであれば,Windows 7 より前の OS でも動作しますが,動作には管理者権限が必要です.
上記記事に解説されているように,これらの関数を組み合わせて使ってもよいですし,どちらか一方のみを使いある程度の false negative は諦めるという手もあります.

#include <Windows.h>
#include <WinIoCtl.h>
#include <Ntddscsi.h>
#include <Setupapi.h>

#include <iostream>
#include <string>

using std::wstring;
using std::wcout;
using std::endl;

// Returns S_OK if |physical_drive_path| has no seek penalty.
// Returns S_FALSE otherwise.
// Returns E_FAIL if fails to retrieve the status.
// |physical_drive_path| should be something like
// "\\\\.\\PhysicalDrive0".
HRESULT HasNoSeekPenalty(const wstring& physical_drive_path) {
    // We do not need write permission.
  const HANDLE handle = ::CreateFileW(
      physical_drive_path.c_str(), FILE_READ_ATTRIBUTES, 
      FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
      OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  if (handle == INVALID_HANDLE_VALUE) {
    return E_FAIL;
  }

  STORAGE_PROPERTY_QUERY query_seek_penalty = {
    StorageDeviceSeekPenaltyProperty,  // PropertyId
    PropertyStandardQuery,             // QueryType,
  };
  DEVICE_SEEK_PENALTY_DESCRIPTOR query_seek_penalty_desc = {};
  DWORD returned_query_seek_penalty_size = 0;
  const BOOL query_seek_penalty_result = DeviceIoControl(
    handle, IOCTL_STORAGE_QUERY_PROPERTY,
    &query_seek_penalty, sizeof(query_seek_penalty),
    &query_seek_penalty_desc,
    sizeof(query_seek_penalty_desc),
    &returned_query_seek_penalty_size, NULL);
  CloseHandle(handle);
  if (!query_seek_penalty_result) {
    // failed to retrieve data.
    return E_FAIL;
  }

  return !query_seek_penalty_desc.IncursSeekPenalty ?
      S_OK : S_FALSE;
}

// Returns S_OK if |physical_drive_path| has nominal media
// rotation rate in terms of ATA8-ACS specification.
// http://www.t13.org/Documents/UploadedDocuments/docs2007/D1699r4-ATA8-ACS.pdf#Page=179
// Returns S_FALSE otherwise.
// Returns E_FAIL if fails to retrieve the status.
// |physical_drive_path| should be something like
// "\\\\.\\PhysicalDrive0".
HRESULT HasNominalMediaRotationRate(
    const wstring& physical_drive_path) {
  // In order to use IOCTL_ATA_PASS_THROUGH,
  // We *do* need read/write permission, which means
  // that the caller has admin privilege.
  const HANDLE handle = CreateFileW(
    physical_drive_path.c_str(),
    GENERIC_READ | GENERIC_WRITE, 
    FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  if (handle == INVALID_HANDLE_VALUE) {
    return E_FAIL;
  }

  struct ATAIdentifyDeviceQuery {
    ATA_PASS_THROUGH_EX header;
    WORD data[256];
  };

  ATAIdentifyDeviceQuery id_query = {};
  id_query.header.Length = sizeof(id_query.header);
  id_query.header.AtaFlags = ATA_FLAGS_DATA_IN;
  id_query.header.DataTransferLength =
      sizeof(id_query.data);
  id_query.header.TimeOutValue = 3;  // sec
  id_query.header.DataBufferOffset =
      sizeof(id_query.header);
  id_query.header.CurrentTaskFile[6] =
      0xec;  // ATA IDENTIFY DEVICE command

  DWORD retval_size = 0;
  const BOOL result = DeviceIoControl( 
    handle, IOCTL_ATA_PASS_THROUGH,
    &id_query, id_query.header.DataTransferLength,
    &id_query, id_query.header.DataTransferLength,
    &retval_size, NULL);
  if (!result) {
    return E_FAIL;
  }
  const int kNominalMediaRotRateWordIndex = 217;
  // RPM == 1 means this is non-rotate device
  return id_query.data[kNominalMediaRotRateWordIndex] == 1 ?
      S_OK : S_FALSE;
}

int main() {
  const wstring kDrive = L"\\\\.\\PhysicalDrive0";

  switch (HasNoSeekPenalty(kDrive)) {
    case S_OK:
      wcout << kDrive << L" has no seek penalty." << endl;
      break;
    case S_FALSE:
      wcout << kDrive << L" has seek penalty." << endl;
      break;
    default:
      wcout << L"failed to retrieve the status." << endl;
      break;
  }
 
  switch (HasNominalMediaRotationRate(kDrive)) {
    case S_OK:
      wcout << kDrive << L" has no seek penalty." << endl;
      break;
    case S_FALSE:
      wcout << kDrive << L" has seek penalty." << endl;
      break;
    default:
      wcout << L"failed to retrieve the status." << endl;
      break;
  } 

  return 0;
}

さて,"C:\Windows\System32\calc.exe" のようなパス名から,そのファイルが存在する物理ドライブ名を取得するにはどうすればよいでしょうか?
これには,

  1. GetVolumePathName API を利用してパスからマウントポイントを取得する
  2. GetVolumeNameForVolumeMountPoint API を利用して,論理ボリューム名を取得する
  3. IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS IO Control を利用して,物理ドライブの番号を取得する

という手順を踏みます.後は,得られた番号を "\\.\PhysicalDrive" の末尾に付ければ,物理ボリューム名が判明します.
なお,ダイナミックボリュームでは,複数の物理ドライブにまたがった論理ボリュームが作成可能なことに注意してください.以下の関数は,指定されたパスに対応する物理ドライブ番号を返します.

vector<int> GetExtentsFromPath(const wstring& path) {
  vector<int> extents;

  wchar_t mount_point[1024];
  if (!GetVolumePathNameW(
        path.c_str(), mount_point, ARRAYSIZE(mount_point))) {
    return extents;
  }

  wchar_t volume_name[1024];
  if (!GetVolumeNameForVolumeMountPointW(
          mount_point, volume_name, ARRAYSIZE(volume_name))) {
    return extents;
  }

  wstring volume = volume_name;

  // remove trailing '\\'
  volume.resize(volume.size() - 1);

  // We do not need write permission (nor admin rights).
  const HANDLE volume_handle = CreateFileW(
      volume.c_str(), FILE_READ_ATTRIBUTES, 
      FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL, NULL);

  VOLUME_DISK_EXTENTS initial_buffer = {};
  DWORD returned_size = 0;
  const BOOL get_volume_disk_result = DeviceIoControl(
      volume_handle, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
      NULL, 0, &initial_buffer, sizeof(initial_buffer),
      &returned_size, NULL);
  const DWORD query_size_error = GetLastError();
  if (get_volume_disk_result != FALSE &&
      initial_buffer.NumberOfDiskExtents == 1) {
    extents.push_back(initial_buffer.Extents[0].DiskNumber);
    return extents;
  }
  if (query_size_error != ERROR_MORE_DATA) {
    return extents;
  }

  const size_t buffer_size =
      sizeof(initial_buffer.NumberOfDiskExtents) + 
      sizeof(initial_buffer.Extents) *
      initial_buffer.NumberOfDiskExtents;
  char* underlaying_buffer = new char[buffer_size];
  VOLUME_DISK_EXTENTS* query_buffer =
      reinterpret_cast<VOLUME_DISK_EXTENTS *>(
          &underlaying_buffer[0]);
  const BOOL devide_ioc_result = DeviceIoControl(
      volume_handle, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
      NULL, 0, 
      query_buffer, buffer_size, &returned_size, NULL);
  const DWORD device_detail_result_error = ::GetLastError();
  if (!!devide_ioc_result) {
    for (DWORD i = 0;
         i < query_buffer->NumberOfDiskExtents; ++i) {
      extents.push_back(query_buffer->Extents[i].DiskNumber);
    }
  }
  delete[] underlaying_buffer;
  CloseHandle(volume_handle);
  return extents;
}

幸いなことに,上記処理は管理者権限なしでも動作します.Windows 7 以降では,管理者に昇格する必要なしに,SSD 上で動作しているらしいかどうかをアプリケーションが判別できるということになります.

Metro スタイルアプリケーションと IME

Metro では,AppContainer という特殊な環境でアプリケーションが実行されます.IME DLL は *1,対象の Metro スタイルアプリケーションプロセスに読み込まれ,AppContainer の管理下で動作することが求められます.

実際,Microsoft は Windows 8 Release Preview の公開に合わせ,Guidelines and checklist for IME development (Metro style apps) というガイドラインの提供を開始しました*2.同ドキュメントには,AppContainer 内で IME の機能を実装する上で,次のようなケースに注意せよとあります.

  • 辞書ファイルの置き場所
  • インターネットを利用したアップデート
  • 学習機能
  • プロセス間での(設定や学習)情報の共有

同ドキュメントには,IME の学習機能や,プロセス間での(設定や学習)情報の共有機能の実装方法として,その AppContainer で可能であればウェブサービスを経由してこれらの機能を実装するよう書かれています.Mac や Android のように IME プロセスを分離してくれていれば,IME 単体に別のアクセスコントロールを適用することも可能だったのでしょうが……

なお,Metro アプリケーションとデスクトップアプリケーションでは,利用可能なテクノロジに違いがあります.詳細については API reference for Metro style apps から辿ることができます.Metro 対応 IME では IMM32 ではなく TSF を使う必要があることはガイドラインで明記されていましたが,その他の API についてははっきりとは書かれていません.公開されると言われている Metro 対応 IME サンプルでも読まないとはっきりしない予感がします.
なお,こうして作られた IME DLL は,従来のデスクトップアプリケーションに読み込まれても動作する必要があります.まさに Run Anywhere,Universal Binary というわけです.

*1:Metro スタイルアプリケーションは,TSF のみのサポートとなりますから,IME というより Text Input Processor; TIP DLL と呼ぶ方が適切かもしれませんが

*2:一方で,予定されていた Metro アプリ用 IME サンプルに関する情報は[http://social.msdn.microsoft.com/Forums/en-US/winappswithnativecode/thread/6abc8b91-110c-4d60-b5b7-e113144902d9:title=削除されました].

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 を使いました