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

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

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

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C#

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

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

Q&A

解決済

2回答

8805閲覧

[C#]P/Invokeで構造体配列を含む構造体を渡す方法

kmz_kappa

総合スコア35

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C#

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

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

0グッド

1クリップ

投稿2017/08/27 10:11

編集2017/08/27 11:13

困っていること

C言語で書かれたDLLがあり、C#からここに定義された関数を呼び出したいと思っています。
しかし、P/Invoke時に関数に渡すべきクラスをどう作れば良いのか分からず困っています。

C#から関数を呼び出す際は以下のように定義したものを呼んでいます。

cs

1[DllImport("test.dll", EntryPoint="TestFunction")] 2public static extern int TestFunction([Out]ResultInfo result);

要するにTestFunction関数に ResultInfo 型の変数を渡すと そこに結果が入るだけなのですが、この ResultInfo が厄介です。

C側で定義されたResultInfo は構造体で、以下のように定義されています。

ResultInfo構造体
intid
Item[100]items

ResultInfo構造体の中に、Item構造体の配列を含んでいます。
Item構造体は以下のように定義されています。

Item構造体
intitemid
char[64]name

このような場合、C#側ではResultInfoItemをどう定義したら良いのでしょうか。

現状

以下のように定義しましたが、正しく動作しません。

cs

1/* ResultInfo.cs */ 2[StructLayout(LayoutKind.Sequential)] 3public class ResultInfo 4{ 5 public string id; 6 [MarshalAs(UnmanagedType.ByValArray, SizeConst=100)] 7 public Item[] items; 8} 9 10/* Item.cs */ 11[StructLayout(LayoutKind.Sequential)] 12public class Item 13{ 14 public int itemid; 15 [MarshalAs(UnmanagementType.ByValTStr, SizeConst=64)] 16 public string name; 17} 18 19/* 関数の呼び出し */ 20var resultInfo = new ResultInfo(); 21var resultCode = TestFunction(resultInfo);

この結果、DLLに定義されている関数自体は呼び出され、引数に渡したresultInfoの中のresultInfo.idには正しい値がセットされています。
しかし、resultInfo.itemsの中身は全てnullとなってしまいます。
少なくとも呼び出しには成功しているようなので、itemsが全てnullになってしまうのはC#側の定義が悪いのだろうと思っています。
どのように修正したら良いのでしょうか。

その他

ここで問題としているてtest.dllは仮の名前であり、実際はサードパーティ製のライブラリのためデバッグができません。

追記 (2017.08.27 20:00)

以下のように変更をしたら状況が進みました。

  • ResultInfoクラスの定義をclassからstructに変更
  • Itemクラスの定義をclassからstructに変更
  • C#のTestMethodの引数を [Out]ResultInfo result → IntPtr result に変更
  • 関数の呼び出し方を以下のように変更

cs

1var resultInfo = new ResultInfo(); 2// var resultCode = TestFunction(resultInfo); 3IntPtr p = Marshal.AllocCoTaskMem(Marshal.SizeOf(resultInfo)); 4Marshal.StructureToPtr(resultInfo, p, false); 5var resultCode = TaskFunction(p); 6ResultInfo res = (ResultInfo)Marshal.PtrToStructure(p, typeof(ResultInfo)); 7Marshal.FreeCoTaskMem(p);

この結果、ResultInfo.items(ここでは変数res)の中身が全てnullという状態は改善し、中身は全てItem型のオブジェクトになりました。
しかし、ResultInto.items[0].itemid には値が入っている状態ですが、ResultInfo.items[0].name はnullになってしまいます。
(本来はResultInfo.items[0].nameには空文字列またはSJISの文字列が入り、nullが入ることはありません)

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

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

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

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

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

ebiryo

2017/08/28 02:51

C++/CLIでラップしちゃったほうが手っ取り早い気がするのですが。。 制限等があるのでしょうか?
kmz_kappa

2017/08/28 11:41

ありがとうございます。まず私がC++/CLIでラップする方法を知らないことと、調べて実装をしたとしてもC++/CLIの知識が必要なため自分以外の人間が保守をすることができなくなることが課題となってしまいます。
guest

回答2

0

こんな感じでしょうか。

C#

1[DllImport("test.dll", EntryPoint="TestFunction")] 2public static extern int TestFunction([Out,In]ref ResultInfo result); 3 4/* ResultInfo.cs */ 5[StructLayout(LayoutKind.Sequential)] 6public struct ResultInfo 7{ 8 public int id; 9 [MarshalAs(UnmanagedType.ByValArray, SizeConst=100)] 10 public Item[] items; 11} 12 13/* Item.cs */ 14[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)] 15public struct Item 16{ 17 public int itemid; 18 [MarshalAs(UnmanagedType.ByValTStr, SizeConst=64)] 19 public string name; 20} 21 22/* 関数の呼び出し */ 23var resultInfo = new ResultInfo(); 24resultInfo.items = new Item[100]; 25for(int i = 0; i < 100; i++) 26{ 27 resultInfo.items[i] = new Item(); 28 resultInfo.items[i].name = ""; 29} 30var resultCode = TestFunction(ref resultInfo); 31

変更点

  • classをstructに変更
  • idをintに変更
  • ItemにCharSet=CharSet.Ansiを追加
  • 構造体のメンバを初期化
  • パラメータを参照で渡す

一番最初のコードがうまくいかない原因の最も大きな点は、クラスのメンバが初期化されていないことです。

投稿2017/08/28 06:50

Harahira

総合スコア243

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

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

kmz_kappa

2017/08/28 11:37

ありがとうございます。 散々悩んで書いていましたが、メンバの値が初期化がされていないことが問題だったとは恥ずかしいです。 メモリだけ確保してC側の処理に投げてしまえばどうにかなるのだろうと思っていました。 お陰様でIntPtrを使わず簡潔に処理を記述することができました。
guest

0

自己解決

自己解決しました。

処理結果に含まれるstringがnullになってしまう原因は、C側の処理で構造体メンバのchar[]にSJISの文字列がセットされることにありました。
C側の処理でchar[]にSJIS文字列がセットされた後、これを.NETは文字コードを考慮せずstringに変換しました。
その結果stringの値は壊れてしまい、デバッガからはstringの値がnullになったように見えました。

これを回避するために定義を以下のように変更し、C側の処理結果をstringではなくbyte[]で受け取るようにしました。

cs

1// [MarshalAs(UnmanagedType.ByValTStr, SizeConst=64)] 2// public string name; 3[MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.ByValTStr, SizeConst=64)] 4public byte[] name;

そして、nameの値をbyte[]から文字コードを考慮してstringに変換することで期待した結果が得られました。

cs

1string str = System.Text.Encoding.GetEncoding("Shift_JIS").GetString(name);

投稿2017/08/28 11:53

kmz_kappa

総合スコア35

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問