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 を呼び出してビルド時に三角関数を計算,なんてのも可能です.

書籍紹介: CLR via C#, Third Edition

CLR via C#, Third Edition

CLR via C#, Third Edition


第3版の季節がやって参りました.
そもそもそんな本は知らんがな,という人ももしかしたらいらっしゃるかもしれませんが,『プログラミングMicrosoft .NET Framework』の原書と言えば多くの .NET 開発者には通じるのではないかと思います*1
さて,今回の更新の目玉のひとつが,.NET Framework 4 / C# 4.0 対応というところらしいですが,まあぶっちゃけ第2版を持っている人はいつもの定期更新てところでしょうか.私も,細かい更新とかはとりあえず手元に届いてから調べるつもりです.
なお,著者のブログで更新内容については告知されています.また記事のコメントでのやりとりでは,「eBook 版の予定もある」と書かれています*2.用心深い方や,邦訳版でとりあえず間に合っている人で,発売日に買うかどうか迷っている方は,その辺りが参考になるでしょう.

  • Part I – CLR Basics
    • Chapter 1-The CLR’s Execution Model
      • Added about discussion about C#’s /optimize and /debug switches and how they relate to each other.
    • Chapter 2-Building, Packaging, Deploying, and Administering Applications and Types
      • Improved discussion about Win32 manifest information and version resource information.
    • Chapter 3-Shared Assemblies and Strongly Named Assemblies
      • Added discussion of TypeForwardedToAttribute and TypeForwardedFromAttribute.
  • Part II – Designing Types
    • Chapter 4-Type Fundamentals
      • No new topics.
    • Chapter 5-Primitive, Reference, and Value Types
      • Enhanced discussion of checked and unchecked code and added discussion of new BigInteger type. Also added discussion of C# 4.0’s dynamic primitive type.
    • Chapter 6-Type and Member Basics
      • No new topics.
    • Chapter 7-Constants and Fields
      • No new topics.
    • Chapter 8-Methods
      • Added discussion of extension methods and partial methods.
    • Chapter 9-Parameters
      • Added discussion of optional/named parameters and implicitly-typed local variables.
    • Chapter 10-Properties
      • Added discussion of automatically-implemented properties, properties and the Visual Studio debugger, object and collection initializers, anonymous types, the System.Tuple type and the ExpandoObject type.
    • Chapter 11-Events
      • Added discussion of events and thread-safety as well as showing a cool extension method to simplify the raising of an event.
    • Chapter 12-Generics
      • Added discussion of delegate and interface generic type argument variance.
    • Chapter 13-Interfaces
      • No new topics.
  • Part III – Essential Types
    • Chapter 14-Chars, Strings, and Working with Text
      • No new topics.
    • Chapter 15-Enums
      • Added coverage of new Enum and Type methods to access enumerated type instances.
    • Chapter 16-Arrays
      • Added new section on initializing array elements.
    • Chapter 17-Delegates
      • Added discussion of using generic delegates to avoid defining new delegate types. Also added discussion of lambda expressions.
    • Chapter 18-Attributes
      • No new topics.
    • Chapter 19-Nullable Value Types
      • Added discussion on performance.
  • Part IV – CLR Facilities
    • Chapter 20-Exception Handling and State Management
      • This chapter has been completely rewritten. It is now about exception handling and state management. It includes discussions of code contracts and constrained execution regions (CERs). It also includes a new section on trade-offs between writing productive code and reliable code.
    • Chapter 21-Automatic Memory Management
      • Added discussion of C#’s fixed state and how it works to pin objects in the heap. Rewrote the code for weak delegates so you can use them with any class that exposes an event (the class doesn’t have to support weak delegates itself). Added discussion on the new ConditionalWeakTable class, GC Collection modes, Full GC notifications, garbage collection modes and latency modes. I also include a new sample showing how your application can receive notifications whenever Generation 0 or 2 collections occur.
    • Chapter 22-CLR Hosting and AppDomains
      • Added discussion of side-by-side support allowing multiple CLRs to be loaded in a single process. Added section on the performance of using MarshalByRefObject-derived types. Substantially rewrote the section on cross-AppDomain communication. Added section on AppDomain Monitoring and first chance exception notifications. Updated the section on the AppDomainManager class.
    • Chapter 23-Assembly Loading and Reflection
      • Added section on how to deploy a single file with dependent assemblies embedded inside it. Added section comparing reflection invoke vs bind/invoke vs bind/create delegate/invoke vs C#’s dynamic type.
    • Chapter 24-Runtime Serialization
      • This is a whole new chapter that was not in the 2nd Edition.
  • Part V – Threading
    • Chapter 25-Threading Basics
      • Whole new chapter motivating why Windows supports threads, thread overhead, CPU trends, NUMA Architectures, the relationship between CLR threads and Windows threads, the Thread class, reasons to use threads, thread scheduling and priorities, foreground thread vs background threads.
    • Chapter 26-Performing Compute-Bound Asynchronous Operations
      • Whole new chapter explaining the CLR’s thread pool. This chapter covers all the new .NET 4.0 constructs including cooperative cancelation, Tasks, the aralle class, parallel language integrated query, timers, how the thread pool manages its threads, cache lines and false sharing.
    • Chapter 27-Performing I/O-Bound Asynchronous Operations
      • Whole new chapter explaining how Windows performs synchronous and asynchronous I/O operations. Then, I go into the CLR’s Asynchronous Programming Model, my AsyncEnumerator class, the APM and exceptions, Applications and their threading models, implementing a service asynchronously, the APM and Compute-bound operations, APM considerations, I/O request priorities, converting the APM to a Task, the event-based Asynchronous Pattern, programming model soup.
    • Chapter 28-Primitive Thread Synchronization Constructs
      • Whole new chapter discusses class libraries and thread safety, primitive user-mode, kernel-mode constructs, and data alignment.
    • Chapter 29-Hybrid Thread Synchronization Constructs
      • Whole new chapter discussion various hybrid constructs such as ManualResetEventSlim, SemaphoreSlim, CountdownEvent, Barrier, ReaderWriterLock(Slim), OneManyResourceLock, Monitor, 3 ways to solve the double-check locking technique, .NET 4.0’s Lazy and LazyInitializer classes, the condition variable pattern, .NET 4.0’s concurrent collection classes, the ReaderWriterGate and SyncGate classes.

The plan is that this book WILL have an eBook available for it. I do not know more of the details just now.

(第2版に引き続き,第3版の翻訳も行われると良いなぁと願う人々が踏むべき過去の) 参考リンク

*1:著者である Jeffrey Richter 氏の名は,『Advanced Windows 第5版 上』『Advanced Windows 第5版 下』の原書の著者として,Win32 時代からの開発者にはお馴染みですね.

*2:Jeffrey Richter 氏と言えば,『なんで Advanced Windows の(原書の)電子ブック版は無くなったの? - NyaRuRuの日記』なんて話も過去にはあっただけに,『CLR via C#, Third Edition』に eBook 版の予定があるというのは嬉しいニュースですね.

別スレッドでリソースを解放することのあれそれ

Boost.SmartPtr:shared_ptr + weak_ptr(Cryolite) の 24 分目あたり.

shared_ptr<void> による遅延解放 vector<shared_ptr<void *> > to_be_disposed; shared_ptr<HeavyToDispose1> px(…); shared_ptr<HeavyToDispose2> py(…); … // ここで削除して処理が止まると困る… to_be_disposed.push_back(px); px.reset(); to_be_disposed.push_back(py); py.reset(); … // 適当なタイミング or 別スレッドで // to_be_disposed.clear() を実行

の部分に関して,

別のスレッドでのリソース解放は,スレッド親和性をもつリソースを破棄には対応できないことに注意.この辺はCLRのファイナライザスレッドも同様の問題を抱えている #boostjp

2 days ago from MiniTwitter


NyaRuRu

てな感じにつぶやいた件について,

@NyaRuRu さんの http://twitter.com/NyaRuRu/status/6594169320 の発言がいまいち理解できていないので,誰か優しく教えてくれないかにゃー.

7:48 AM Dec 13th Twitで


という質問を頂いた件について遅くなりましたが補足,というほどでもないんですが,軽くいくつかつらつらと.
以下,基本的に Win32 + .NET (CLR) な文脈で.

ファイナライザスレッド

MS CLR のファイナライザは,ファイナライザスレッドという別のスレッドで実行されます *1.なので,あるリソース解放処理が別スレッドでも行えるか否かという観点は,.NET な人には割とお馴染みなはず.
よく訓練された .NET プログラマは,ファイナライザを書きながら「この処理はファイナライザスレッドで実行しても大丈夫かにぇ?」と呟くといいます[要出典]

スレッド親和性を持つリソースについて

Remarks

A thread cannot use DestroyWindow to destroy a window created by a different thread.

The methods IDirect3DDevice9::Reset, IUnknown, and IDirect3DDevice9::TestCooperativeLevel must be called from the same thread that used this method to create a device.

このように,しれっとドキュメントに制限が書いてあるのですな.コンパイル時エラーにはならないのでほんと注意.

スレッドごとに設定可能なコンテキスト

解放処理を書くときに,思わずコンストラクタと同じ実行コンテキストを仮定しがちなのですが,ファイナライザスレッドのコンテキストがどうなっているかはよく分からんということで注意.
Windows で per thread な設定を思いつくままにいくつか列挙.

さらに,ファイナライザ中でこの手の設定を弄ると,後続するファイナライザも影響を受けるわけで.
スレッドプールとかでも似たような話はあるんでしょうけど.

ファイナライザのタイムアウト

vector<shared_ptr<void *> > のように,対象を区別せずに一様に解放処理を行う場合,

  1. 本当に致命的なことは起きないと仮定する
  2. 万一致命的なことが起きた場合に備えて Watchdog 等も準備

の判断が場合によっては難しいかもですにゃーとか.
たとえば,全てのファイナライザが実行し終わるまでアプリケーションが終了できないようになっていると,ひとつのファイナライザが無限に待機するだけで永遠にアプリケーションが終了できなくなります.同様に,プラグインを低い権限で動かすようなアプリケーションを作っているときに,プラグインが生成するオブジェクトのファイナライザで無限ループするだけでホストアプリケーション全体がやばいことになってしまうようでは困ります.
とまあそんなこんなで,ファイナライザやアプリケーションの終了処理にタイムアウトを設定できるようにしようとか,本当に重要なファイナライザとそうでないファイナライザを分けようとか,色々と夢や風呂敷が広がったりすると,結局いまの CLR と似たものができあがりそうだったり.

余談: 本当に保護すべきリソースは何か?

微妙に関係ない話ですが,以下のような不具合があるとき,どちらの修正を優先するかという視点もあります.

  • 軽微なメモリリークはあるが,いつプロセスを強制終了してもファイルは壊れないモジュール
  • 単体では完璧に動くが,特定タイミングで外部からプロセスを強制終了すると確実にファイルが破損するモジュール

Windows アプリケーションは,単にその実行ファイル名が怪しいという理由だけでユーザーに不審に思われてタスクマネージャから殺される世界です.当然デストラクタは実行されません.ログオフ時にちょっと処理が遅れただけでも強制終了されます*2.当然デストラクタは実行されません.
例外安全でメモリリークもなくデストラクタで確実にリソースが解放されるプログラムも大切ですが,どのコード行でプロセスが突然終了しても守られるべきデータは無事というプログラムも大事だと思う今日この頃.

*1:多くの JVM もそんな感じなんでしょうけど

*2:もちろんあがくことはできます

書籍紹介: .NETのクラスライブリ設計 開発チーム直伝の設計原則、コーディング標準、パターン (Framework Design Guidelines 2nd Edition)

.NETのクラスライブラリ設計 開発チーム直伝の設計原則、コーディング標準、パターン (Microsoft.net Development Series)

.NETのクラスライブラリ設計 開発チーム直伝の設計原則、コーディング標準、パターン (Microsoft.net Development Series)


Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries (2nd Edition)』の邦訳と言えば分かる人はまあ説明は不要ですかね.
本書は,.NET Framework のデザインと実装を経て Microsoft が培ったコーティング標準と設計パターン集です..NET に限らず,いわゆるフレームワークの作成経験のある人は,読めば必ず何かの再発見があるでしょう.もちろん .NETフレームワークを作っている人なら読むべき一冊です.
また,本書の特徴として超豪華メンバーによる注釈が挙げられます.各トピックには,様々なバックグラウンドをもつ専門家*1によって注釈が付けられており,本書の内容をとてもバランスのとれたものにしています.ルール集にありがちな「その理屈はおかしい」系の読後感が本書にないのは,やはりこの注釈の存在が大きいのでしょう.
本書の内容が日本語で読めるようになるのは,本当にすばらしいことです.@yfakariya さんと @matarillo さんに拍手!

*1:経歴を見るだけでくらくら来る Annotator が多数参加.Anders Hejilsberg や Paul Vick,Herb Sutter といった大御所ももちろん参加しています.

単位の話

だいぶ前に書こうと思って書き忘れてたのを今頃書いてみたり.
ishisaka さんが Units of Measure をいたくお気に入りの様子.

F#のUnits of MeasureはそれだけでF#を勉強してもいいと思えるぐらいすごいんだけど、世の中的には反応薄いか。無事ね素アプリばっか作っているプログラマに見せてもだめなんだろうなぁ

素アプリすらまともに作れていない気がする自分ですが,単位込みで計算するという発想自体は割と何度も目にしている気が.例えば Mathematica 使いで,単位込みの計算ができることを知らない人はモグリさんじゃなかろうか.

他にも,C++ で以前お世話になっていたヘッダファイルに,SystemOfUnits.h というものがあります.こいつは高エネルギー物理業界で広く使われている C++ 用ライブラリ CLHEP の一部で,厳密に次元のチェックをしてくれるわけではないものの,ソースコードの見た目上はなんとなく単位付きで書いてあるように書けるというもの.
(追記)まああるだろうなぁと思って見てみたらやっぱり boost にもありました.こっちは型チェック有りっぽい.

長さも重さも変数の型は double で表現していて、 うっかり引数の順番を間違えてしまったりすることは無いでしょうか。 ちょっと複雑な物理シミュレーションの式を書いていて、 掛け算を1個忘れてしまったりすることは無いでしょうか。 あるいは、radian と degree がごちゃごちゃに混ざって変換を間違えてしまったりすることは……

……というときに、型に単位の情報まで含めてチェックできるようにしてしまおう!という、 テンプレートメタプログラミングの古典のようなテーマがありますが、 それをきっちり実装したのがこのライブラリです。デフォルトでは上記の例のように quantity 型は単位付き double 型になりますが、第二テンプレート引数で型を指定することで、 int や complex 型を使うことも可能です。

あと使用経験皆無なものの,Curl も単位をサポートしているとか.

数値に単位をつけられる

Curl では数量型という概念がある。これは、数値に単位系をくっつけたもの。これを使うと、コードがたいへん分かりやすくなる。

例えば「10kg」と書けば、重さを表す数量型になる。単なる「10」ではない、単位を伴った数量なのである。だから「10kg==10000g」は true であるし、「10kg==10m」は false である (そもそも異なる単位系の数量型を比較すると、コンパイル時にエラーとなる)。

また既存の数量型から、新しい数量型を定義することもできる。たとえば、Velocity は Distance を Time で割った数量型だという定義が可能で、54km / 3.0hr = 5.0m*s^-1 という値がそれになる。もちろん単位系まで考えてコンパイルエラーがでる。

Ruby on Rails では 1.hour とか 2.month とかできて大変便利だけど、それを知っている人なら Curl の数量型がいかに便利かわかるだろう。数量型はさらに Time や Date 以外の SI 単位系全般で使えて、かつ型システムと結びついているからすごい。

ここまでくると、静的な型があってよかったと誰もが思うだろう (Rubyist や PHPer でもそう思うはず)。数量型を知ると、C#Java のような型システムがいかに時代遅れかということを思い知らされる。

ただ,いかにも Web 用というか,現行の Curl の単位サポートは,(計算の中間結果も含めた次元の)値の範囲にかなり狭い制限があるみたい.人工衛星とか飛ばす場合には『例えば、Curl を避ける』と書いておいても良さそうではある.

Curl 言語のこのリリースでは、単位を -8 乗より小さくすることや、7 乗より大きくすることはできません。たとえば、5(m^12) または 5(m^-12) にすることはできません。計算の中間結果がこうした累乗を超えないように注意する必要があります。

更新履歴

  • 2010年2月2日
    • "(計算の中間結果も含めた次元の)" を追記.