从C#调用非托管代码-返回带数组的结构

从C#调用非托管代码-返回带数组的结构,c#,c++,pinvoke,ipc,marshalling,C#,C++,Pinvoke,Ipc,Marshalling,[编辑]我按照斯蒂芬·马丁(Stephen Martin)的建议更改了来源(以粗体突出显示)。并添加了C++源代码。 < >我想在一个自编写的C++ DLL中调用一个非托管函数。此库读取机器的共享内存以获取第三方软件的状态信息。因为有两个值,所以我想在结构中返回这些值。但是,在结构中有char[](具有固定大小的char数组)。我现在尝试从dll调用接收该结构,如下所示: [StructLayout(LayoutKind.Sequential)] public struct SYSTEM_OUT

[编辑]我按照斯蒂芬·马丁(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\u DATA
有一个由结构
SYSTEM\u特征的四个实例组成的数组
,并且在该结构中有
char[]
s未被填充,导致指针错误。这就是为什么我试图提取
STATUS\u DATA
中第一个
SYSTEM\u特征
项的子集

#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;
}
#包括
#包括
#包括
#包括
#包括
#如果已定义(\u MSC\u VER)
#包括
#定义DLL外部“C”\uuu declspec(dllexport)
#否则
#定义DLL
#恩迪夫
使用名称空间std;
枚举{SYSID_LEN=1024,VERS_LEN=128,SCENE_LEN=1024};
枚举{MAX_引擎=4};
结构系统特性
{
未签名的短准备连接;
char-VizVersionStr[VERS_LEN];
char NameOfFile[场景];
char Unimplemented[SCENE_LEN];//尚未实现,导致指针错误,我想排除它(有系统_输出的原因)
};
结构系统输出
{
未签名的短准备连接;
char-VizVersionStr[VERS_LEN];
char NameOfFile[场景];
};
结构状态数据
{
系统特性引擎[最大引擎];
};
TCHAR szName[]=文本(“E_状态”);
DLL int getStatus(系统输出*输出)
{
处理hMapFile;
状态数据*pBuf;
hMapFile=OpenFileMapping(
文件\u映射\u读取,//读取访问权限
FALSE,//不继承名称
szName);//映射对象的名称
if(hMapFile==NULL)
{ 
_tprintf(文本(“无法打开文件映射对象(%d)。\n”),
GetLastError());
返回-2;
} 
pBuf=(状态数据*)映射视图文件(hMapFile,文件映射读取,0,0);
if(pBuf==NULL)
{ 
_tprintf(文本(“无法映射文件(%d)的视图)。\n”),
GetLastError());
CloseHandle(hMapFile);
返回-1;
}
输出->ReadyForConnect=pBuf->引擎[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);
未经批准的文件(pBuf);
返回0;
}

现在我得到一个空的
output
struct,返回值不是预期的0。这是一个不断变化的七位数数字,这让我感到困惑。。。我把dll搞砸了吗?如果我使非托管代码可执行并调试它,我可以看到,
输出
中填充了适当的值。

在结构中返回信息时,标准方法是将指向结构的指针作为方法的参数传递。该方法填充结构成员,然后返回某种状态代码(或布尔值)。因此,您可能希望更改C++方法以获取SyrSoReult*,并返回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;
        }
    }
}

谁为结构分配了内存?无法从托管堆中删除本机内存。一般来说,如果本机DLL希望调用者释放内存,或者返回一个回调接口(如IMalloc)来释放返回的内存,那么它应该在COM堆上进行分配。这意味着您需要以IntPtr的形式接收结果内存地址,并在释放内存之前使用System.Runtime.InteropServices.Marshal将数据从本机复制到托管堆(可能是结构)

编辑更新的函数签名:
使用公共静态外部int getStatus(参考系统输出);您没有在本机函数的COM堆上进行分配,因此out是不必要的。

您实际上没有将任何数据封送到托管端。在托管端声明
output
时,其默认值为
null
。然后,在非托管端,您永远不会为
输出分配任何内存。您应该分配一些非托管内存,将指向该内存的指针传递给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++/CLI程序集添加到您的项目中?这是弥合托管代码和非托管代码之间差距的一种非常简单和强大的方法。我自己也经常用它

  • 确保ReadyForConnect字段的填充长度不超过4字节。在我的项目中,所有的短int(2字节)字段都被非托管DLL中的伪字节填充到了4字节。如果这是问题所在,您应该以这种方式对结构进行封送:
  • [StructLayout(LayoutKind.Sequential)] 公共结构系统输出 { [Marshallas(UnmanagedType.I2)] UInt16已准备好进行连接; [Marshallas(UnmanagedType.ByValArray,ArraySubType=UnmanagedType.I1,SizeConst=2)] byte[]aligment;//2字节aligment最多4字节边距 [Marshallas(UnmanagedType.ByValTStr,SizeConst=128)] 字符串版本STR; [Marshallas(UnmanagedType.ByValTStr,SizeConst=1024)] 字符串名称文件;//。。。 }
  • 如果字符串是以ANSI null结尾的字符串,则可以将其注释为:
  • [Marshallas(UnmanagedType.LPSt [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; // ... } [MarshalAs(UnmanagedType.LPStr)] public String VersionStr;
    MemoryMappedFile file = MemoryMappedFile.Open(FileMapRead, name);
    using (Stream stream = memoryMappedFile.MapView(MapAccess.FileMapAllAccess, 0, length))
    {
        // here read the information that you need   
    }