5

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;
  }
1
  • Update: it breaks in sakeSearchForRecords(); Commented Sep 30, 2009 at 15:37

2 Answers 2

8

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 static 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

Sign up to request clarification or add additional context in comments.

1 Comment

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 = &list; // ??
1

A better way is simply to use unsafe code with sbyte which is the same as c-char (-128 to 127) 1 byte. You can write yourself some extern functions like alloc_txt, free_txt,etc.. for allocating and freeing from the heap. Mostly when I write with interop I do use unsafe code because IntPtr gets you the address but you still have to use extern functions to get members in the structure it points to or if a primitive have to Marshal methods to extract what the value is.

The only time you have to declare a c# structure as unsafe is if you are using actual pointers which you are not but using MarshalAs instead. I still would prefer you use unsafe pointers via MarshalAs(UnmanagedType.?) which allows you do deal with the members directly.

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.