Hatena::ブログ(Diary)

匣の向こう側 - あまりに.NETな RSSフィード

2006-04-18(Tue)

[][]Genericsとunsafe

某所より。

byte* array;

int index;

struct S;

とあって、C++でいうところの

S S* = (S*)&(array + index);

としたいのですけど、これと等価なC#のコードを教えてくださいませ。

これに対し、fixedを使えばいいと回答しましたが、

それ、genericsを使って、byte → Tとして、where T : struct

と書いた場合、コンパイルが通らないのは何故でしょう?(´ω`)

*おおっと、レテポーターgenerics*との事。そーいや、私はgenericsをunsafeでで使ってみたことがありませんでした。取りあえず何も考えずにストレートなコードを書いてみます。

(追記)

handle = GCHandle.Alloc(data, GCHandleType.Pinned);

をしないとMarshal.UnsafeAddrOfPinnedArrayElementは安全に使えません。間違った情報を広めてしまいそうなので追記しておきます。

static void Foo<T>(T[] t) where T : struct
{
    unsafe
    {
        // マネージ型のアドレスの取得、マネージ型のサイズの取得、
        // またはマネージ型へのポインタの宣言が実行できません('T')
        fixed (T* p = &t[0])
        {
            Point* pt = (Point*)p;
        }
    }
}

コンパイルエラーです。内容は上記コードのコメントにある通り。genericsの制約条件にstructを付けているのに何でだろ?・・・あっ、structのメンバーに参照型を入れることが可能なのでstruct制約ではポインタ型に変換できる保証はありませんね。それならば、T[]をbyte[]に変換できれば何とかなるかな?とか考えて、こんな事をやってみました。

static void Foo<T> (T[] t, int size) where T : struct
{
    // genericな配列をbyte[]に変換する
    // BlockCopyは実行時チェックなのでコンパイル出来る
    byte[] bytes = new byte[size];
    Buffer.BlockCopy(t, 0, bytes, 0, bytes.Length);
    unsafe
    {
        fixed (byte* p = &bytes[0])
        {
            Point* pt = (Point*)p;
        }
    }
}

BlockCopyならコンパイル時にエラーにしないので上手く行きました。が、

> T[] -> byte[]変換が入ってしまいますが、

そのoverheadが許容できない…(´ω`)

と、お許しになりません。(^^;

まぁ、実際問題4096byteのバッファでテストしたら2桁ほどオーダーが違っているので、確かに駄目っぽいのですが。諦めようかと思ったのですが、この手の処理だとMarshalクラスに何か使えるモノがあったりすることを思い出して探してみたところビンゴでした。

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Diagnostics;

public struct Point
{
    public int X;
    public int Y;
    public int Z;
}

class Program
{
    static void Foo(byte[] bytes)
    {
        unsafe
        {
            fixed (byte* p = &bytes[0])
            {
                Point* pt = (Point*)p;
            }
        }
    }

    static void Foo<T>(T[] t) where T : struct
    {
        IntPtr p = Marshal.UnsafeAddrOfPinnedArrayElement(t, 0);
        unsafe
        {
            Point* pt = (Point*)p;
        }

    }

    const int LOOP = 100000000;
    static void Main(string[] args)
    {
        byte[] bytes = new byte[4096];

        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < LOOP; ++i)
            Foo(bytes);
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);

        sw.Reset();
        sw.Start();
        for (int i = 0; i < LOOP; ++i)
            Foo<byte>(bytes);
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);
    }
}

/* 結果
591
729
 */

これにて解決、かな?

Marshal.UnsafeAddrOfPinnedArrayElement

これが勝利の鍵だ。

oidon33oidon33 2006/04/19 00:45 ささやき
えいしょう
いのり
……ねんじろ!

*C# は よみがえった*

NyaRuRuNyaRuRu 2006/04/19 03:01 Marshal.UnsafeAddrOfPinnedArrayElement 使うなら Pinning しておかないとまずいような.
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=26459&forum=7&4

yaneuraoyaneurao 2006/04/19 04:13 おお。そうなんですか。ありがとうございます。 > meiさん & NyaRuRuさん

akirameiakiramei 2006/04/19 12:38 >>oidon33さん
そこにつっこんでもらえるとは(笑)
>*C# は よみがえった*
その後の展開で、灰になりそうな雰囲気です。(^^;

>>NyaRuRuさん
>使うなら Pinning しておかないとまずいような.
うあ、日本語のヘルプしか見てませんでしたが、ちょっと分かりにくいです・・・(^^;

>>yaneuraoさん
BBSを見ましたが、構造体S*にキャストしても制約がstructだとなにも操作が出来ない気がします。その辺はC++のテンプレートと違ってコンパイル時に解決してくれないので厳しいですね。

はてなユーザーのみコメントできます。はてなへログインもしくは新規登録をおこなってください。

トラックバック - http://d.hatena.ne.jp/akiramei/20060418/p1
Connection: close