ホームページ >バックエンド開発 >C#.Net チュートリアル >C# が C++DLL を呼び出して構造体配列を渡す場合の解決策の詳細な説明
この記事は、C# が C++DLL を呼び出して構造体配列を渡すための究極の解決策に関する関連情報を主に紹介します。必要な友人はそれを参照してください
C# が C++DLL を呼び出して構造体配列を渡すための究極の解決策について説明します。 array
プロジェクトを開発するときは、C++ でカプセル化された DLL を呼び出す必要があります。一般に、DllImport を使用して DLL から 関数 をインポートするだけです。しかし、構造体、構造体配列、または構造体ポインターが渡されると、C# には対応する型が存在しないことがわかります。このときどうすればよいでしょうか? 最初の反応は、C# も構造体を定義し、それをパラメーターとして渡すことです。ただし、構造体を定義し、それにパラメーターを渡そうとすると、例外がスローされるか、構造体は渡されますが、戻り値が必要なものではないことがデバッグ追跡の結果判明しました。まったく変更されていない、コードは次のとおりです。
[DllImport("workStation.dll")] private static extern bool fetchInfos(Info[] infos); public struct Info { public int OrderNO; public byte[] UniqueCode; public float CpuPercent; }; private void buttonTest_Click(object sender, EventArgs e) { try { Info[] infos=new Info[128]; if (fetchInfos(infos)) { MessageBox.Show("Fail"); } else { string message = ""; foreach (Info info in infos) { message += string.Format("OrderNO={0}\r\nUniqueCode={1}\r\nCpu={2}", info.OrderNO, Encoding.UTF8.GetString(info.UniqueCode), info.CpuPercent ); } MessageBox.Show(message); } } catch (System.Exception ex) { MessageBox.Show(ex.Message); } }
その後、情報を検索したところ、C# はマネージド メモリであると記載されていました。今度は、属性アンマネージド メモリである構造体配列を渡す必要があります。空間を指定して渡す必要があります。そこで以下のように構造を変更します。
StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public struct Info { public int OrderNO; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] UniqueCode; public float CpuPercent; };
しかし、そのような改善の後でも、実行結果は依然として理想的ではなく、値が間違っているか、変更されていません。その理由は何でしょうか?情報を探し続けた結果、構造体の転送について言及した記事を見つけました。特にパラメータが C++ の構造体ポインタまたは構造体配列ポインタの場合、ポインタも上記のように行う必要があります。 C# 呼び出しに対応するために使用されます。次のコードは後で改良されます。
[DllImport("workStation.dll")] private static extern bool fetchInfos(IntPtr infosIntPtr); [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public struct Info { public int OrderNO; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] UniqueCode; public float CpuPercent; }; private void buttonTest_Click(object sender, EventArgs e) { try { int workStationCount = 128; int size = Marshal.SizeOf(typeof(Info)); IntPtr infosIntptr = Marshal.AllocHGlobal(size * workStationCount); Info[] infos = new Info[workStationCount]; if (fetchInfos(infosIntptr)) { MessageBox.Show("Fail"); return; } for (int inkIndex = 0; inkIndex < workStationCount; inkIndex++) { IntPtr ptr = (IntPtr)((UInt32)infosIntptr + inkIndex * size); infos[inkIndex] = (Info)Marshal.PtrToStructure(ptr, typeof(Info)); } Marshal.FreeHGlobal(infosIntptr); string message = ""; foreach (Info info in infos) { message += string.Format("OrderNO={0}\r\nUniqueCode={1}\r\nCpu={2}", info.OrderNO, Encoding.UTF8.GetString(info.UniqueCode), info.CpuPercent ); } MessageBox.Show(message); } catch (System.Exception ex) { MessageBox.Show(ex.Message); } }
現時点では、interfaceが IntPtr に変更されていることに注意してください。上記のメソッドを通じて、最終的に構造体配列が渡されます。ただし、ここで注意すべき点は、コンパイラーによって構造体のサイズがわからないことです。たとえば、上記の構造体
BCB にバイト アライメントがない場合、通常よりも 2 テール大きくなる場合があります。構造体のサイズ。 BCB のデフォルトは 2 バイト ソートであり、VC のデフォルトは 1 バイト ソートであるためです。この問題を解決するには、BCB 構造にバイト アライメントを追加するか、C# でさらに 2 バイト (さらにある場合) を開きます。バイトアライメントコードは以下のとおりです。
#pragma pack(push,1) struct Info { int OrderNO; char UniqueCode[32]; float CpuPercent; }; #pragma pack(pop)
Marsh.AllocHGlobalを使用して構造体ポインタのメモリ領域を解放します。その目的は、それをアンマネージメモリに変換することです。では、Marsh.AllocHGlobalを使用しない場合、他の方法はありますか?
実際、C++ではポインタであっても配列であっても、最終的には1バイト、つまり1次元のバイト配列の形で表示されるので、同じサイズの 1 次元配列 を開くだけで十分でしょうか?答えは「はい」です。実装は以下に示されています。
[DllImport("workStation.dll")] private static extern bool fetchInfos(IntPtr infosIntPtr); [DllImport("workStation.dll")] private static extern bool fetchInfos(byte[] infos); [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public struct Info { public int OrderNO; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] UniqueCode; public float CpuPercent; }; private void buttonTest_Click(object sender, EventArgs e) { try { int count = 128; int size = Marshal.SizeOf(typeof(Info)); byte[] inkInfosBytes = new byte[count * size]; if (fetchInfos(inkInfosBytes)) { MessageBox.Show("Fail"); return; } Info[] infos = new Info[count]; for (int inkIndex = 0; inkIndex < count; inkIndex++) { byte[] inkInfoBytes = new byte[size]; Array.Copy(inkInfosBytes, inkIndex * size, inkInfoBytes, 0, size); infos[inkIndex] = (Info)bytesToStruct(inkInfoBytes, typeof(Info)); } string message = ""; foreach (Info info in infos) { message += string.Format("OrderNO={0}\r\nUniqueCode={1}\r\nCpu={2}", info.OrderNO, Encoding.UTF8.GetString(info.UniqueCode), info.CpuPercent ); } MessageBox.Show(message); } catch (System.Exception ex) { MessageBox.Show(ex.Message); } } #region bytesToStruct /// <summary> /// Byte array to struct or classs. /// </summary> /// <param name=”bytes”>Byte array</param> /// <param name=”type”>Struct type or class type. /// Egg:class Human{...}; /// Human human=new Human(); /// Type type=human.GetType();</param> /// <returns>Destination struct or class.</returns> public static object bytesToStruct(byte[] bytes, Type type) { int size = Marshal.SizeOf(type);//Get size of the struct or class. if (bytes.Length < size) { return null; } IntPtr structPtr = Marshal.AllocHGlobal(size);//Allocate memory space of the struct or class. Marshal.Copy(bytes, 0, structPtr, size);//Copy byte array to the memory space. object obj = Marshal.PtrToStructure(structPtr, type);//Convert memory space to destination struct or class. Marshal.FreeHGlobal(structPtr);//Release memory space. return obj; } #endregion
データを送信する方法が本当に思いつかない場合は、バイト配列 (4 バイトが割り当てられている限り (32 ビット未満) であれば整数でも問題ありません) として送信することを検討できます。長さが一致している場合は、データを取得した後、型の規則に従って必要なデータに変換する必要があります。通常、目的は達成されます。
以上がC# が C++DLL を呼び出して構造体配列を渡す場合の解決策の詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。