从 C# 调用非托管代码 - 返回带有数组的结构
-
22-07-2019 - |
题
[编辑]我按照斯蒂芬·马丁的建议更改了来源(以粗体突出显示)。并添加了 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
在该结构内有 char[]
s,尚未被填充,导致指针错误。这就是为什么我试图提取第一个的子集 SYSTEM_CHARACTERISTICS
项目在 STATUS_DATA
.
#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
struct 并且返回值不是预期的 0。这是一个七位数的变化数字,这让我很困惑......我把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个字节。在我的项目就变成了所有短整型(2个字节)领域充满了伪字节在非托管的DLL 4个字节。如果是这样的问题,您应该马歇尔的结构是这样的: 醇>
- 如果字符串是ANSI空终止字符串可以注释它们为: 醇>
[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;
您实际上并未将任何数据编组到托管端。当你声明 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模拟它,一切都使得它回来的罚款;两者的返回值和输出结构是正确的。
可能什么是不对劲您的项目设置?这听起来很愚蠢,但你提到的运行和调试unamnaged代码;你正在建设一个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应分配的COM堆上,如果它希望主叫方释放内存,或返回像IMalloc回调接口以释放返回的内存。这意味着你需要接收结果存储器地址作为IntPtr的和使用System.Runtime.InteropServices.Marshal释放内存之前从本地数据复制到管理堆(可以是一个结构)。
编辑为更新的函数签名: 使用公共静态外部的getStatus INT(参照SYSTEM_OUTPUT输出);您还没有分配上的本机函数的COM堆,所以出是不必要的。
你有没有考虑增加一个C ++ / CLI装配到您的项目?这是弥合托管和非托管代码之间的差距非常简单而强大的方式。我用它了很多我自己。