2008年1月18日金曜日

.NET での共用体

.NET Framework の 共通言語仕様(CLS)には共用体は存在しません。また、C#にもVB.NETにも共用体は存在しません。共用体自体はそれほど使う機会が多いというわけではないですし、また、なければならないというものでもありません。しかし、ごく稀にあると便利かなと思うことがあります。そういうときに、.NET FrameworkのStructLayout属性とFieldOffset属性を用いるとC#やVB.NETの構造体でも共用体の機能を再現することができます。それではまずStructLayout属性について見てみることにします。この属性は構造体(クラスも可)に対して適用し、この属性を適用することで構造体の各メンバ変数のメモリ上での配置方法を指定することができます。具体的には、LayoutKind.Autoを指定するとコンパイラが最も適した方法で配置し、LayoutKind.Sequentialを指定するとメモリ内に連続して順番に配置され、LayoutKind.Explicitを指定するとプログラム側で明示的に位置を指定しなければなりません。また、ここでLayoutKind.Explicitを指定すると、全てのメンバ変数に対して明示的に位置を指定しなければなりません。そこで、位置を指定するために使用する属性がFieldOffset属性です。この属性では構造体の先頭から各メンバ変数の先頭までのオフセット値をバイト単位で指定します。それでは、早速これらの属性を適用した構造体を作成してみようと思います。このサンプルではshortではなくSystem.UInt16を用いていますが、16Bitの符号無し整数型を用いていると言うことを視覚的に表すためであって、それ以外に深い意味はありません。

using System;
using System.Runtime.InteropServices;
namespace StructAndUnion
{
 // StructLayout属性及びFieldOffset属性を適用した構造体
 [StructLayout(LayoutKind.Explicit)]
 struct SampleStruct
 {
  [FieldOffset(0)] public System.UInt16 Value1;
  [FieldOffset(2)] public System.UInt16 Value2;
  [FieldOffset(4)] public System.UInt16 Value3;
  [FieldOffset(6)] public System.UInt16 Value4;
 }

 // アプリケーションのエントリーポイントを提供するクラス
 class SampleClass
 {
  [STAThread]
  static void Main(string[] args)
  {
   SampleStruct s = new SampleStruct();
   // 各フィールドに値を指定
   s.Value1 = 0x1122;
   s.Value2 = 0x3344;
   s.Value3 = 0x5566;
   s.Value4 = 0x7788;

   // 各フィールドの値を表示
   Console.WriteLine( "SampleStruct.Value1 : 0x" + s.Value1.ToString("X4") );
   Console.WriteLine( "SampleStruct.Value2 : 0x" + s.Value2.ToString("X4") );
   Console.WriteLine( "SampleStruct.Value3 : 0x" + s.Value3.ToString("X4") );
   Console.WriteLine( "SampleStruct.Value4 : 0x" + s.Value4.ToString("X4") );
  }
 }
}

オフセット値 格納値
 0 0x22
 1 0x11
 2 0x44
 3 0x33
 4 0x66
 5 0x55
 6 0x88
 7 0x77

このように、StructLayout属性で明示的に配置方法を指定することができ、FieldOffsetでメンバ変数のオフセット値を指定することが出きるとなれば、共用体を作る方法はおのずと浮かんでくるはずです。つまり、共用体の仕組み・構造を思い浮かべて下さい。共用体では一つ以上の同じまたは異なる型のメンバが、同じメモリ領域を共用します。つまり、メンバ変数のオフセット値を全て同じにすれば、共用体と同じ構造の構造体を作ることができることになります。

using System;
using System.Runtime.InteropServices;
namespace StructAndUnion
{
 // 構造体で再現した共用体
 [StructLayout(LayoutKind.Explicit)]
 struct DoubleWord
 {
  [FieldOffset(0)] public System.UInt32 Value;
  [FieldOffset(0)] public System.UInt16 LowWord;
  [FieldOffset(2)] public System.UInt16 HighWord;
 }

 // アプリケーションのエントリーポイントを提供するクラス
 class SampleClass
 {
  [STAThread]
  static void Main(string[] args)
  {
   DoubleWord dw = new DoubleWord();

   // ダブルワード値を指定
   dw.Value = 0x11223344;

   // 下位ワードと上位ワードを表示
   Console.WriteLine( "Low word: 0x" + dw.LowWord.ToString("X4") );
   Console.WriteLine( "High word: 0x" + dw.HighWord.ToString("X4") );
  }
 }
}

オフセット値 格納値
 0 0x44
 1 0x33
 2 0x22
 3 0x11

0 件のコメント: