Click here to Skip to main content
Click here to Skip to main content
Articles » Languages » C# » PInvoke » Revisions
 

PInvoke pointer safety: Replacing IntPtr with unsafe struct pointers

, 23 Aug 2012
Rate this:
Please Sign up or sign in to vote.
Replacing IntPtr with unsafe struct pointers.
This is an old version of the currently published article.

Introduction 

When using .NET Platform Invoke tools too call native C functions in DLLs, it's common for paramaters to contain pointers to structures, classes, or strings. Sometimes it's practical to marshall the data into a managed representation, such as when marshalling a C char* string into a .NET String class.

However, other times the complexity of the native data can be left entirely in the native code. We call these pointers opaque pointers, because from managed code we will never understand the data they point to. Instead our is our responsibility to receive, store, and produce the opaque native pointer when necessary in the API.  

The .NET CTS provides a value type called IntPtr, which can be used to store opaque native pointers. However, it has a serious drawback, in that with respect to the type-system, all IntPtrs are the same type. If your native library has several types of pointers passed across the PInvoke boundary, the compiler can't help you make sure you provided the right IntPtr in the right situation. Providing the wrong pointer to a native C-DLL entry point at best will have eronous results, and at worst will crash your program. 

A safer alternative to IntPtr is the unsafe struct *. Just like IntPtr, unsafe-struct-pointers are a value type used to store opaque pointer data that won't be accessed from managed code.  However, because the structs themselves have types, the compiler can typecheck and assure the proper pointer type is supplied to the proper native entry point.  

How does it work 

We will use the .NET wrapper around the Clearsilver HTML templating library as an example. Two functions in the C Clearsilver DLL are: 

NEOERR *hdf_inif(HDF **hdf);
NEOERR *hdf_set_value(HDF *hdf, const char *name, const char *value); 

These functions contain two structures which we're going to treat as opaque data. Both NEOERR and the HDF types are pointers to native structures. When using this library from C, those structures are opaque data, manipulated only through functions. We wish our .NET code to operate in the same way.

One could use PInvoke to provide access to these functions using IntPtr. Our C# .NET imports might look like: 

[DllImport("libneo", EntryPoint="hdf_init")]
static extern unsafe IntPtr hdf_init(IntPtr* hdf); 

[DllImport("libneo")] 
static unsafe extern IntPtr hdf_set_value(IntPtr hdf, 
   [MarshalAs(UnmanagedType.LPStr)] string name,
   [MarshalAs(UnmanagedType.LPStr)] string value); 

As you can see above, both the NEOERR return value and the HDF paramaters are provided using the type IntPtr. As a result, the compiler can't tell them apart. Later, when using these paramaters, it is possible to provide one in the place of another without a compiler error. For example, the following code will compile, even though it is invalid.

IntPtr hdf;  
hdf_set_value(hdf_init(&hdf), "foo", "bar"); 

We would like to handle these pointers as separate types. The way to do this is via unsafe struct pointers. Our C# wrapper import code instead becomes: 

unsafe struct HDF {};
unsafe struct NEOERR {};

[DllImport("libneo", EntryPoint="hdf_init")]
static extern unsafe NEOERR* hdf_init(HDF** hdf); 

[DllImport("libneo")] 
static unsafe extern NEOERR* hdf_set_value(HDF* hdf, 
   [MarshalAs(UnmanagedType.LPStr)] string name, 
   [MarshalAs(UnmanagedType.LPStr)] string value);  

The compiler now knows the specific types of the HDF and NEOERR pointers and can tell them apart. A C# managed class can then control access to these unsafe pointers, while still receiving compiler typechecking that the proper pointers are provided at the proper entry points. Here is a small excerpt from the Clearsilver HDF wrapper. 

  // opaque types
  public unsafe struct HDF {}; 
  public unsafe struct NEOERR {};

  public unsafe class Hdf {
   [DllImport("libneo", EntryPoint="hdf_init")]
  private static extern unsafe NEOERR* hdf_init(HDF **foo);

  // NEOERR* hdf_set_value (HDF *hdf, char *name, char *value)
  [DllImport("libneo")]

  private static unsafe extern NEOERR* hdf_set_value(HDF *hdf,
       [MarshalAs(UnmanagedType.LPStr)] string name,
       [MarshalAs(UnmanagedType.LPStr)] string value);

  // instance data
  public HDF *hdf_root;

  // constructor 
  public Hdf() {
      fixed (HDF **hdf_ptr = &hdf_root) {
        hdf_init(hdf_ptr);
      }
      // Console.WriteLine((int)hdf_root);
  }

  // managed accessor method 
  public void setValue(string name,string value) {
      NEOERR* err = hdf_set_value(hdf_root,name,value);
  } 
  // ..... more code snipped... 
}  

To see more examples of how to use unsafe structs for safer PInvoke entrypoints, check out the full Clearsilver C# wrapper, available from clearsilver.net.  

Points of Interest 

The above unsafe struct pointer usage is valid according to the Common Type System spec, and works in Microsoft.NET. However, several versions of the Mono .NET runtime marshalling code did not properly handle unsafe struct pointers in PInvoke entry points.  Therefore, you'll need to be using the very latest Mono 2.12 (or very very old versions of Mono) for this to work.  

History 

  • 2012 August - Initial release 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

David Jeske

United States United States
David Jeske is an Entrepreneur and Computer Programmer, currently living in San Francisco, California.
 
He earned his B.S. Computer Engineering at University of Illnois at Champaign/Urbana (UIUC), and has worked at several Silicon Valley companies, including Google, Yahoo, eGroups.com, 3dfx, and Akklaim Entertainment. He has managed and architected extremely high-traffic websites, including Yahoo Groups and orkut.com, and has experience in a broad spectrum of technology areas including scalability, databases, drivers, system software, and 3d graphics.
 
You can contact him at davidj -a-t- gmail (dot) com for personal messages about this article.

Comments and Discussions


Discussions posted for the Published version of this article. Posting a message here will take you to the publicly available article in order to continue your conversation in public.
 
GeneralMy vote of 5 PinmemberNicolas Dorier24-Aug-12 1:28 
GeneralMy vote of 3 PinmemberJonathan C Dickinson23-Aug-12 22:24 
GeneralSafeHandle references... [modified] PinmemberDavid Jeske24-Aug-12 4:15 
GeneralRe: My vote of 3 [modified] PinmemberJonathan C Dickinson24-Aug-12 8:55 
Generalunsafe-struct vs SafeHandle PinmemberDavid Jeske24-Aug-12 9:59 
SuggestionRe: unsafe-struct vs SafeHandle PinprofessionalShawn-USA1-May-13 18:55 
GeneralMy vote of 5 PinmemberTom Nielsen23-Aug-12 19:54 
GeneralMy vote of 5 PinmemberChristian Amado23-Aug-12 11:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 23 Aug 2012
Article Copyright 2012 by David Jeske
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid