質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.37%

  • C#

    4770questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

C#で構造体(値型)からポインターを取得して、構造体の内容を変化させた後にポインター側で変更後の結果を取得したい

解決済

回答 2

投稿 2017/11/27 12:41

  • 評価
  • クリップ 0
  • VIEW 106

fumiasi

score 6

実現したいこと

C#にて構造体(値型)からポインター(IntPtr)を取得して、ポインター(IntPtr)を保持しておき
構造体(値型)の内容を変化させた後に、ポインター(IntPtr)の値から変更後の結果を受け取る方法を探しています。

試してみた内容

public class ClassInfo
{
    [Serializable]
    public struct Info
    {
        public int state;
    }

    public Info m_Info;

    public IntPtr m_Ptr;
    public int m_Size;

    public ClassInfo()
    {
        // state1に変更する
        info.state = 1;

        // 構造体からポインターを取得する
        m_Size = Marshal.SizeOf(typeof(Info));
        m_Ptr = Marshal.AllocCoTaskMem(m_Size);
        Marshal.StructureToPtr(info, m_Ptr, false);
    }

    public Update()
    {
        // stateを変更する
        m_Info.state++;

        // ポインターから復元する
        Info info;
        IntPtr ptr = Marshal.AllocHGlobal(m_Size);
        Marshal.StructureToPtr(info, ptr, false);

        // ここで上記で復元したinfo.stateが++されて2になった状態で取得したい

        // 解放しておく
        Marshal.FreeCoTaskMem(m_Ptr);
        Marshal.FreeCoTaskMem(ptr);
    }
}

補足情報

クラス(参照)ではなく構造体(値型)を使用している理由は、
構造体の内容をbyte配列化して通信データとして外部に送信する必要がある為です。

AllocHGlobalでメモリーを確保して、確保したメモリーに構造体の内容をStructureToPtrで代入している為
構造体と確保したメモリーの内容が別物になっている事が原因だと考えているのですが、
構造体自身のポインターを取得する方法、または変更後の結果をポインターから取得出来る方法があれば
ご教授頂けますと幸いです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

0

こんにちは。

byte[]でしかやったことないのですが、構造体もobjectを継承しているようですので、恐らく下記で行けると思います。

IntPtr ptr = Marshal.AllocHGlobal(m_Size);
Marshal.StructureToPtr(info, ptr, false);
    // ここで上記で復元したinfo.stateが++されて2になった状態で取得したい
Marshal.FreeCoTaskMem(m_Ptr);
Marshal.FreeCoTaskMem(ptr);


の部分を下記へ変更。

GCHandle handle = GCHandle.Alloc(this, GCHandleType.Pinned);
IntPtr ptr = handle.AddrOfPinnedObject();
    // ここで上記で復元したinfo.stateが++されて2になった状態で取得したい
handle.Free();

【追加のご報告を見て構造体の振る舞いが気になったので確認してみました】
C#の構造体をほとんど使ったことが無かったので、Info info;で領域獲得されるかと思っていたのですが、そうではないようですね。直後のMarshal.StructureToPtr(info, ptr, false);で「CS0165 未割り当てのローカル変数 'info' が使用されました」エラーになりました。

その他、私のミスも含めて修正してみたところ、どちらも 2 が表示されました。
元々のm_Ptr は AllocCoTaskMem で獲得した領域へ StructureToPtr にてm_Infoをコピーしたものですので、m_Ptr は m_Info を指していません。

public class ClassInfo
{
[Serializable]
public struct Info
{
    public int state;
}

public Info m_Info;

public IntPtr m_Ptr;
public int m_Size;

public ClassInfo()
{
    // state1に変更する
    m_Info.state = 1;

    // 構造体からポインターを取得する
    m_Size = Marshal.SizeOf(typeof(Info));
    m_Ptr = Marshal.AllocCoTaskMem(m_Size);
    Marshal.StructureToPtr(m_Info, m_Ptr, false);
}

public void Update()
{
    // stateを変更する
    m_Info.state++;
#if true
    // ポインターから復元する
    IntPtr ptr = Marshal.AllocHGlobal(m_Size);
    Marshal.StructureToPtr(m_Info, ptr, false);

    // ここで上記で復元したinfo.stateが++されて2になった状態で取得したい
    Info info = (Info)Marshal.PtrToStructure(ptr, typeof(Info));
    Debug.WriteLine(info.state);

    // 解放しておく
    Marshal.FreeCoTaskMem(m_Ptr);
    Marshal.FreeCoTaskMem(ptr);
#else
    // ポインターから復元する
    GCHandle handle = GCHandle.Alloc(m_Info, GCHandleType.Pinned);
    IntPtr ptr = handle.AddrOfPinnedObject();

    // ここで上記で復元したinfo.stateが++されて2になった状態で取得したい
    Info info = (Info)Marshal.PtrToStructure(ptr, typeof(Info));
    Debug.WriteLine(info.state);

    // 解放しておく
    handle.Free();
#endif
}

Visual Studio 2017 Communityで確認しました。

投稿 2017/11/27 13:00

編集 2017/11/29 10:52

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/27 14:15

    上記で教えて頂いた方法でIntPtrを確保して
    Info info = (Info)Marshal.PtrToStructure(m_Ptr, typeof(Info));
    にて復元したところstateが2になって復元されました。

    ご教授頂きましてありがとうございました。

    キャンセル

  • 2017/11/29 11:32 編集

    追記の情報ありがとうございます。

    追記頂いたソースを確認してみたのですが、
    m_Info.state++;を行った後にポインターを取得している形になっているかと思うのですが
    ポインターの取得後に再度加算を行った場合にどちらも3になる事はありませんでした。
    どちらも m_Infoをコピーしたもの になるのでしょうか

    public void Update()
    {
    // stateを変更する
    m_Info.state++;
    #if true
    // ポインターから復元する
    IntPtr ptr = Marshal.AllocHGlobal(m_Size);
    Marshal.StructureToPtr(m_Info, ptr, false);

    // ここで再度stateを変更する
    m_Info.state++;

    // ここで上記で復元したinfo.stateが++されて3になった状態で取得したい
    Info info = (Info)Marshal.PtrToStructure(ptr, typeof(Info));
    Debug.WriteLine(info.state);

    // 解放しておく
    Marshal.FreeCoTaskMem(m_Ptr);
    Marshal.FreeCoTaskMem(ptr);
    #else
    // ポインターから復元する
    GCHandle handle = GCHandle.Alloc(m_Info, GCHandleType.Pinned);
    IntPtr ptr = handle.AddrOfPinnedObject();

    // ここで再度stateを変更する
    m_Info.state++;

    // ここで上記で復元したinfo.stateが++されて3になった状態で取得したい
    Info info = (Info)Marshal.PtrToStructure(ptr, typeof(Info));
    Debug.WriteLine(info.state);

    // 解放しておく
    handle.Free();
    #endif
    }
    上記で頂いたソースの途中にstate++;を追加した物になります

    キャンセル

  • 2017/11/29 13:31

    > どちらも m_Infoをコピーしたもの になるのでしょうか

    前者は明示的に領域を確保してそこへコピーしていますので、コピーです。
    後者はコピーしていないと理解していたのですが、コピーしているようです。ちょっとびっくりしました。
    検索したら↓が見つかりました。GCHandle.Allocは参照型の場合にアドレスを返却するそうです。
    https://qa.atmarkit.co.jp/q/9283
    このふるまいを見ると値型の場合はコピーするようですね。
    値型はスタックに確保できますので、その場合を考慮した動作になっているのかも知れません。
    Infoをclass型へ変更してもダメでした。bilttableでないとダメなのでしかたないですね。
    つまり、基本型(byteやintなど)の配列でないとポインタを取り出せないということのような気がします。

    キャンセル

  • 2017/11/29 14:53

    ご返答ありがとうございます。

    ポインターなので構造体(値型)でも操作できるかと思っていたのですが
    どちらもコピーした内容を参照していたんですね
    今回の件で勉強になりました。

    > 値型はスタックに確保できますので、その場合を考慮した動作になっているのかも知れません。
    なんとなく納得出来る気がします。

    わざわざ調べて頂き、ありがとうございました。

    キャンセル

0

以前に教えて頂いた方法なのですが、
PCを再起動したところ正常に動作しなくなってしまいました。
(むしろ再起動する前がおかしな状態になっていた可能性があります。)

いろいろ思考錯誤を行ったのですが、
構造体(値型)からポインターを取得しても
直接変更した後にポインター側の内容が反映させる方法は見つかりませんでした。
(構造体を複製してポインターを作成しているのでしょうか...)

最終的にはclass(参照型)で管理を行い、必要なタイミングでbyte配列に変換する事にしました。

public class ClassInfo
{
    public class Info
    {
        public int   state;

        // ポインターを取得した場合はハンドルを保持する
        private GCHandle handle;

        // ポインターを取得する
        public IntPtr getPtr()
        {
            handle = GCHandle.Alloc(this);
            return GCHandle.ToIntPtr(handle);
        }

        // ポインターを取得した場合は解放する
        public void free()
        {
            if(handle.IsAllocated) handle.Free();
        }

        // 解放漏れが無いように消す
        ~Info()
        {
            free();
        }

        // ポインタから変換する
        public static Info Convert(IntPtr ptr)
        {
            GCHandle handle = GCHandle.FromIntPtr(ptr);
            return (Info)handle.Target;
        }

        // ポインターからバイト配列を取得する
        public static byte[] GetByteData(IntPtr ptr)
        {
            GCHandle handle = GCHandle.FromIntPtr(ptr);
            Info class_data = (Info)handle.Target;

            Info_struct struct_data = new Info_struct();
            struct_data.state = class_data.state;

            int size = Marshal.SizeOf(struct_data);
            byte[] bytes = new byte[size];
            GCHandle gch = GCHandle.Alloc(bytes, GCHandleType.Pinned);
            Marshal.StructureToPtr(struct_data, gch.AddrOfPinnedObject(), false);
            gch.Free();
            return bytes;
        }
    }

    // バイト配列の変換用にクラスと同じ内容の変数を所持しておく
    public struct Info_struct
    {
        public int   state;
    }

    // 本体
    public Info m_Info = new Info();

    // ポインター
    public IntPtr m_Ptr;

    void Start () {
        m_Info.state = 1;
        m_Ptr = m_Info.getPtr();
    }

    void Update () {
        // stateを変更する
        m_Info.state++;

        // ポインターから復元する
        Info info = Info.Convert(m_Ptr);

        // ここで上記で復元したinfo.stateが++されて2になった状態で取得出来る
        System.Diagnostics.Debug.WriteLine("state : " + info.state);

        // バイト配列の取得
        byte[] data = Info.GetByteData(m_Ptr);
        System.Diagnostics.Debug.WriteLine("Length : " + data.Length);
    }
}

上記では質問した内容に合わせてbyte配列の変換用に構造体を用意していますが、
実際に使用する場合は下記の関数を作成して構造体を作成せずに直接byte配列に変換した方が良いと思います。

    public static byte[] SerializeData<T>(T data)
    {
        BinaryFormatter bf = new BinaryFormatter();
        MemoryStream memoryStream = new MemoryStream();
        bf.Serialize(memoryStream, data);
        byte[] byteData = memoryStream.ToArray();
        memoryStream.Close();
        return byteData;
    }

    public static T DeserializeData<T>(byte[] byte_data)
    {
        MemoryStream memoryStream = new MemoryStream(byte_data.Length);
        memoryStream.Write(byte_data, 0, byte_data.Length);
        memoryStream.Seek(0,SeekOrigin.Begin);
        BinaryFormatter bf = new BinaryFormatter();
        T data = (T)bf.Deserialize(memoryStream);
        memoryStream.Close();
        return data;
    }

最後にご検討して頂いた皆様、ありがとうございました。

投稿 2017/11/29 05:31

編集 2017/11/29 05:33

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

ただいまの回答率

91.37%

関連した質問

同じタグがついた質問を見る

  • C#

    4770questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

  • トップ
  • C#に関する質問
  • C#で構造体(値型)からポインターを取得して、構造体の内容を変化させた後にポインター側で変更後の結果を取得したい