views:

762

answers:

1

Hi,

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;
  }
+2  A: 

The best way to Marshal nasty string pointers, especially double pointers within a struct is to simply use an IntPtr.

public IntPtr mFieldNames;

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.

public List<string> GetAllStrings(IntPtr ptr, int size) {
  var list = new List<string>();
  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;
}

The only real downside is that you will have to manually free the memory

JaredPar
If I would try to use your function (I have a similar one if you look at my code (using array)), how can I get IntPtr from the "List<string>"?IntPtr pList = // ??
Slawa