[mapguide-trac] #1047: Error in marshalling of unmanaged strings in .NET wrappers in RFC 68 implementation

MapGuide Open Source trac_mapguide at osgeo.org
Fri Jul 24 14:30:00 EDT 2009


#1047: Error in marshalling of unmanaged strings in .NET wrappers in RFC 68
implementation
-------------------------+--------------------------------------------------
   Reporter:  anilpatel  |       Owner:       
       Type:  defect     |      Status:  new  
   Priority:  medium     |   Milestone:       
  Component:  General    |     Version:  2.0.2
   Severity:  major      |    Keywords:       
External_id:             |  
-------------------------+--------------------------------------------------
 There appears to be a bug in the marshalling of unmanaged ANSI strings
 across the unmanaged/managed boundary in the .NET wrappers due to changes
 made for RFC 68.  This has caused heap corruption issues when the .NET
 wrappers are called on a product that uses the MapGuide OS code.

 MapGuide RFC 68 can be found here:

 http://trac.osgeo.org/mapguide/wiki/MapGuideRfc68

 The changelist to implement RFC 68 can be found here:

 http://trac.osgeo.org/mapguide/changeset/4006

 The submission did not marshall ANSI strings correctly across the
 unmanaged/managed boundary.  This resulted in heap corruption which was
 most
 readily evident in heap corruption assertions on 64-bit platforms, but
 should
 also be present in 32-bit platforms.  The heap corruptions occurred at
 startup
 of the product using MapGuide OS code via the .NET API and would result in
 the product failing to successfully start.

 In the submission, the following methods were added to MgObject:

 virtual char* GetMultiByteClassName();
 virtual char* GetNameSpace();

 These methods returned a string with the class name or name space.  On the
 unmanaged side, two new static global methods were added to the SWIG
 output in
 getclassid.code:

 DllExport char* getClassName(void* ptrObj)
 {
   return ((MgObject*)ptrObj)->GetMultiByteClassName();
 }

 DllExport char* getNameSpace(void* ptrObj)
 {
   return ((MgObject*)ptrObj)->GetNameSpace();
 }

 These methods are exported from the unmanaged DLL and called from the
 managed
 wrapper DLL (also generated by SWIG) through the use of the DLLImport
 mechanism
 like this:

   [DllImport("unmanagedDllName", EntryPoint="getClassName")]
   public static extern string getClassName(IntPtr objectRef);

   [DllImport("unManagedDllName", EntryPoint="getNameSpace")]
   public static extern string getNameSpace(IntPtr objectRef);

 The problem occurs because the DLLImport mechanism doesn't marshall the
 char *
 return value from getClassName/getNameSpace to the managed world.
 Marshalling
 of non-intrinsic return values like ANSI strings must be hanlded
 explicitly.
 SWIG does have a mechanism to do this for classes that it generates
 wrappers for
 itself.  But the static global getClassName/getNameSpace methods are not
 part of
 the classes being wrapped and are instead being emitted literally in the
 SWIG
 changes.

 To fix this issue, marshalling of the ANSI string via a shared memory
 buffer
 accessible via both managed and unmanaged code was implemented.  The
 SWIGStringHelper (on the managed side) and SWIG_csharp_string_callback (on
 the
 unmanaged side) provided the prototype for the changes.

 The new implementation of the static global methods on the unmanaged side
 call a
 managed code callback function to allocate shared memory.  The ANSI string
 is
 copied into this memory and a pointer to the shared block is passed back
 via
 void/IntPtr to the managed wrapper.  The code now looks like this:

 DllExport void* getClassName(void* ptrObj)
 {
     // Call virtual function
     void* jresult = 0;
     char* result = ((MgObject*)ptrObj)->GetMultiByteClassName();
     // Allocate common managed/unmanaged memory to hold string
     int buflength = (int)(strlen(result)+1)*sizeof(char *);
     jresult = SWIG_csharp_string_callback(buflength);
     // Copy string into common memory
     strncpy((char*)jresult, result, buflength);
     // Return ptr to common memory
     return jresult;
 }

 (similar changes to getNameSpace)

 The return type should also be changed on the managed side DLLImports from
 string to
 IntPtr:

   [DllImport("unManagedDllName", EntryPoint="getClassName")]
   public static extern IntPtr getClassName(IntPtr objectRef);

   [DllImport("unManagedDllName", EntryPoint="getNameSpace")]
   public static extern IntPtr getNameSpace(IntPtr objectRef);

 Since the getClassName and getNameSpace functions now return IntPtr, the
 createObject overload that took these two values as parameters needed to
 be
 modified to match the type.  In addition, code was added to createObject
 to
 create a System.String from the ANSI string in the shared memory buffer
 and then
 release the shared memory buffer:

  static public object createObject(int id, IntPtr nameSpaceNamePtr, IntPtr
 classNamePtr, IntPtr cPtr, bool ownMemory)
   {
       // Marshall strings in nameSpaceNamePtr and classNamePtr from
 unmanaged
 char * to managed strings
       String nameSpaceName =
 System.Runtime.InteropServices.Marshal.PtrToStringAnsi(nameSpaceNamePtr);
       String className     =
 System.Runtime.InteropServices.Marshal.PtrToStringAnsi(classNamePtr);
 System.Runtime.InteropServices.Marshal.FreeCoTaskMem(nameSpaceNamePtr);
       System.Runtime.InteropServices.Marshal.FreeCoTaskMem(classNamePtr);

       ...

 A patch file with the proposed fixes is attached to this ticket.

-- 
Ticket URL: <http://trac.osgeo.org/mapguide/ticket/1047>
MapGuide Open Source <http://mapguide.osgeo.org/>
MapGuide Open Source Internals


More information about the mapguide-trac mailing list