質問

[編集] 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;
        }
    }
}

他のヒント

  1. 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;        // ...
    }
  1. 文字列が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アセンブリをプロジェクトに追加することを検討しましたか?これは、マネージコードとアンマネージコードのギャップを埋める非常に簡単で強力な方法です。私は自分でかなり多く使用しています。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top