ポインタから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));