トップ回答者
unsigned char*を含む構造体のポインタを引数とするDLLの呼び出し方について

質問
-
C++でDLLを作成し、C#とC++/CLIから呼び出しを行いたいと思っています。
DLLでは、byte配列の先頭ポインタ(unsigned char*)と
byte配列の長さ(unsigned longからなる構造体を引数にとる関数を定義しています。C++/CLIからの呼び出しには成功しますが、
C#からの呼び出しがうまくいきません。
(nullptrで渡っている?)
試験的にDLL関数の第2引数として、byte配列の先頭ポインタを取る様にしてみましたが、
このケースでは、C#でもうまく引き渡せている様なので、
構造体に含めた場合の引き渡し方に問題があるものと推測しています。以下に環境と、ソースコードを添付します。
環境
OS:Win7 64bit
.NET Framework4.6.1SampleDll.h(C++ DLL)
#pragma once struct BinaryData { unsigned long len; unsigned char* dataPtr; }; extern "C" int OutputBinary(BinaryData* binaryData, unsigned char* b);
SampleDll.cpp(C++ DLL)
#include "pch.h" #include "SampleDll.h" int OutputBinary(BinaryData* binaryData, unsigned char* b) { // 第二引数のポインタアドレスを表示 printf("%p", b); printf("\n"); // 第二引数のポインタを操作してバイト配列を表示 // C#から呼んだ場合もC++/CLIから呼んだ場合も表示される。 for (unsigned long i = 0; i < binaryData->len; i++) { printf("%02x", *(b + i)); } printf("\n"); // 構造体の中のポインタを操作してバイト配列を表示 // C++/CLIから呼んだ場合は、上手くいくが、 // C#からは、以下のエラーになる。 // 例外がスローされました:読み取りアクセス違反。 // binaryData->dataPtr が 0x1110112 でした。 for (unsigned long i = 0; i < binaryData->len; i++) { printf("%02x", *(binaryData->dataPtr + i)); } return 0; }
SampleConsole.cpp(C++/CLI)
この呼び方は、問題ない#include <iostream> #include "SampleDll.h" #include <windows.h> using namespace System; int main(array<System::String^>^ args) { // DLL内の関数の型を宣言 typedef int(*OutputBinary)(BinaryData * data, unsigned char* b); // DLL読み込み HMODULE SampleDll = LoadLibrary(L"SampleDll.dll"); // 関数アドレスの取得 OutputBinary func = (OutputBinary)GetProcAddress(SampleDll, "OutputBinary"); // バイト配列作成(ASCII文字列"TEST"を表すバイト配列) array<System::Byte>^ byteArray = System::Convert::FromBase64String("VEVTVA=="); pin_ptr<System::Byte> p = &byteArray[0]; unsigned char* pby = p; unsigned long len = byteArray->Length; // 構造体に入れる BinaryData data = { len, pby }; // 関数の実行 int ret = func(&data, pby); getchar(); return 0; }
実行結果
02CC2A2C 54455354 54455354
Program.cs(C#)
この場合は、エラーとなる。using System; using System.Runtime.InteropServices; namespace SampleApp { class Program { // DLL内の関数の型を宣言 [DllImport("SampleDll.dll", CallingConvention = CallingConvention.Cdecl)] static unsafe extern int OutputBinary(BinaryData* binaryData, byte* b); static void Main(string[] args) { byte[] byteArray = Convert.FromBase64String("VEVTVA=="); int ret; unsafe { // バイト配列のメモリ確保 IntPtr arrPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(byteArray[0]) * byteArray.Length); // アンマネージド領域にコピー Marshal.Copy(byteArray, 0, arrPtr, byteArray.Length); // 構造体に入れる BinaryData binaryData = new BinaryData { len = Convert.ToUInt64(byteArray.Length), dataPtr = (byte*)arrPtr }; // 構造体分のメモリ確保 IntPtr dataPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(binaryData)); // 構造体をアンマネージド領域にコピー Marshal.StructureToPtr(binaryData, dataPtr, false); try { // 関数呼び出し ret = OutputBinary((BinaryData*)dataPtr, (byte*)arrPtr); } finally { // メモリ解放 Marshal.FreeCoTaskMem(dataPtr); Marshal.FreeCoTaskMem(arrPtr); } } Console.WriteLine(ret); Console.Read(); } } // 構造体定義 [StructLayout(LayoutKind.Sequential)] internal unsafe struct BinaryData { public ulong len; public byte* dataPtr; } }
実行結果
0072F720 54455354
例外メッセージ
例外がスローされました:読み取りアクセス違反。 binaryData->dataPtr が 0x1110112 でした。
何かお気づきの方がいらっしゃいましたら、
ご指摘頂ければ幸いです。
回答
-
VCのunsigned longは4バイトで、C#のulongは8バイトです。
C#側でulong型のlenに4を入れると上位バイトはゼロになっているため、C++側でみるとdataPtrの領域をゼロにしてしまってます。
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
すべての返信
-
VCのunsigned longは4バイトで、C#のulongは8バイトです。
C#側でulong型のlenに4を入れると上位バイトはゼロになっているため、C++側でみるとdataPtrの領域をゼロにしてしまってます。
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
-
ご指摘ありがとう御座います。
確かにIntPtrをそのまま使用してunsafeを外した方が良さそうです。
C#側は、以下のコードに修正しました。
意図した通り動作しております。using System; using System.Runtime.InteropServices; namespace SampleApp { class Program { // DLL内の関数の型を宣言 [DllImport("SampleDll.dll", CallingConvention = CallingConvention.Cdecl)] static extern int OutputBinary(IntPtr binaryData, IntPtr b); static void Main(string[] args) { byte[] byteArray = Convert.FromBase64String("VEVTVA=="); int ret; // バイト配列のメモリ確保 IntPtr arrPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(byteArray[0]) * byteArray.Length); // アンマネージド領域にコピー Marshal.Copy(byteArray, 0, arrPtr, byteArray.Length); // 構造体に入れる BinaryData binaryData = new BinaryData { len = Convert.ToUInt32(byteArray.Length), dataPtr = arrPtr }; // 構造体分のメモリ確保 IntPtr dataPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(binaryData)); // 構造体をアンマネージド領域にコピー Marshal.StructureToPtr(binaryData, dataPtr, false); try { // 関数呼び出し ret = OutputBinary(dataPtr, arrPtr); } finally { // メモリ解放 Marshal.FreeCoTaskMem(dataPtr); Marshal.FreeCoTaskMem(arrPtr); } Console.WriteLine(ret); Console.Read(); } } // 構造体定義 [StructLayout(LayoutKind.Sequential)] internal struct BinaryData { public uint len; public IntPtr dataPtr; } }
-
.NET Frameworkにはマーシャリングと言って、マネージ(C#側)とアンマネージ(C++側)とのやり取りの際に自動変換を行う機能があります。その機能を制御することで、C#側はほとんど何もしなくても表現できると思います。
# 動作確認はしていません。
using System; using System.Runtime.InteropServices; namespace SampleApp { class Program { // C# 7.2のin;読み取り専用引数の参照渡し [DllImport("SampleDll.dll", CallingConvention = CallingConvention.Cdecl)] static extern int OutputBinary(in BinaryData binaryData, [MarshalAs(UnmanagedType.LPArray)] byte[] b); static void Main(string[] args) { var byteArray = Convert.FromBase64String("VEVTVA=="); var binaryData = new BinaryData { len = byteArray.Length, dataPtr = byteArray }; var ret = OutputBinary(binaryData, byteArray); Console.WriteLine(ret); Console.Read(); } } [StructLayout(LayoutKind.Sequential)] internal struct BinaryData { public int len; [MarshalAs(UnmanagedType.LPArray)] public byte[] dataPtr; } }
- 編集済み 佐祐理 2019年6月14日 22:21