🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C#

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

Q&A

解決済

1回答

3540閲覧

uint配列を含む構造体の共有メモリへのマッピング

ttact

総合スコア170

C#

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

0グッド

0クリップ

投稿2020/11/04 01:48

共有メモリアクセス及び構造体定義について、マッピングされる位置がおかしい問題と、AccessViolationExceptionがthrowされる問題がそれぞれ発生しており、それを解消したく思っています。

質問の背景

C側で以下の構造体を共有メモリにマッピングしています:

C

1struct Data 2{ 3 uint32_t member1; 4 uint32_t member2; 5 uint32_t member3[128]; 6};

共有メモリアクセスでは(MemoryMappedFile及びMemoryMappedViewAccessorを使わず)WIN32 APIのOpenFileMapping()及びMapViewOfFile()を用いてIntPtrを得て行っています。

問題ない動作

Spanを使う場合は、期待通りのデータを参照できています。

C#

1void Hoge(IntPtr addr) 2{ 3 Span<uint> span; 4 unsafe 5 { 6 span = new Span<uint>(addr.ToPointer(), 130); 7 } 8 // ここでspan変数を使ってアクセス 9 // →例外はthrowされないし、共有メモリ上の値が正しく取得できる。 10}

なので、共有メモリ上のデータ、及びIntPtrが指すアドレス位置は問題ないことがわかっています。

発生した問題(1) マッピングされる位置がおかしい

C#側で以下の構造体を定義してアクセスしようとしました:

C#

1[StructLayout(LayoutKind.Sequential, Pack = 1)] 2public struct Data 3{ 4 public uint member1; 5 public uint member2; 6 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] 7 public uint[] member3; 8}; 9 10void Hoge(IntPtr addr) 11{ 12 unsafe 13 { 14 ref var data = ref Unsafe.AsRef<Data>(attr.ToPointer()); 15 // ここでdata.member1にアクセス 16 // →先頭から12bytesの位置(C側構造体でのmember3[1])の値が読める 17 } 18}

そうすると、上記コード中のコメントに記述したとおり、member1にアクセスしようとしているのにmember3[1]にアクセスされてしまいます。

発生した問題(2) AccessViolationExceptionがthrowされる

上記(1)でアクセス位置がおかしいので、Dataの定義を以下のように修正しました:

C#

1[StructLayout(LayoutKind.Explicit)] 2public struct Data 3{ 4 [FieldOffset(0)] 5 public uint member1; 6 [FieldOffset(4)] 7 public uint member2; 8 [FieldOffset(8)] 9 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] 10 public uint[] member3; 11};

そうすると、member1及びmember2は共有メモリ上の値を正しく取得できたのですが、member3[0]の値を取得しようとするとAccessViolationExceptionがthrowされました。

質問

  1. 問題(1)においてアクセス位置がずれてしまうのは何故なのでしょうか。
  2. 問題(2)においてmember3にアクセスできないのは何故なのでしょうか。
  3. どのように修正したら、共有メモリ上のデータに直接アクセスできるでしょうか。ここでいう「直接アクセス」とは、スタック上に別途Dataの構造体をnewして、共有メモリからその変数にmemcpy()するようなことを避けたい、という意味です。これは次の2つの理由によります:(1)パフォーマンス上の懸念(2)共有メモリ上のデータのset/getにおいて毎回Write/Readを呼ぶような実装上の煩雑性を避けたい。

開発・動作環境

Windows 10 Pro 1909 (Build 18363.1139)
Visual Studio Professional 2019 ver.16.7.y
C# コンソールアプリケーション
ターゲットフレームワーク .NET Framework 4.8
「32ビットを選ぶ」のチェックをオフ
「アンセーフコードの許可」にチェック
System.Memory 4.5.4をNugetで取得して利用

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

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

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

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

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

guest

回答1

0

ベストアンサー

そういう事をしたい場合は、unsafe structを使用する必要があるかと思います。

csharp

1[StructLayout(LayoutKind.Sequential, Pack = 1)] 2public unsafe struct Data 3{ 4 public uint member1; 5 public uint member2; 6 public fixed uint member3[128]; 7};

固定ではない配列を含む構造体は、非Blittable型になります。
Blittable 型と非 Blittable 型
Marshalクラスや、PInvoke呼び出しを経由する分には、マーシャリングで上手く変換してくれるのですが、非Blittableな構造体の実際のメモリの配置のされ方は、C言語の構造体とは違います。

下記のコードをデバッグ実行して、Console.ReadLineの辺りにブレークポイントを設定し、デバッグ⇒ウィンドウ⇒メモリでptr1,ptr2の指すメモリ内のデータの配置のされ方を覗いたり、変数の値を覗いたりしてみてください。
何故上手くいかないのか理由が判ると思います。

csharp

1 class Program 2 { 3 [StructLayout(LayoutKind.Sequential, Pack = 1)] 4 public unsafe struct Data1 5 { 6 public uint member1; 7 public uint member2; 8 public fixed uint member3[128]; 9 }; 10 11 [StructLayout(LayoutKind.Sequential, Pack = 1)] 12 public struct Data2 13 { 14 public uint member1; 15 public uint member2; 16 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] 17 public uint[] member3; 18 }; 19 20 static void Main(string[] args) 21 { 22 try 23 { 24 unsafe 25 { 26 var dat1 = new Data1(); 27 dat1.member1 = 1; 28 dat1.member2 = 2; 29 for (uint i = 0; i < 128; i++) 30 { 31 dat1.member3[i] = i; 32 } 33 34 var ptr1 = &dat1; 35 var dat2 = Marshal.PtrToStructure<Data2>((IntPtr)ptr1); 36 //var ptr2 = &dat2; //これはコンパイルエラー 37 var ptr2 = Unsafe.AsPointer(ref dat2); 38 var span1 = new Span<uint>((void*)ptr1, 130); 39 var span2 = new Span<uint>((void*)ptr2, 130); 40 var ref_dat1_Data1 = Unsafe.AsRef<Data1>((void*)ptr1); 41 var ref_dat2_Data1 = Unsafe.AsRef<Data1>((void*)ptr2); 42 var ref_dat1_Data2 = Unsafe.AsRef<Data2>((void*)ptr1); 43 var ref_dat2_Data2 = Unsafe.AsRef<Data2>((void*)ptr2); 44 45 Console.ReadLine(); 46 } 47 } 48 catch (Exception ex) 49 { 50 Console.Write(ex.ToString()); 51 } 52 } 53 }

投稿2020/11/04 02:58

編集2020/11/04 05:28
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

ttact

2020/11/04 23:03

とても詳細で明快なご回答、どうもありがとうございます。Blittable 型と非Blittable型について知らなかったのですが、C言語と厳密にメモリレイアウトを合わせるために何をすれば明確になりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問