(c#)从c dll获取char **时的accessViolationException

(C#) AccessViolationException when getting char ** from C++ DLL

本文关键字:char 时的 accessViolationException 获取 dll      更新时间:2023-10-16

我已经写了一个基本的C 库,该库从OPC UA服务器获取数据,并将其格式化为一系列字符串(char **)。我已经确认它是独立的,但是现在我试图使用DLL/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);
        }
}

这应该做的是,初始化一个列表以保存最终结果,而INTPTR则由C DLL填充为char **,然后将其重新处理为C#并格式化为列表。但是,每次我从C#调用GetToplevelnodes时,都会抛出AccessVioLationException。我该怎么办来解决此内存问题?这是通过Interop通过一系列字符串的最佳方法吗?

预先感谢您

编辑:我仍在寻找更多答案,如果有一种更简单的方法可以实现C#和DLL之间的字符串数组,请让我知道!

方法1-高级结构编组。

与编组列表相反,尝试创建这样的C#结构:

[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct StringData
{
    public string [] mylist; /* maybe better yet byte[][] (never tried)*/
};

现在在C#Marshall中:

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 的字节来解释字节。

"包装"是这里的关键,以确保结构在内存中以相同的方式对齐。

方法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'已经使用替换算法将其替换为';''或者,使用strtok with';'作为定界符或使用Boost!

或者,如果可能,请尝试制作字节指针阵列。

Byte*[i] theStringStartPointers = &stringList[i]/* in a for loop*/
fixed(byte* *cp = theStringStartPointers) /// Continue

这种方式要简单得多。unsafe块允许fixed块,固定块确保C#内存管理机制不会移动该数据。