ポインタからfloat,doubleの値を読み取る
ポインタから値を読み取るメソッドとして、Marshal.ReadByte, Marshal.ReadInt16, Marshal.ReadInt32, Marshal.ReadInt64 があります。すなわち、byte, short, int, long についてはこれらのメソッドを使うことで、unsafeを使うこと無くポインタの実体の値を得ることができます。
しかし、float(Single)やdoubleについては、Marshal.ReadDoubleというようなメソッドは無いため、困ることになります。今回の話はこれをunsafeを使わず何とかする方法です。
前準備
以下のようにして、float* と double* なIntPtrをつくっておきます。面倒なので初期化はunsafe使っちゃいます。
IntPtr pf = Marshal.AllocHGlobal(sizeof(float)); IntPtr pd = Marshal.AllocHGlobal(sizeof(double)); unsafe { *(float*)pf = 1.234567f; *(double*)pd = 1.234567d; }
double* -> double
doubleをIntPtrから読み取ります。doubleは8バイトなので、同じ8バイトであるlongでまず読み取り、そのあとでBitConverter.Int64BitsToDoubleでdouble値に変換します。
long longValue = Marshal.ReadInt64(pd); double doubleValue = BitConverter.Int64BitsToDouble(longValue);
float* -> float
これが厄介。あまりよい方法が思いつきません。
Marshal.Copyで長さ1のfloat配列にコピーというのが比較的楽な方法。
float[] temp = new float[1]; Marshal.Copy(pf, temp, 0, 1); float floatValue = temp[0];
ただしこの方法では、読み取る際にオフセットの指定ができません。自力でIntPtrのアドレスを sizeof(float) * offset 分ずらす必要があります。
float ReadFloat(IntPtr ptr, int offset) { ptr = new IntPtr(ptr.ToInt32() + (offset * sizeof(float))); float[] temp = new float[1]; Marshal.Copy(pf, temp, 0, 1); return temp[0]; }
他の方法としては、BitConverterを使うとこんな感じでしょうか。
float floatValue = BitConverter.ToSingle( new byte[] { Marshal.ReadByte(pf, 0), Marshal.ReadByte(pf, 1), Marshal.ReadByte(pf, 2), Marshal.ReadByte(pf, 3), }, 0);
こう書いてもいけます。
float floatValue = BitConverter.ToSingle(BitConverter.GetBytes(Marshal.ReadInt32(pf), 0));