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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C#

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

Q&A

解決済

2回答

4368閲覧

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

fumiasi

総合スコア12

C#

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

0グッド

0クリップ

投稿2017/11/27 03:41

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

###試してみた内容

C#

1public class ClassInfo 2{ 3 [Serializable] 4 public struct Info 5 { 6 public int state; 7 } 8 9 public Info m_Info; 10 11 public IntPtr m_Ptr; 12 public int m_Size; 13 14 public ClassInfo() 15 { 16 // stateを1に変更する 17 info.state = 1; 18 19 // 構造体からポインターを取得する 20 m_Size = Marshal.SizeOf(typeof(Info)); 21 m_Ptr = Marshal.AllocCoTaskMem(m_Size); 22 Marshal.StructureToPtr(info, m_Ptr, false); 23 } 24 25 public Update() 26 { 27 // stateを変更する 28 m_Info.state++; 29 30 // ポインターから復元する 31 Info info; 32 IntPtr ptr = Marshal.AllocHGlobal(m_Size); 33 Marshal.StructureToPtr(info, ptr, false); 34 35 // ここで上記で復元したinfo.stateが++されて2になった状態で取得したい 36 37 // 解放しておく 38 Marshal.FreeCoTaskMem(m_Ptr); 39 Marshal.FreeCoTaskMem(ptr); 40 } 41}

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

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

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

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

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

C#

1public class ClassInfo 2{ 3 public class Info 4 { 5 public int state; 6 7 // ポインターを取得した場合はハンドルを保持する 8 private GCHandle handle; 9 10 // ポインターを取得する 11 public IntPtr getPtr() 12 { 13 handle = GCHandle.Alloc(this); 14 return GCHandle.ToIntPtr(handle); 15 } 16 17 // ポインターを取得した場合は解放する 18 public void free() 19 { 20 if(handle.IsAllocated) handle.Free(); 21 } 22 23 // 解放漏れが無いように消す 24 ~Info() 25 { 26 free(); 27 } 28 29 // ポインタから変換する 30 public static Info Convert(IntPtr ptr) 31 { 32 GCHandle handle = GCHandle.FromIntPtr(ptr); 33 return (Info)handle.Target; 34 } 35 36 // ポインターからバイト配列を取得する 37 public static byte[] GetByteData(IntPtr ptr) 38 { 39 GCHandle handle = GCHandle.FromIntPtr(ptr); 40 Info class_data = (Info)handle.Target; 41 42 Info_struct struct_data = new Info_struct(); 43 struct_data.state = class_data.state; 44 45 int size = Marshal.SizeOf(struct_data); 46 byte[] bytes = new byte[size]; 47 GCHandle gch = GCHandle.Alloc(bytes, GCHandleType.Pinned); 48 Marshal.StructureToPtr(struct_data, gch.AddrOfPinnedObject(), false); 49 gch.Free(); 50 return bytes; 51 } 52 } 53 54 // バイト配列の変換用にクラスと同じ内容の変数を所持しておく 55 public struct Info_struct 56 { 57 public int state; 58 } 59 60 // 本体 61 public Info m_Info = new Info(); 62 63 // ポインター 64 public IntPtr m_Ptr; 65 66 void Start () { 67 m_Info.state = 1; 68 m_Ptr = m_Info.getPtr(); 69 } 70 71 void Update () { 72 // stateを変更する 73 m_Info.state++; 74 75 // ポインターから復元する 76 Info info = Info.Convert(m_Ptr); 77 78 // ここで上記で復元したinfo.stateが++されて2になった状態で取得出来る 79 System.Diagnostics.Debug.WriteLine("state : " + info.state); 80 81 // バイト配列の取得 82 byte[] data = Info.GetByteData(m_Ptr); 83 System.Diagnostics.Debug.WriteLine("Length : " + data.Length); 84 } 85}

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

C#

1 public static byte[] SerializeData<T>(T data) 2 { 3 BinaryFormatter bf = new BinaryFormatter(); 4 MemoryStream memoryStream = new MemoryStream(); 5 bf.Serialize(memoryStream, data); 6 byte[] byteData = memoryStream.ToArray(); 7 memoryStream.Close(); 8 return byteData; 9 } 10 11 public static T DeserializeData<T>(byte[] byte_data) 12 { 13 MemoryStream memoryStream = new MemoryStream(byte_data.Length); 14 memoryStream.Write(byte_data, 0, byte_data.Length); 15 memoryStream.Seek(0,SeekOrigin.Begin); 16 BinaryFormatter bf = new BinaryFormatter(); 17 T data = (T)bf.Deserialize(memoryStream); 18 memoryStream.Close(); 19 return data; 20 }

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

投稿2017/11/28 20:31

編集2017/11/28 20:34
fumiasi

総合スコア12

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

こんにちは。

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

C#

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

の部分を下記へ変更。

C#

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

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

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

C#

1public class ClassInfo 2{ 3[Serializable] 4public struct Info 5{ 6 public int state; 7} 8 9public Info m_Info; 10 11public IntPtr m_Ptr; 12public int m_Size; 13 14public ClassInfo() 15{ 16 // stateを1に変更する 17 m_Info.state = 1; 18 19 // 構造体からポインターを取得する 20 m_Size = Marshal.SizeOf(typeof(Info)); 21 m_Ptr = Marshal.AllocCoTaskMem(m_Size); 22 Marshal.StructureToPtr(m_Info, m_Ptr, false); 23} 24 25public void Update() 26{ 27 // stateを変更する 28 m_Info.state++; 29#if true 30 // ポインターから復元する 31 IntPtr ptr = Marshal.AllocHGlobal(m_Size); 32 Marshal.StructureToPtr(m_Info, ptr, false); 33 34 // ここで上記で復元したinfo.stateが++されて2になった状態で取得したい 35 Info info = (Info)Marshal.PtrToStructure(ptr, typeof(Info)); 36 Debug.WriteLine(info.state); 37 38 // 解放しておく 39 Marshal.FreeCoTaskMem(m_Ptr); 40 Marshal.FreeCoTaskMem(ptr); 41#else 42 // ポインターから復元する 43 GCHandle handle = GCHandle.Alloc(m_Info, GCHandleType.Pinned); 44 IntPtr ptr = handle.AddrOfPinnedObject(); 45 46 // ここで上記で復元したinfo.stateが++されて2になった状態で取得したい 47 Info info = (Info)Marshal.PtrToStructure(ptr, typeof(Info)); 48 Debug.WriteLine(info.state); 49 50 // 解放しておく 51 handle.Free(); 52#endif 53}

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

投稿2017/11/27 04:00

編集2017/11/29 01:52
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fumiasi

2017/11/27 05:15

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

2017/11/29 02: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++;を追加した物になります
Chironian

2017/11/29 04:31

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

2017/11/29 05:53

ご返答ありがとうございます。 ポインターなので構造体(値型)でも操作できるかと思っていたのですが どちらもコピーした内容を参照していたんですね 今回の件で勉強になりました。 > 値型はスタックに確保できますので、その場合を考慮した動作になっているのかも知れません。 なんとなく納得出来る気がします。 わざわざ調べて頂き、ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問