[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