[Mono-list] Memory Allocation in unmanaged code
Jonathan Pryor
jonpryor@vt.edu
Thu, 09 Sep 2004 19:03:17 -0400
--=-zw8bdT7yO5vSzcvXW4kb
Content-Type: text/plain
Content-Transfer-Encoding: 7bit
On Wed, 2004-09-08 at 17:43, Jim Fehlig wrote:
> >>>Jonathan Pryor <jonpryor@vt.edu> 09/06/04 4:57 pm >>>
<snip/>
> I modified the wrapper implementation to use "unsafe" code but still
> unsuccessful in retrieving properties from the C# structure below.
> The unmanaged ListLocalPrinters is returning a list containing 1
> element yet the loop in PrintLibWrapper.ListLocalPrinters is executed
> twice, throwing a NullReferenceException when "printer =
> printer->nextElement"
> is executed on the second pass.
Your NullReferenceException is generated because the runtime marshaller
is never involved.
"What!," I hear you cry.
Well, we avoided it. We declared a DllImport function which takes an
IntPtr, so the marshaller just blitted the pointer across, as it
should. But then we go off and access the structure members /as if they
were managed objects/ (the last parts the kicker, as those managed
objects were never properly initialized).
In other words, this is a Bad Idea:
struct MyStruct {public string someStringInTheStructure;}
IntPtr p = GetResource();
MyStruct* s = (MyStruct*) p;
string n = s->someStringInTheStructure;
`someStringInTheStructure' won't be properly marshaled as a string, as
the runtime marshaler never *saw* the string, and thus didn't marshal
it. Oops.
Of course, I should have remembered that, but there are only some things
I can pull off without testing....
The solution? Marshal the structure, using
System.Runtime.InteropServices.Marshal.PtrToStructure():
IntPtr p = GetResource();
MyStruct s = (MyStruct) Marshal.PtrToStructure (p, typeof(MyStruct));
string n = s.someStringInTheStructure;
That explains the NullReferenceException.
Something else that confuses me is the behavior of `nextElement'; this
doesn't work properly:
IntPtr p = GetResource();
ListElement* le = (ListElement*) p;
while (le != null) le = le->nextElement;
If I know that GetResource() returns a linked list of 3 elements, the
while loop is only executed once. I have no idea why this is, and it
may be a mono bug. I'd have to test it against .NET to be sure, but
that won't happen for awhile (my .NET machine isn't setup; the joy of
moving...). I'm certainly open to anyone else testing this scenario
under .NET and telling me what happens... (hint, hint.)
The fix for the linked list? The same as the fix for the
NullReferenceException: invoke the runtime marshaller directly.
For your learning pleasure, I've attached two files, managed.cs and
native.c. Managed.cs walks a linked list, using the InternalPrinterList
structure, and native.c returns a linked list of PrinterList structures
containing 3 elements.
Usage:
$ gcc -shared -fpic native.c -o libnative.so
$ mcs -unsafe managed.cs
# don't forget to set your library path to find libnative.so
$ export LD_LIBRARY_PATH=`pwd`
$ mono managed.exe
Now I should just update my Wonder Guide
(http://www.jprl.com/~jon/interop.html), and pray that my cable modem
doesn't disappear again (where's fiber-to-the-home when I want it?), and
all will be good. I hope.
- Jon
--=-zw8bdT7yO5vSzcvXW4kb
Content-Disposition: attachment; filename=managed.cs
Content-Type: text/x-csharp; name=managed.cs; charset=UTF-8
Content-Transfer-Encoding: 7bit
// interop struct
using System;
using System.Collections;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
unsafe struct InternalPrinterList
{
[MarshalAs(UnmanagedType.ByValTStr/*LPStr*/, SizeConst=1024)]
public string printerUri;
[MarshalAs(UnmanagedType.ByValTStr/*LPStr*/, SizeConst=1024)]
public string printerCupsUri;
[MarshalAs(UnmanagedType.ByValTStr/*LPStr*/, SizeConst=1024)]
public string printerName;
[MarshalAs(UnmanagedType.ByValTStr/*LPStr*/,SizeConst=81)]
public string printerMakeModel;
public IntPtr nextElement;
}
public class PrinterList {
public string Uri, CupsUri, Name, MakeModel;
}
public unsafe class PrinterLib {
// Convert linked list constructed by unmanaged code to
// array of user-visible PrinterList objects.
public static unsafe PrinterList[] ListLocalPrinters()
{
IntPtr list = IntPtr.Zero;
try
{
ListLocalPrinters(ref list);
Console.WriteLine ("ListLocalPrinters returned: {0:x}", list);
ArrayList printers = new ArrayList();
IntPtr printer = list;
while (printer != IntPtr.Zero)
{
Console.WriteLine ("printer={0:x}", printer);
InternalPrinterList ipl = (InternalPrinterList) Marshal.PtrToStructure ((IntPtr) printer, typeof(InternalPrinterList));
PrinterList pl = new PrinterList();
pl.Uri = ipl.printerUri;
pl.CupsUri = ipl.printerCupsUri;
pl.Name = ipl.printerName;
pl.MakeModel = ipl.printerMakeModel;
// throws NullReferenceException
Console.WriteLine("PrinterInfo: {0}, {1}", pl.Name, pl.Uri);
printers.Add(pl);
// Something not right here as we will go through again
// even when list contains only 1 element.
Console.WriteLine ("M: next printer={0:x}", ipl.nextElement);
printer = ipl.nextElement;
}
return (PrinterList[]) printers.ToArray (typeof(PrinterList));
}
catch (Exception e)
{
return new PrinterList[0];
}
finally
{
if (list != IntPtr.Zero) {
Console.WriteLine ("Freeing address: {0:x}", list);
FreeLocalPrintersList(list);
}
}
}
[DllImport ("native")]
private static extern void ListLocalPrinters (ref IntPtr p);
[DllImport ("native")]
private static extern void FreeLocalPrintersList (IntPtr p);
public static unsafe void Main ()
{
Console.WriteLine ("Sizeof(InternalPrinterList)=" +
Marshal.SizeOf(typeof(InternalPrinterList)));
Console.WriteLine ("Listing local printers...");
PrinterList[] printers = ListLocalPrinters ();
foreach (PrinterList pl in printers) {
Console.WriteLine ("Printer\n\tUri={0}\n\tCupsUri={1}\n" +
"\tName={2}\n\tMakeMode={3}\n", pl.Uri, pl.CupsUri, pl.Name,
pl.MakeModel);
}
Console.WriteLine ("Done...");
}
}
--=-zw8bdT7yO5vSzcvXW4kb
Content-Disposition: attachment; filename=native.c
Content-Type: text/x-csrc; name=native.c; charset=UTF-8
Content-Transfer-Encoding: 7bit
#include <stdio.h>
struct PrinterList {
char printerUri[1024];
char printerCupsUri[1024];
char printerName[1024];
char printerMakeModel[81];
struct PrinterList* nextElement;
};
typedef struct PrinterList PrinterList;
struct PrinterList g_3 = {
"uri://printer-3",
"cups-uri://printer-3",
"name://printer-3",
"make-model://printer-3",
NULL
};
struct PrinterList g_2 = {
"uri://printer-2",
"cups-uri://printer-2",
"name://printer-2",
"make-model://printer-2",
&g_3
};
struct PrinterList g_1 = {
"uri://printer-1",
"cups-uri://printer-1",
"name://printer-1",
"make-model://printer-1",
&g_2
};
extern void
ListLocalPrinters (PrinterList **list)
{
printf ("PrinterLib: &g_1=%p\n", &g_1);
printf ("PrinterLib: &g_2=%p\n", &g_2);
printf ("PrinterLib: &g_3=%p\n", &g_3);
*list = &g_1;
printf ("PrinterLib: returning pointer %p\n", &g_1);
}
extern void
FreeLocalPrintersList (PrinterList *list)
{
printf ("PrinterLib: freeing pointer %p\n", list);
}
int main ()
{
printf ("sizeof(PrinterList)=%i\n", sizeof(PrinterList));
PrinterList* pl;
ListLocalPrinters (&pl);
while (pl) {
printf ("processing %s\n", pl->printerName);
pl = pl->nextElement;
}
}
--=-zw8bdT7yO5vSzcvXW4kb--