[Mono-list] Memory Allocation in unmanaged code
Jonathan Pryor
jonpryor@vt.edu
Mon, 06 Sep 2004 18:57:25 -0400
Below...
On Fri, 2004-09-03 at 20:10, Jim Fehlig wrote:
> I am having difficulty with a C# wrapper for some unmanaged C code
> that allocates a list. Unmanaged code snippet:
> typedef struct CupsPrinterListStruct
> {
> char printerUri[1024];
> char printerCupsUri[1024];
> char printerName[1024];
> char printerMakeAndModel[256];
> struct CupsPrinterListStruct *nextPtr;
> }CupsPrinterList;
> int ListLocalPrinters(CupsPrinterList **printerList);
> int FreeLocalPrinterList(CupsPrinterList *listHead);
As Marcus mentioned, changing your C# struct to a class allows your code
to work. Whether that is correct is another matter.
Correctness depends upon the ListLocalPrinters() documentation. What is
`printerList' supposed to be? Just a pointer to a
CupsPrinterListStruct*? Or should it be an array of pointers? How many
elements will be addressed?
This is rather important because if ListLocalPrinters() uses
`printerList' as an array, then it will corrupt memory. If
ListLocalPrinters() *allocates* an array, then a C# class will only pick
up the first element of the array, and leak the rest. Further, you have
memory allocator issues: a C# "class" will cause the runtime to free the
returned pointer using CoTaskMemAlloc() (Windows) or g_free() (Linux).
Given that presumably FreeLocalPrinterList() should be used, this could
also lead to memory corruption.
The "safe" thing to do is (1) continue to use a C# struct, and (2) use
"unsafe" code and casting to retrieve information. The following
pseudo-code assumes that ListLocalPrinters() takes a pointer to a
CupsPrinterListStruct* (e.g. pointer to a pointer to a single element,
and ListLocalPrinters() will allocate that element).
// Warning: untested code, but should get the point across
// public user-visible class, NOT used for interop
public class PrinterList {
public string Uri, CupsUri,
Name, MakeModel;
}
public unsafe class PrintLibWrapper {
// Convert linked-list to an array
[DllImport]
private static extern int ListLocalPrinters (ref IntPtr p);
[DllImport]
private static extern int FreeLocalPrinterList(IntPtr p);
// struct for interop purposes
private unsafe struct CupsPrinterList {
// TODO: add element attributes
public string printerUri, printerCupsUri, printerName,
printerMakeModel;
public CupsPrinterList* nextElement;
}
// Convert linked list to array
public unsafe PrinterList[] ListLocalPrinters ()
{
IntPtr list = IntPtr.Zero;
try {
// TODO: check return value
ListLocalPrinters (ref list);
CupsPrinterList* printer = (PrinterList*) list;
ArrayList printers = new ArrayList();
// traverse linked list, adding each element to
// printers
while (printer != null) {
// copy data
PrinterList pl = new PrinterList ();
pl.Uri = printer->printerUri;
pl.CupsUri = printer->printerCupsUri;
pl.Name = printer->printerName;
pl.MakeModel = printer->printerMakeModel;
printers.add (pl);
printer = printer->nextElement;
}
PrinterList[] rval = new PrinterList[printers.Count];
printers.CopyTo (rval);
return rval;
}
finally {
if (list != IntPtr.Zero)
FreeLocalPrintersList (list);
}
return new PrinterList[0];
}
}
The above proves I can't write short pseudo-code. You also need to be
careful about exception-safety, as you don't want to leak unmanaged
memory.
For more information, see:
http://www.jprl.com/~jon/interop.html
- Jon