連載

C#入門

第21回 ポインタを使用できるunsafe

(株)ピーデー
川俣 晶
2002/02/16


本当にunsafeコードが欲しくなる事例

 以下は、本当にunsafeコードが欲しくなる事例として作成したものである。24bitのBMPファイルを読み込んで、ネガ反転処理を行うものである。まず、.NET Frameworkのクラス・ライブラリを活用した正統派のソースをご覧いただこう。

 1: using System;
 2: using System.IO;
 3: using System.Drawing;
 4:
 5: namespace ConsoleApplication4
 6: {
 7:   class Class1
 8:   {
 9:     private static void NegativeBMP( string inputFileName, string outputFileName )
10:     {
11:       Bitmap bitmap = new Bitmap( inputFileName );
12:       for( int y=0; y<bitmap.PhysicalDimension.Height; y++ )
13:       {
14:         for( int x=0; x<bitmap.PhysicalDimension.Width; x++ )
15:         {
16:           Color c = bitmap.GetPixel( x, y );
17:           bitmap.SetPixel( x, y, Color.FromArgb((byte)~c.R,(byte)~c.G,(byte)~c.B) );
18:         }
19:       }
20:       bitmap.Save( outputFileName );
21:     }
22:     static void Main(string[] args)
23:     {
24:       const string inputFileName = "c:\\test1.bmp";
25:       const string outputFileName = "c:\\test2.bmp";
26:       NegativeBMP( inputFileName, outputFileName );
27:     }
28:   }
29: }
BMPファイルのネガ反転処理を行うサンプル・プログラム9
.NET Frameworkのクラス・ライブラリにあるBitmapクラスを使用して各ピクセルの反転を行っているバージョン。

 次に、unsafeコードを用いた例をご覧いただこう。

 1: using System;
 2: using System.IO;
 3: using System.Runtime.InteropServices;
 4:
 5: namespace ConsoleApplication3
 6: {
 7:   [StructLayout(LayoutKind.Sequential,Pack=1)]
 8:   struct BITMAPFILEHEADER
 9:   {
10:     public ushort   bfType;
11:     public uint bfSize;
12:     public ushort   bfReserved1;
13:     public ushort   bfReserved2;
14:     public uint bfOffBits;
15:   }
16:   [StructLayout(LayoutKind.Sequential,Pack=1)]
17:   struct BITMAPINFOHEADER
18:   {
19:     public uint biSize;
20:     public int  biWidth;
21:     public int  biHeight;
22:     public ushort   biPlanes;
23:     public ushort   biBitCount;
24:     public uint biCompression;
25:     public uint biSizeImage;
26:     public int  biXPelsPerMeter;
27:     public int  biYPelsPerMeter;
28:     public uint biClrUsed;
29:     public uint biClrImportant;
30:     public const int BI_RGB = 0;
31:   };
32:
33:   class Class1
34:   {
35:     unsafe private static void NegativeBMP( string inputFileName, string outputFileName )
36:     {
37:       FileStream inputFile = new FileStream(inputFileName,FileMode.Open);
38:       byte [] fileImage = new byte[inputFile.Length];
39:       inputFile.Read(fileImage,0,(int)inputFile.Length);
40:       inputFile.Close();
41:
42:       fixed( byte * basePtr = &fileImage[0] )
43:       {
44:         BITMAPFILEHEADER * bitmapFileHeader = (BITMAPFILEHEADER *)basePtr;
45:         BITMAPINFOHEADER * bitmapInfoHeader = (BITMAPINFOHEADER * )(basePtr + sizeof(BITMAPFILEHEADER));
46:         byte * bits = basePtr + bitmapFileHeader->bfOffBits;
47:         if( bitmapInfoHeader->biBitCount != 24 || bitmapInfoHeader->biCompression != BITMAPINFOHEADER.BI_RGB )
48:         {
49:           Console.WriteLine( "Error: 24bit RGB Bitmap Only");
50:           return;
51:         }
52:         int bytesInLine = (bitmapInfoHeader->biWidth+3)/4*4;
53:         int totalBytes = bytesInLine * bitmapInfoHeader->biHeight * 3;
54:         for( int i = 0; i<totalBytes; i++ )
55:         {
56:           *bits = (byte)~*bits;
57:           bits++;
58:         }
59:       }
60:
61:       FileStream outputFile = new FileStream(outputFileName,FileMode.Create);
62:       outputFile.Write(fileImage,0,fileImage.Length);
63:       outputFile.Close();
64:     }
65:     static void Main(string[] args)
66:     {
67:       const string inputFileName = "c:\\test1.bmp";
68:       const string outputFileName = "c:\\test2.bmp";
69:       NegativeBMP( inputFileName, outputFileName );
70:     }
71:   }
72: }
ポインタを使用してネガ反転処理を行うサンプル・プログラム10
BMPファイルを配列に読み込み、そのサイズを求めてから、ポインタ経由で各ピクセルの値の反転を行っているバージョン。処理は複雑になるが、その実行速度はBitmapクラスを用いたバージョンに比べて格段に速くなる。

 これを実行すると以下のように画像が加工される。結果はどちらも同じになる。

入力に用いた24bitのビットマップ画像

ネガ反転処理を行った出力画像

 さて、ソースを見比べれば、正統派の方がずっと短く読みやすいことが分かるだろう。しかも、正統派のソースは、24bit BMPに制限されず、Bitmapクラスがサポートするあらゆるデータ型を扱える。それにもかかわらず、unsafeコードが欲しくなる理由は、圧倒的な性能差にある。Pentium 4/1.5GHzのマシンで、正統派のコードは実行に21秒かかったが、unsafe版の方は0.5秒ほどで処理を完了した。

 もちろん、ここで取り上げた正統派のソースはあまりにも愚直すぎるので、unsafeコードを使わなくても、低レベルの処理に置き換えることで、かなりの高速化を図ることは可能だ。例えば、byte配列で一気に反転処理を行えば、性能面で大差ないコードを書くこともできるだろう。しかし、このような単純な処理で済まない場合、型を持ったポインタを使用できるunsafeコードの方が、よりエレガントに問題を解決できるだろう。

まとめ

 ここまで説明してきたが、やはりunsafeコードは使わなくて済めば使わないに越したことはない機能である。できるだけ使わなくて済むように頑張ろう。

 さて、次回は最終回として、これまでの連載で取りこぼしてきたいくつかのトピックを取り上げたいと考えている。

 それでは次回もLet's See Sharp!End of Article


 INDEX
  第21回 ポインタを使用できるunsafe
    1.unsafeコードとは何か
    2.固定型と移動型
    3.配列にポインタ・アクセスする
    4.ポインタを演算する
  5.本当にunsafeコードが欲しくなる事
 
「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 記事ランキング

本日 月間