Wrapping an IntPtr in a Struct for safer Interop

Posted on

Problem

Consider the following C#/C interop scenario:

public static extern IntPtr lua_newstate();
public static extern void lua_close(IntPtr state);

IntPtr luaState = lua_newstate();
// use lua ...
lua_close(luaState)

Obviously this works well enough, however there’s a fringe possibility that an IntPtr not pointing to a lua state could be passed to lua_close, since it accepts any IntPtr.

I realized that it’s possible to use the behavior of struct marshaling to make this interop a bit more type-safe

[StructLayout(LayoutKind.Sequential)]
public struct LuaState
{
    public readonly IntPtr Pointer;
}

public static extern LuaState lua_newstate();
public static extern void lua_close(LuaState state);

LuaState luaState = lua_newstate();
// use lua ...
lua_close(luaState)

Since LuaState contains only an IntPtr, the marshaler happily converts the native pointer returned by lua_newstate to an instance of LuaState, and will also quite happily convert a LuaState struct to a native pointer when passed back to a native function.

This essentially creates a typed pointer. There is a cost associated with passing LuaState though the Marshaling system instead of passing IntPtr directly to the native side, but in testing the cost appears to be negligible.

Questions: Does using LuaState to create a typed wrapper for IntPtr in this way raise any red flags? Has anyone done something similar and run into issues?

Solution

I think, I would experiment with a safe or smart pointer approach in a way like this:

public sealed class LuaHandle : IDisposable
{
  private static extern IntPtr lua_newstate();
  private static extern void lua_close(IntPtr state);

  private IntPtr m_handle = IntPtr.Zero;

  public LuaHandle()
  {
    m_handle = lua_newstate();
  }

  public IntPtr Handle => m_handle;


  public void Dispose()
  {
    if (m_handle != IntPtr.Zero)
    {
      lua_close(m_handle);
    }

    m_handle = IntPtr.Zero;
  }
}

Usage:

  using (LuaHandle luaHandle = new LuaHandle())
  {
    // do something with luaHandle.Handle
  }

In this way you – as a client of LuaHandle – don’t have to deal with the extern api’s at all and no invalid IntPtr can be past as argument to lua_close()

Leave a Reply

Your email address will not be published. Required fields are marked *