(C#) 从 C++ DLL 获取 char ** 时出现 AccessViolationException
(C#) AccessViolationException when getting char ** from C++ DLL
我编写了一个基本的 C++ 库,它从 OPC UA 服务器获取数据并将其格式化为字符串数组 (char **)。我已经确认它可以独立运行,但现在我正尝试使用 DLLs/pInvoke 和 运行 从 C# 程序调用它,导致严重的内存错误。
我的 C# 主程序:
List<String> resultList = new List<string>();
IntPtr inArr = new IntPtr();
inArr = Marshal.AllocHGlobal(inArr);
resultList = Utilities.ReturnStringArray(/*data*/,inArr);
C# 辅助函数:
public class Utilities{
[DllImport(//DllArgs- confirmed to be correct)]
private static extern void getTopLevelNodes(/*data*/, IntPtr inArr);
public static List<String> ReturnStringArray(/*data*/,IntPtr inArr)
{
getTopLevelNodes(/*data*/,inArr); // <- this is where the AccessViolationException is thrown
//functions that convert char ** to List<String>
//return list
}
最后,我的 C++ DLL 实现:
extern "C" EXPORT void getTopLevelNodes(*/data*/,char **ret){
std::vector<std::string> results = std::vector<std::string>();
//code that fills vector with strings from server
ret = (char **)realloc(ret, sizeof(char *));
ret[0] = (char *)malloc(sizeof(char));
strcpy(ret[0], "");
int count = 0;
int capacity = 1;
for (auto string : results){
ret[count] = (char*)malloc(sizeof(char) * 2048);
strcpy(ret[count++], string.c_str());
if (count == capacity){
capacity *= 2;
ret = (char **)realloc(ret, sizeof(char *)*capacity + 1);
}
}
这应该做的是,初始化一个 List 来保存最终结果,并初始化 IntPtr 以由 C++ DLL 填充为 char **,然后在 C# 中对其进行处理并格式化为一个 List。但是,每次我从 C# 调用 getTopLevelNodes 时都会抛出 AccessViolationException。我该怎么做才能解决此内存问题?这是通过互操作传递字符串数组的最佳方式吗?
提前致谢
编辑:
我仍在寻找更多答案,如果有更简单的方法在 C# 和 DLL 之间实现字符串数组互操作,请告诉我!
方法 1 - 高级结构编组。
与编组列表相反,尝试像这样创建一个 c# 结构:
[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct StringData
{
public string [] mylist; /* maybe better yet byte[][] (never tried)*/
};
现在在 c# 编组中是这样的:
IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(StringData)); // Into Unmanaged space
获取指向结构的指针。
StringData theStringData = /*get the data*/;
Marshal.StructureToPtr(theStringData, pnt, false);
// Place structure into unmanaged space.
getTopLevelNodes(/* data */, pnt); // call dll
theStringData =(StringData)Marshal.PtrToStructure(pnt,typeof(StringData));
//get structure back from unmanaged space.
Marshal.FreeHGlobal(pnt); // Free shared mem
现在在 CPP 中:
#pragma pack(2)
/************CPP STRUCT**************/
struct StringDataCpp
{
char * strings[]
};
和函数:
extern "C" EXPORT void getTopLevelNodes(/*data*/,char *ret){ //just a byte pointer.
struct StringDataCpp *m = reinterpret_cast<struct StringDataCpp*>(ret);
//..do ur thing ..//
}
我也将此模式用于更复杂的结构。关键是您只是从 C# 中逐字节复制并在 C++ 中逐字节解释。
'pack' 是这里的关键,以确保结构在内存中以相同的方式对齐。
方法 2 - fixed
的简单字节数组
//USE YOUR LIST EXCEPT List<byte>.
unsafe{
fixed (byte* cp = theStringData.ToArray)
{
getTopLevelNodes(/* data */, cp)
/////...../////
//SNIPPET TO CONVERT STRING ARRAY TO BYTE ARRAY
string[] stringlist = (/* get your strings*/);
byte[] theStringData = new stringlist [stringlist .Count()];
foreach (string b in parser)
{
// ADD SOME DELIMITER HERE FOR CPP TO SPLIT ON?
theStringData [i] = Convert.ToByte(stringlist [i]);
i++;
}
现在
CPP 刚刚接收到 char*。您现在需要一个定界符来分隔字符串。
请注意,您的字符串可能有定界符“\0”,已经使用替换算法将其替换为“;”或者在 CPP 的循环中使用 STRTOK 和 ';' 轻松地标记一些东西作为分隔符或使用 BOOST!
或者,如果可能的话,尝试制作一个字节指针数组。
Byte*[i] theStringStartPointers = &stringList[i]/* in a for loop*/
fixed(byte* *cp = theStringStartPointers) /// Continue
这种方式简单多了。 unsafe
块允许 fixed
块并且 fixed 确保 c# 内存管理机制不会移动该数据。
我编写了一个基本的 C++ 库,它从 OPC UA 服务器获取数据并将其格式化为字符串数组 (char **)。我已经确认它可以独立运行,但现在我正尝试使用 DLLs/pInvoke 和 运行 从 C# 程序调用它,导致严重的内存错误。
我的 C# 主程序:
List<String> resultList = new List<string>();
IntPtr inArr = new IntPtr();
inArr = Marshal.AllocHGlobal(inArr);
resultList = Utilities.ReturnStringArray(/*data*/,inArr);
C# 辅助函数:
public class Utilities{
[DllImport(//DllArgs- confirmed to be correct)]
private static extern void getTopLevelNodes(/*data*/, IntPtr inArr);
public static List<String> ReturnStringArray(/*data*/,IntPtr inArr)
{
getTopLevelNodes(/*data*/,inArr); // <- this is where the AccessViolationException is thrown
//functions that convert char ** to List<String>
//return list
}
最后,我的 C++ DLL 实现:
extern "C" EXPORT void getTopLevelNodes(*/data*/,char **ret){
std::vector<std::string> results = std::vector<std::string>();
//code that fills vector with strings from server
ret = (char **)realloc(ret, sizeof(char *));
ret[0] = (char *)malloc(sizeof(char));
strcpy(ret[0], "");
int count = 0;
int capacity = 1;
for (auto string : results){
ret[count] = (char*)malloc(sizeof(char) * 2048);
strcpy(ret[count++], string.c_str());
if (count == capacity){
capacity *= 2;
ret = (char **)realloc(ret, sizeof(char *)*capacity + 1);
}
}
这应该做的是,初始化一个 List 来保存最终结果,并初始化 IntPtr 以由 C++ DLL 填充为 char **,然后在 C# 中对其进行处理并格式化为一个 List。但是,每次我从 C# 调用 getTopLevelNodes 时都会抛出 AccessViolationException。我该怎么做才能解决此内存问题?这是通过互操作传递字符串数组的最佳方式吗?
提前致谢
编辑: 我仍在寻找更多答案,如果有更简单的方法在 C# 和 DLL 之间实现字符串数组互操作,请告诉我!
方法 1 - 高级结构编组。
与编组列表相反,尝试像这样创建一个 c# 结构:
[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct StringData
{
public string [] mylist; /* maybe better yet byte[][] (never tried)*/
};
现在在 c# 编组中是这样的:
IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(StringData)); // Into Unmanaged space
获取指向结构的指针。
StringData theStringData = /*get the data*/;
Marshal.StructureToPtr(theStringData, pnt, false);
// Place structure into unmanaged space.
getTopLevelNodes(/* data */, pnt); // call dll
theStringData =(StringData)Marshal.PtrToStructure(pnt,typeof(StringData));
//get structure back from unmanaged space.
Marshal.FreeHGlobal(pnt); // Free shared mem
现在在 CPP 中:
#pragma pack(2)
/************CPP STRUCT**************/
struct StringDataCpp
{
char * strings[]
};
和函数:
extern "C" EXPORT void getTopLevelNodes(/*data*/,char *ret){ //just a byte pointer.
struct StringDataCpp *m = reinterpret_cast<struct StringDataCpp*>(ret);
//..do ur thing ..//
}
我也将此模式用于更复杂的结构。关键是您只是从 C# 中逐字节复制并在 C++ 中逐字节解释。
'pack' 是这里的关键,以确保结构在内存中以相同的方式对齐。
方法 2 - fixed
//USE YOUR LIST EXCEPT List<byte>.
unsafe{
fixed (byte* cp = theStringData.ToArray)
{
getTopLevelNodes(/* data */, cp)
/////...../////
//SNIPPET TO CONVERT STRING ARRAY TO BYTE ARRAY
string[] stringlist = (/* get your strings*/);
byte[] theStringData = new stringlist [stringlist .Count()];
foreach (string b in parser)
{
// ADD SOME DELIMITER HERE FOR CPP TO SPLIT ON?
theStringData [i] = Convert.ToByte(stringlist [i]);
i++;
}
现在
CPP 刚刚接收到 char*。您现在需要一个定界符来分隔字符串。 请注意,您的字符串可能有定界符“\0”,已经使用替换算法将其替换为“;”或者在 CPP 的循环中使用 STRTOK 和 ';' 轻松地标记一些东西作为分隔符或使用 BOOST!
或者,如果可能的话,尝试制作一个字节指针数组。
Byte*[i] theStringStartPointers = &stringList[i]/* in a for loop*/
fixed(byte* *cp = theStringStartPointers) /// Continue
这种方式简单多了。 unsafe
块允许 fixed
块并且 fixed 确保 c# 内存管理机制不会移动该数据。