(C#) AccessViolationException เมื่อรับถ่าน ** จาก C++ DLL

ฉันได้เขียนไลบรารี C ++ พื้นฐานที่รับข้อมูลจากเซิร์ฟเวอร์ OPC UA และจัดรูปแบบเป็นอาร์เรย์ของสตริง (อักขระ **) ฉันยืนยันว่ามันทำงานแบบสแตนด์อโลน แต่ตอนนี้ฉันกำลังพยายามเรียกมันจากโปรแกรม C# ที่ใช้ DLLs/pInrigg และพบข้อผิดพลาดร้ายแรงของหน่วยความจำ

หลัก 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 ซึ่งจะถูกประมวลผลกลับใน C# และจัดรูปแบบเป็นรายการ อย่างไรก็ตาม AccessViolationException จะถูกส่งทุกครั้งที่ฉันเรียก getTopLevelNodes จาก C# ฉันจะทำอย่างไรเพื่อแก้ไขปัญหาหน่วยความจำนี้ นี่เป็นวิธีที่ดีที่สุดในการส่งอาร์เรย์ของสตริงผ่านการทำงานร่วมกันหรือไม่

ขอบคุณล่วงหน้า

แก้ไข: ฉันยังคงมองหาคำตอบเพิ่มเติม หากมีวิธีที่ง่ายกว่าในการใช้การทำงานร่วมกันของอาร์เรย์สตริงระหว่าง C# และ DLL โปรดแจ้งให้เราทราบ!


person T. Meads    schedule 20.12.2016    source แหล่งที่มา
comment
ไม่ได้ใส่สิ่งนี้เป็นคำตอบเพราะฉันไม่แน่ใจและไม่สามารถทดสอบได้ แต่คุณลองใช้ IntPtr[] แทน IntPtr แล้วในแง่หนึ่งคุณส่งผ่านอาร์เรย์ของพอยน์เตอร์ ไม่ใช่เพียงแค่พอยน์เตอร์ตัวเดียว นอกจากนี้ เช่นเดียวกับหมายเหตุด้านข้าง ฉันเชื่อว่าคุณมีหน่วยความจำรั่ว 1 อักขระสำหรับองค์ประกอบอาเรย์แรกของคุณ เนื่องจากคุณ malloc จุดสำหรับมัน จากนั้นเรียก malloc อีกครั้งในการวนซ้ำครั้งแรกของคุณ   -  person pstrjds    schedule 20.12.2016
comment
@pstrjds ขอบคุณ ฉันจะแก้ไขมัน   -  person T. Meads    schedule 20.12.2016
comment
ฉันไม่ทราบสถานการณ์ของคุณ แต่เป็นไปได้ไหมที่จะสร้าง wrapper C++/CLI รอบๆ การโทร C++ ของคุณ จากนั้นคุณสามารถมีคลาสที่ทำการเรียกที่เติมเวกเตอร์ แต่จากนั้นดันข้อมูลนั้นเข้าไปในโครงสร้าง .Net เพื่อการใช้งานบนฝั่ง C# ของคุณ   -  person pstrjds    schedule 20.12.2016


คำตอบ (1)


วิธีที่ 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 เพิ่งได้รับถ่าน* ตอนนี้คุณจะต้องมีตัวคั่นเพื่อแยกสตริง โปรดทราบว่าสตริงของคุณอาจมี DELIMETER '\0' ใช้อัลกอริธึมการแทนที่แล้วเพื่อแทนที่ด้วย ';' หรือบางสิ่งบางอย่างและโทเค็นได้อย่างง่ายดายในการวนซ้ำใน CPP โดยใช้ STRTOK ด้วย ';' เป็นตัวคั่นหรือใช้เพิ่ม!

หรือลองสร้างอาร์เรย์ตัวชี้ไบต์ถ้าเป็นไปได้

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

วิธีนี้ง่ายกว่ามาก บล็อก unsafe อนุญาตให้บล็อก fixed และการแก้ไขทำให้มั่นใจได้ว่ากลไกการจัดการหน่วยความจำ c# จะไม่ย้ายข้อมูลนั้น

person sbail95    schedule 20.12.2016
comment
ดูเหมือนว่าจะมีแนวโน้มดี ฉันจะพยายามนำไปใช้และติดต่อกลับ ขอบคุณ! - person T. Meads; 20.12.2016
comment
@T.Meads เนื่องจากสิ่งเหล่านี้เป็นสตริงตรวจสอบให้แน่ใจว่าคุณสังเกตเห็นการแก้ไขของฉัน คุณต้อง Marshal.sizeof(theStringData) เนื่องจากอยู่ที่รันไทม์ที่จะกำหนดขนาดสตริง! ตามหลักการแล้ว โครงสร้างจะต้องมีขนาดที่กำหนด คือจำนวนอาร์เรย์ไบต์ในขนาดที่แน่นอน ไบต์[5][12]. - person sbail95; 20.12.2016
comment
@ T.Meads เป็นเรื่องจริงเนื่องจากคุณแค่ใช้สตริงฉันชอบวิธีที่ 2 ที่ฉันเพิ่มไว้ดีกว่าสำหรับคุณ - person sbail95; 20.12.2016
comment
ปัญหาคือโปรแกรมของฉันจัดการข้อมูลจำนวนมากโดยมีสตริงได้ครั้งละหลายพันสตริง ฉันไม่คิดว่าการใส่ทั้งหมดไว้ในสตริง char * ที่คั่นขนาดใหญ่เส้นเดียวจะดี การจัดการหน่วยความจำที่ชาญฉลาด - person T. Meads; 20.12.2016
comment
อืม โอเค ฉันจะปล่อยให้ทั้งหมดนี้เป็นเพียงเครื่องมือที่เป็นไปได้ที่คุณสามารถใช้ได้ ดังนั้นนี่คืออีกหนึ่งแนวคิด เพิ่งรู้ว่า IntPtr ตัวหนึ่งชี้ไปที่ HGlobal (ฮีปที่ไม่มีการจัดการทั่วโลก) จากนั้น cpp จะต้องตีความว่าจะแยกหน่วยความจำนี้ออกเป็นสตริงแยกจากกันที่ไหน คุณอาจต้องส่งผ่านอาร์เรย์ของ IntPtrs ซึ่งแต่ละรายการเป็นการอ้างอิงถึงสตริงตัวใดตัวหนึ่ง หรือแก้ไข byte* cp = anArrayOfBytePointers; จากนั้นใน cpp ตีความใหม่ว่าเป็นอาร์เรย์ของ char* ที่แยกกันซึ่งสามารถใช้สร้าง std::strings ได้ - person sbail95; 20.12.2016