Search code examples
c#pinvokestructlayout

Modify struct layout from p/invoke


I'm looking for best practice guidance around changing the struct/class layout of objects returned/passed into a p/invoke function. I've searched for an answer to this but maybe I'm just too tired and I'm not searching effectively.

The simplest example I can come up with (the real one is a bit too complex for here) is with something like GetWindowRect.

If I wanted to add a few extra properties to the RECT struct, should I just add it to the definition for the struct itself or should I switch over to subclassing to add the extra properties?

Is there a best practice from Microsoft or another reliable source around the following methods? Are both of these against best practice?

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(HandleRef hWnd, out RECT lpRect);

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int Left;        // x position of upper-left corner
    public int Top;         // y position of upper-left corner
    public int Right;       // x position of lower-right corner
    public int Bottom;      // y position of lower-right corner

    public string Extra;    // ADDED
}

Versus

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(HandleRef hWnd, out RECT lpRect);

[StructLayout(LayoutKind.Sequential)]
public class RECT
{
    public int Left;        // x position of upper-left corner
    public int Top;         // y position of upper-left corner
    public int Right;       // x position of lower-right corner
    public int Bottom;      // y position of lower-right corner
}

public class RectEx : RECT
{
    public string Extra;    // Added

    public RectEx(RECT r)
    {
        Left = r.Left;
        Top = r.Top;
        Right = r.Right;
        Bottom = r.Bottom;
        Extra = "test";
    }
}

Solution

  • Here is another option: this allows you to maintain native functionality and provides some safety over the objects you are using.

    // used internally in native method
    [StructLayout(LayoutKind.Sequential)]
    internal struct RECT
    {
        public int Left;        // x position of upper-left corner
        public int Top;         // y position of upper-left corner
        public int Right;       // x position of lower-right corner
        public int Bottom;      // y position of lower-right corner
    }
    
    
    // public accessible struct with extra fields 
    public struct RectEx
    {
        public int Left;        // x position of upper-left corner
        public int Top;         // y position of upper-left corner
        public int Right;       // x position of lower-right corner
        public int Bottom;      // y position of lower-right corner
    
        public dynamic Extra = "Extra";
    }
    
    
    public static class UnsafeNativeMethods
    {
        //used internally to populate RECT struct
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetWindowRect(HandleRef hWnd, out RECT lpRect);
    
        //public safe method with exception handling and returns a RectEx
        public static RectEx GetWindowRectangle(HandleRef hWnd)
        {
            RECT r = new RECT();
            RectEx result = new RectEx();
    
            try
            {
                GetWindowRect(hWnd, r);
                result.Left = r.Left;
                result.Top = r.Top;
                result.Right = r.Right;
                result.Bottom = r.Bottom;
                // assign extra fields
            }
            catch(Exception ex)
            {
                // handle ex
            }
    
        return result;
        }
    }