I'm calling a C DLL function and need to supply the following C struct:
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;
The problem is with char **mFieldNames; I've tried marshalling automatically like this:
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPTStr, SizeConst = 9)]
public String[] mFieldNames;
This way I get an error in Marshal.SizeOf() – can't compute the correct size. Then I decided to deal with pointers manually. It's in fact just a pointer to the array of C strings. Here's my code which is leading to
System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
So I've screwed up pointers somewhere. The code seems OK to me, where is the 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;
}
Best Answer
The best way to Marshal nasty string pointers, especially double pointers within a struct is to simply use an IntPtr.
This will Marshal correctly albeit with a not so useful type. However if you understand the structure of the IntPtr it's very easy to get the resulting strings out.
The only real downside is that you will have to manually free the memory