連載:[完全版]究極のC#プログラミング

Chapter19 小さな改善とコンパイラの新機能

川俣 晶
2010/05/24
Page1 Page2 Page3 Page4

19.3 固定サイズバッファ

 できるだけ避けて通りたいが、まれに、どうしても避けられなくなるのが「unsafeコンテキスト」である。unsafeコンテキストとはポインタを使用できるunsafeコードを記述できる文脈を示す。

 プログラムとは、実現すべき機能を実現するところから価値が生まれるのであって、そのために避けて通れないことは受け入れるしかない。理想論だけでプログラムは書けないのである。そのような意味で「unsafeコンテキストのような危険な機能が存在することはおかしい」という批判は的外れといえる。unsafeコンテキストに類する機能を持たないプログラミング言語を使って書く場合は、結局、より危険な別言語でその箇所を記述してプログラムに取り込んで使うしかない。しかし、たとえば、C言語で書いたモジュールを取り込むぐらいなら、C言語と比較して飛躍的に安全度が高いC#のunsafeコンテキストを使うほうが、はるかに安心といえる。

 さて、そうはいってもunsafeコンテキストはさまざまな面倒を抱え込んでいる。特に外部とデータをやり取りする場合の「型のマーシャリング」は面倒なことになる。それを支援するためにさまざまな機能が用意されているのだが、C# 1.xでは決定的に足りないものがあった。それは、構造体の中に固定サイズの配列を埋め込んだ形で宣言できないことだった。

 たとえば、WindowsのAPIが定義して使用する(C/C++で定義された)構造体には、配列が埋め込まれていることは珍しくない。特に、データの名前として文字列を格納する場合は、文字列を格納するための配列があってあたりまえといえる。

 しかし、C# 1.xは構造体の内部に配列を埋め込むことができない。なぜかといえば、C#の配列は、配列単体で独立したオブジェクトとして扱われるためである。構造体の中に入れることができるのは、配列オブジェクトへの参照であって、配列そのものではない。

 たとえば、Win32 APIで論理フォントを指定するLOGFONT構造体は、フォントサイズなどの数値と並んでフォント名を格納する配列を含む。このような構造体はそのままC# 1.xで記述することができなかったのである。これに対処するため、文字列に変換するマーシャリング指定を行うためにMarshalAs属性を埋め込むなどの方策が採られていたが、万全とはいえない。マーシャリング機能が提供する変換機能の枠を超えてしまうと扱えないし、マーシャリング指定を誤るとそれだけでプログラムが暴走する可能性も存在するのである。

 そこで、C# 2.0では「固定サイズバッファ」という機能を用意している。

 固定サイズバッファは、C# 1.xにも存在したfixedステートメントによる「移動されない変数」を構造体に拡張したものといえる(移動されないという特徴は固定サイズバッファにも適用される)。

 使い方は簡単で、次のように配列を「fixedキーワード」を付けて宣言するだけである。これで、配列のサイズを直接書き込んで指定することが可能になる

public fixed int AnArray[100];

 配列の型はbool、byte、char、short、int、long、sbyte、ushort、uint、ulong、float、doubleのみが使用できる。

 さて、実際に使用した例として、API経由でフォント名を取得するリスト19.3を次ページに用意した。C# 1.xを用いて実現する場合、通常は、MarshalAs属性が不可欠となる事例である。

 ちなみに、このサンプルプログラムでは、フォント名はushort型の配列として取得され、文字列オブジェクトとしては取得できていないことにも注意を払っていただきたい。MarshalAs属性を使えば文字列として受け渡すこともできるが、固定サイズバッファを使うと、それはできないのである。ありのままの構造体を受け渡すとは、そういうことである。

 なお、リスト19.3はVisual Studio 2008でWindowsアプリケーションのプロジェクトを作成し、Labelコントロールをフォームに貼ったうえで、プロジェクトのプロパティの[ビルド]タブにある「アンセーフコードの許可]にチェックを入れた状態で動作可能となる。WindowsFormsApplicationXは、生成した名前空間名をそのまま使う。

using System;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WindowsFormsApplicationX
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public const int LF_FACESIZE = 32;

        [StructLayout(LayoutKind.Sequential)]
        public unsafe struct LOGFONT
        {
            public int lfHeight;
            public int lfWidth;
            public int lfEscapement;
            public int lfOrientation;
            public int lfWeight;
            public byte lfItalic;
            public byte lfUnderline;
            public byte lfStrikeOut;
            public byte lfCharSet;
            public byte lfOutPrecision;
            public byte lfClipPrecision;
            public byte lfQuality;
            public byte lfPitchAndFamily;
            public fixed ushort lfFaceName[LF_FACESIZE]; // 固定サイズ
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            object boxedLogFont = new LOGFONT();
            this.Font.ToLogFont(boxedLogFont);

            LOGFONT logFont = (LOGFONT)boxedLogFont;
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < LF_FACESIZE; i++)
            {
                unsafe // unsafeコンテキスト
                {
                    if (logFont.lfFaceName[i] == 0) break;
                    sb.Append((char)logFont.lfFaceName[i]);
                };
            }
            label1.Text = sb.ToString();
        }
    }
}
リスト19.3 Win32 API経由でフォント名を取得する

 論理フォントを取得するためのFontクラスのToLogFontメソッドは、クラスライブラリが提供するメソッドであるにもかかわらず、その引数の型の定義はクラスライブラリに含まれておらず、object型を受け渡すようになっている。そのため、プログラマーが自分で論理フォント(LOGFONT)構造体の定義を用意しなければならない。


 INDEX
  [完全版]究極のC#プログラミング
  Chapter19 小さな改善とコンパイラの新機能
    1.19.1 インライン警告制御「#pragma warning」
    2.19.2 部分メソッド定義
  3.19.3 固定サイズバッファ
    4.19.4 volatileがIntPtr型およびUIntPtr型へ適用できる/練習問題
 
インデックス・ページヘ  「[完全版]究極のC#プログラミング」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間