在C#中将字符串数组编组为char **

我正在调用C DLL函数,需要提供以下C结构:

typedef struct { char *mTableId; char **mFieldNames; int mNumFields; char *mFilter; char *mSort; int mOffset; int mMaxRecords; char *mTargetRecordFilter; int mSurroundingRecordsCount; int *mOwnerIds; int mNumOwnerIds; gsi_bool mCacheFlag; } SAKESearchForRecordsInput; 

问题在于char ** mFieldNames; 我试过像这样自动编组:

[MarshalAs(UnmanagedType.LPArray,ArraySubType = UnmanagedType.LPTStr,SizeConst = 9)] public String [] mFieldNames;

这样我在Marshal.SizeOf()中得到一个错误 – 无法计算正确的大小。 然后我决定手动处理指针。 它实际上只是指向C字符串数组的指针。 这是我的代码导致的

System.AccessViolationException:尝试读取或写入受保护的内存。 这通常表明其他内存已损坏。

所以我搞砸了指针。 代码对我来说似乎没问题,bug在哪里?

C#:

  [StructLayout(LayoutKind.Sequential)] unsafe public class SAKESearchForRecordsInput { [MarshalAs(UnmanagedType.LPTStr)] public String mTableId; //[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPTStr, SizeConst = 9)] // HARDCODED!?! //public String[] mFieldNames; // char **mFieldNames; public IntPtr mFieldNames; public int mNumFields; [MarshalAs(UnmanagedType.LPTStr)] public String mFilter; [MarshalAs(UnmanagedType.LPTStr)] public String mSort; public int mOffset; public int mMaxRecords; //[MarshalAs(UnmanagedType.LPTStr)] public IntPtr mTargetRecordFilter; public int mSurroundingRecordsCount; public IntPtr mOwnerIds; public int mNumOwnerIds; public gsi_bool mCacheFlag; } [DllImport("saketestd.dll")] unsafe static extern void* sakeSearchForRecords( IntPtr sake, IntPtr input, //SAKESearchForRecordsInput * SAKERequestCallback callback, //SAKERequestCallback IntPtr userData); unsafe public bool sakeSearchForRecordsE() { bool ret = false; try { searchInput.mTableId = "bbdx_score"; //searchInput.mFieldNames = mFieldNames.to; searchInput.mFilter = "num_ratings = 0 AND filestore > 0"; searchInput.mSort = ""; searchInput.mOffset = 0; searchInput.mMaxRecords = 1; //searchInput.mTargetRecordFilter = ""; searchInput.mSurroundingRecordsCount = 0; searchInput.mOwnerIds = IntPtr.Zero; searchInput.mNumOwnerIds = 0; searchInput.mCacheFlag = true; int sakeSize = Marshal.SizeOf(sake); debug.AddLine(this.getMethodName() + ": sizeof(sake): " + sakeSize); IntPtr pSake = Marshal.AllocHGlobal(sakeSize); Marshal.StructureToPtr(sake, pSake, true); int inputSize = Marshal.SizeOf(searchInput); debug.AddLine(this.getMethodName() + ": sizeof(input): " + inputSize); IntPtr pInput = Marshal.AllocHGlobal(inputSize); Marshal.StructureToPtr(searchInput, pInput, true); IntPtr[] mFieldNamesPtr; int i; if (true) { // IntPtr[] mFieldNamesPtr = new IntPtr[mFieldNames.Length]; i = 0; foreach (string str in mFieldNames) { mFieldNamesPtr[i++] = Marshal.StringToHGlobalAnsi(str); } //searchInput.mFieldNames = mFieldNamesPtr; } else { //searchInput.mFieldNames = mFieldNames; } searchInput.mNumFields = mFieldNames.Length; void* pRequestInternal = null; void* p = mFieldNamesPtr[0].ToPointer(); searchInput.mFieldNames = (IntPtr)p; pRequestInternal = sakeSearchForRecords( pSake, pInput, new SAKERequestCallback(this.sakeSearchForRecordsCB), IntPtr.Zero ); sake = (SAKEInternal)Marshal.PtrToStructure(pSake, typeof(SAKEInternal)); if (searchRequest == null) { debug.AddLine(this.getMethodName() + ": mStartRequestResult: " + sake.mStartRequestResult); } else { ret = true; this.searchRequest = (SAKERequestInternal)Marshal.PtrToStructure( new IntPtr(pRequestInternal), typeof(SAKERequestInternal) ); searchInput = (SAKESearchForRecordsInput)Marshal.PtrToStructure( pInput, typeof(SAKESearchForRecordsInput) ); if (true) { i = 0; foreach (string str in mFieldNames) { Marshal.FreeHGlobal(mFieldNamesPtr[i++]); } } PrintStruct ps = new PrintStruct(sake); debug.AddLine(this.getMethodName() + ": sake: " + ps); ps = new PrintStruct(searchRequest); debug.AddLine(this.getMethodName() + ": searchRequest: " + ps.print_r()); ps = new PrintStruct(searchInput); debug.AddLine(this.getMethodName() + ": searchInput: " + ps.print_r()); } Marshal.FreeHGlobal(pSake); Marshal.FreeHGlobal(pInput); } catch (Exception ex) { debug.Text += ex.ToString(); } return ret; } 

制作令人讨厌的字符串指针的最佳方法,尤其是结构中的双指针,只需使用IntPtr。

 public IntPtr mFieldNames; 

这将是正确的元帅,尽管有一种不那么有用的类型。 但是,如果您了解IntPtr的结构,则很容易将结果字符串输出。

 public static List GetAllStrings(IntPtr ptr, int size) { var list = new List(); for ( int i = 0; i < size; i++ ) { var strPtr = (IntPtr)Marshal.PtrToStructure(ptr, typeof(IntPtr)); list.Add(Marshal.PtrToStringUni(strPtr)); ptr = new IntPtr(ptr.ToInt64()+IntPtr.Size); } return list; } 

唯一真正的缺点是你必须手动释放内存

更好的方法是使用带有sbyte的不安全代码,这与c-char(-128到127)1字节相同。 您可以自己编写一些外部函数,如alloc_txt,free_txt等,用于分配和释放堆。 大多数情况下,当我使用interop编写时,我确实使用了不安全的代码,因为IntPtr会为您提供地址,但您仍然必须使用extern函数来获取它所指向的结构中的成员,或者如果基元必须使用Marshal方法来提取值。

唯一一次你必须声明ac#结构是不安全的,如果你使用的是实际的指针,你不是使用MarshalAs。 我仍然希望你通过MarshalAs(UnmanagedType。?)使用不安全的指针,它允许你直接处理成员。

 [Struct(Layout.Sequential)] public unsafe struct SAKESearchForRecordsInput { sbyte*mTableId; sbyte**mFieldNames; int mNumFields; sbyte*mFilter; sbyte*mSort; int mOffset; int mMaxRecords; char*mTargetRecordFilter; int mSurroundingRecordsCount; int*mOwnerIds; int mNumOwnerIds; bool mCacheFlag;//?don't know what the typedef for the bytes };