(C#) AccessViolationException saat mendapatkan char ** dari C++ DLL

Saya telah menulis pustaka C++ dasar yang mengambil data dari server OPC UA dan memformatnya menjadi array string (char **). Saya telah mengonfirmasi bahwa ini berfungsi mandiri, tetapi sekarang saya mencoba memanggilnya dari program C# menggunakan DLL/pInvoke dan mengalami kesalahan memori yang serius.

C # utama saya:

List<String> resultList = new List<string>();
IntPtr inArr = new IntPtr();
inArr = Marshal.AllocHGlobal(inArr);
resultList = Utilities.ReturnStringArray(/*data*/,inArr);

Fungsi pembantu 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
    }

Dan terakhir, implementasi C++ DLL saya:

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);
        }
}

Apa yang harus dilakukan adalah, inisialisasi Daftar untuk menampung hasil akhir dan IntPtr untuk diisi sebagai char ** oleh C++ DLL, yang kemudian diproses kembali dalam C# dan diformat menjadi Daftar. Namun, AccessViolationException muncul setiap kali saya memanggil getTopLevelNodes dari C#. Apa yang dapat saya lakukan untuk memperbaiki masalah memori ini? Apakah ini cara terbaik untuk meneruskan array string melalui interop?

Terima kasih sebelumnya

Sunting: Saya masih mencari jawaban lain, jika ada cara yang lebih sederhana untuk mengimplementasikan interop array string antara C# dan DLL, beri tahu saya!


person T. Meads    schedule 20.12.2016    source sumber
comment
Tidak menjadikan ini sebagai jawaban karena saya tidak yakin dan tidak dapat mengujinya, tetapi sudahkah Anda mencoba menggunakan IntPtr[] alih-alih IntPtr karena Anda, dalam arti tertentu, meneruskan serangkaian pointer, bukan hanya satu pointer. Selain itu, sebagai catatan tambahan, saya yakin Anda mengalami kebocoran memori sebesar 1 karakter untuk elemen array pertama Anda, karena Anda mencari tempat untuk itu, lalu panggil malloc lagi pada iterasi pertama loop Anda.   -  person pstrjds    schedule 20.12.2016
comment
@pstrjds terima kasih, saya akan memperbaikinya   -  person T. Meads    schedule 20.12.2016
comment
Saya tidak tahu situasi Anda, tetapi apakah mungkin membuat pembungkus C++/CLI di sekitar panggilan C++ Anda? Kemudian Anda dapat memiliki kelas yang membuat panggilan yang mengisi vektor, tetapi kemudian memasukkan data tersebut ke dalam struktur .Net untuk dikonsumsi di sisi C# Anda.   -  person pstrjds    schedule 20.12.2016


Jawaban (1)


METODE 1 - Marshalling Struktur Tingkat Lanjut.

Daripada menyusun daftar, coba buat struct c# seperti ini:

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

Sekarang di c# marshall seperti ini:

IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(StringData)); // Into Unmanaged space

Dapatkan pointer ke struktur.

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

Sekarang di CPP:

#pragma pack(2)
/************CPP STRUCT**************/
struct StringDataCpp
{
    char * strings[]
}; 

Dan fungsinya:

extern "C" EXPORT void getTopLevelNodes(/*data*/,char *ret){ //just a byte pointer.   

struct StringDataCpp *m = reinterpret_cast<struct StringDataCpp*>(ret);

//..do ur thing ..//

}

Saya telah menggunakan pola ini dengan struct yang jauh lebih rumit juga. Kuncinya adalah Anda hanya menyalin byte demi byte dari c# dan menafsirkan byte demi byte di c++.

'Paket' adalah kuncinya di sini, untuk memastikan struct disejajarkan dengan cara yang sama di memori.

METODE 2 - Array byte sederhana dengan 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++;
     }

SEKARANG

CPP baru saja menerima char*. Anda memerlukan pembatas sekarang untuk memisahkan string. PERHATIKAN BAHWA STRING ANDA MUNGKIN MEMILIKI DELIMETER '\0' SUDAH MENGGUNAKAN ALGORITMA REPLACE UNTUK MENGGANTINYA DENGAN ';' ATAU SESUATU DAN TOKENIZE DENGAN MUDAH DALAM LOOP DI CPP MENGGUNAKAN STRTOK DENGAN ';' SEBAGAI DELIMITER ATAU GUNAKAN BOOST!

ATAU, coba buat array penunjuk byte jika memungkinkan.

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

Cara ini jauh lebih sederhana. Blok unsafe mengizinkan blok fixed dan blok tetap memastikan bahwa mekanisme manajemen memori c# tidak memindahkan data tersebut.

person sbail95    schedule 20.12.2016
comment
Ini tampaknya menjanjikan, saya akan mencoba menerapkannya dan menghubungi Anda kembali. Terima kasih! - person T. Meads; 20.12.2016
comment
@ T.Meads Karena ini adalah string, pastikan Anda memperhatikan hasil edit saya, Anda harus Marshal.sizeof(theStringData) karena pada saat runtime ukuran string akan ditentukan!. Idealnya struct akan berukuran deterministik. yaitu sejumlah array byte tertentu dengan ukuran tertentu. byte[5][12]. - person sbail95; 20.12.2016
comment
@ T.Meads sebenarnya karena Anda hanya menggunakan string, saya suka metode 2 yang saya tambahkan lebih baik untuk Anda. - person sbail95; 20.12.2016
comment
Masalahnya adalah program saya menangani data dalam jumlah besar hingga ribuan string sekaligus. Saya tidak berpikir meletakkan semuanya dalam satu string char * yang dibatasi besar akan baik, dari segi manajemen memori - person T. Meads; 20.12.2016
comment
Hmm oke Saya akan membiarkan semua ini sebagai alat yang mungkin bisa Anda gunakan, jadi inilah satu pemikiran lagi. Sadarilah bahwa satu IntPtr menunjuk ke HGlobal (heap global yang tidak dikelola) dari sana cpp harus menafsirkan di mana membagi memori ini menjadi string terpisah. Anda mungkin perlu memasukkan array IntPtrs yang masing-masing merupakan referensi ke salah satu string. Atau perbaiki byte* cp = anArrayOfBytePointers; Kemudian di cpp tafsirkan ulang itu ke array char* terpisah yang juga dapat digunakan untuk membuat std::strings - person sbail95; 20.12.2016