C#からアンマネージコードを呼び出す-配列を含む構造体を返す
-
22-07-2019 - |
質問
[編集] Stephen Martinが示唆するようにソースを変更しました(太字で強調表示)。また、C ++ソースコードも追加しました。
自己記述C ++ dllでアンマネージ関数を呼び出したいです。このライブラリは、サードパーティのソフトウェアのステータス情報についてマシンの共有メモリを読み取ります。いくつかの値があるため、構造体で値を返したいです。ただし、構造体内には char []
(固定サイズのcharの配列)があります。私は今、次のようなdll呼び出しからその構造体を受信しようとします:
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_OUTPUT
{
UInt16 ReadyForConnect;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
String VersionStr;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
String NameOfFile;
// actually more of those
}
public partial class Form1 : Form
{
public SYSTEM_OUTPUT output;
[DllImport("testeshm.dll", EntryPoint="getStatus")]
public extern static int getStatus(out SYSTEM_OUTPUT output);
public Form1()
{
InitializeComponent();
}
private void ReadSharedMem_Click(object sender, EventArgs e)
{
try
{
label1.Text = getStatus(out output).ToString();
}
catch (AccessViolationException ave)
{
label1.Text = ave.Message;
}
}
}
c ++ dllからもコードを投稿しますが、さらに探すべきものがあると確信しています。元の構造体 STATUS_DATA
には、構造体 SYSTEM_CHARACTERISTICS
の4つのインスタンスの配列があり、その構造体内には char []
があります。 (まだ)塗りつぶされており、ポインタが正しくありません。そのため、 STATUS_DATA
の最初の SYSTEM_CHARACTERISTICS
アイテムのサブセットを抽出しようとしています。
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include <iostream>
#if defined(_MSC_VER)
#include <windows.h>
#define DLL extern "C" __declspec(dllexport)
#else
#define DLL
#endif
using namespace std;
enum { SYSID_LEN = 1024, VERS_LEN = 128, SCENE_LEN = 1024 };
enum { MAX_ENGINES = 4 };
struct SYSTEM_CHARACTERISTICS
{
unsigned short ReadyForConnect;
char VizVersionStr[VERS_LEN];
char NameOfFile[SCENE_LEN];
char Unimplemented[SCENE_LEN]; // not implemented yet, resulting to bad pointer, which I want to exclude (reason to have SYSTEM_OUTPUT)
};
struct SYSTEM_OUTPUT
{
unsigned short ReadyForConnect;
char VizVersionStr[VERS_LEN];
char NameOfFile[SCENE_LEN];
};
struct STATUS_DATA
{
SYSTEM_CHARACTERISTICS engine[MAX_ENGINES];
};
TCHAR szName[]=TEXT("E_STATUS");
DLL int getStatus(SYSTEM_OUTPUT* output)
{
HANDLE hMapFile;
STATUS_DATA* pBuf;
hMapFile = OpenFileMapping(
FILE_MAP_READ, // read access
FALSE, // do not inherit the name
szName); // name of mapping object
if (hMapFile == NULL)
{
_tprintf(TEXT("Could not open file mapping object (%d).\n"),
GetLastError());
return -2;
}
pBuf = (STATUS_DATA*) MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);
if (pBuf == NULL)
{
_tprintf(TEXT("Could not map view of file (%d).\n"),
GetLastError());
CloseHandle(hMapFile);
return -1;
}
output->ReadyForConnect = pBuf->engine[0].ReadyForConnect;
memcpy(output->VizVersionStr, pBuf->engine[0].VizVersionStr, sizeof(pBuf->engine[0].VizVersionStr));
memcpy(output->NameOfFile, pBuf->engine[0].NameOfFile, sizeof(pBuf->engine[0].NameOfFile));
CloseHandle(hMapFile);
UnmapViewOfFile(pBuf);
return 0;
}
現在、空の output
構造体を取得していますが、戻り値は意図したとおり0ではありません。それはむしろ7桁の数字の変化であり、私を困惑させます... DLLを台無しにしましたか?アンマネージコードを実行可能にしてデバッグすると、 output
に適切な値が入力されていることがわかります。
解決
構造体で情報を返す場合、標準メソッドは、構造体へのポインタをメソッドのパラメーターとして渡すことです。このメソッドは、構造体のメンバーを埋めてから、何らかのステータスコード(またはブール値)を返します。そのため、C ++メソッドを変更してSYSTEM_OUTPUT *を取得し、成功またはエラーコードの場合は0を返します。
public partial class Form1 : Form
{
public SYSTEM_OUTPUT output;
[DllImport("testeshm.dll", EntryPoint="getStatus")]
public extern static int getStatus(out SYSTEM_OUTPUT output);
public Form1()
{
InitializeComponent();
}
private void ReadSharedMem_Click(object sender, EventArgs e)
{
try
{
if(getStatus(out output) != 0)
{
//Do something about error.
}
}
catch (AccessViolationException ave)
{
label1.Text = ave.Message;
}
}
}
他のヒント
- ReadyForConnectフィールドが4バイトまで埋められていないことを確認してください。私のプロジェクトでは、すべてのshort int(2バイト)フィールドは、アンマネージDLLのダミーバイトから4バイトで埋められていました。それが問題である場合、次のように構造体をマーシャルする必要があります。
[StructLayout(LayoutKind.Sequential)] public struct SYSTEM_OUTPUT { [MarshalAs(UnmanagedType.I2)] UInt16 ReadyForConnect; [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.I1, SizeConst=2)] byte[] aligment; // 2 byte aligment up to 4 bytes margin [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] String VersionStr; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)] String NameOfFile; // ... }
- 文字列がANSI null終了文字列の場合、次のように注釈を付けることができます。
[MarshalAs(UnmanagedType.LPStr)] public String VersionStr;
実際には、管理対象側にデータをマーシャリングしているわけではありません。管理側で output
を宣言すると、デフォルト値は null
です。次に、非管理側では、 output
にメモリを割り当てないでください。アンマネージメモリを割り当て、そのメモリへのポインタをdll関数に渡し、そのメモリのポインタを構造体にマーシャリングする必要があります。
[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Ansi)]
public struct SYSTEM_OUTPUT
{
UInt16 ReadyForConnect;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
String VersionStr;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
String NameOfFile;
// actually more of those
}
public partial class Form1 : Form
{
public SYSTEM_OUTPUT output;
[DllImport("testeshm.dll", EntryPoint="getStatus")]
public extern static int getStatus(IntPtr output);
public Form1()
{
InitializeComponent();
}
private void ReadSharedMem_Click(object sender, EventArgs e)
{
IntPtr ptr;
try
{
ptr = Marshall.AllocHGlobal(Marshall.SizeOf(typeof(SYSTEM_OUTPUT)));
int ret = getStatus(ptr);
if(ret == 0)
{
output = (SYSTEM_OUTPUT)Marshal.PtrToStructure(ptr, typeof(SYSTEM_OUTPUT));
}
//do something with output
label1.Text = ret;
}
catch (AccessViolationException ave)
{
label1.Text = ave.Message;
}
finally
{
Marshal.FreeHGlobal(ptr); //make sure to free the memory
}
}
}
編集:
あなたの問題は、パッキング戦略。構造体の定義を更新しました。
編集:この答え全体を書き直しています。
C ++とC#の両方のコードをすべて取得し、ソリューションにドロップして実行しました。すべてがうまくいきました。特定のメモリマッピングはありませんでしたので、pBufに偽のデータを入力してシミュレートしましたが、すべて正常に戻ります。戻り値と出力構造の両方が正しい。
プロジェクト設定で何かおかしいことがありますか?これは馬鹿げているように聞こえますが、あなたは修正されていないコードの実行とデバッグに言及しました。 dllを正しく構築していますか?
あなたがしようとしていることは可能ですが、間違った問題を解決していると思います。
メモリマップファイルをC#から直接読み取らないのはなぜですか? Winterdom.IO.FileMap
をご覧ください。使用しましたが、正常に動作します。
MemoryMappedFile file = MemoryMappedFile.Open(FileMapRead, name);
using (Stream stream = memoryMappedFile.MapView(MapAccess.FileMapAllAccess, 0, length))
{
// here read the information that you need
}
これで終わりではありません-バイトバッファを構造体に変換する必要がありますが、管理された側にいるので簡単です。
誰が構造にメモリを割り当てましたか?マネージヒープからネイティブメモリを削除することはできません。一般的に、ネイティブDLLは、呼び出し元がメモリを解放するか、IMallocなどのコールバックインターフェイスを返し、返されたメモリを解放すると予想される場合、COMヒープに割り当てる必要があります。つまり、結果メモリアドレスをIntPtrとして受け取り、System.Runtime.InteropServices.Marshalを使用して、メモリを解放する前に、ネイティブから管理ヒープ(構造体など)にデータをコピーする必要があります。
更新された関数シグネチャの編集: public static extern int getStatus(ref SYSTEM_OUTPUT output);を使用します。ネイティブ関数でCOMヒープに割り当てていないので、出力は不要です。
C ++ / CLIアセンブリをプロジェクトに追加することを検討しましたか?これは、マネージコードとアンマネージコードのギャップを埋める非常に簡単で強力な方法です。私は自分でかなり多く使用しています。