Search code examples
c#pinvoke

How can I p/invoke a function with a parameter that can accept many different types?


Have a look:

[DllImport("User32.dll")]
public static extern StatusCode DisplayConfigGetDeviceInfo(
    ref IDisplayConfigInfo a
);

and now my structure that inherits from IDisplayConfigInfo:

var displayConfigTargetDeviceName = new DisplayConfigTargetDeviceName
{
    header = new DisplayConfigDeviceInfoHeader
    {
        adapterId = targetModeInfo.adapterId,
        id = targetModeInfo.id,
        size = Marshal.SizeOf(typeof(DisplayConfigTargetDeviceName)),
        type = DisplayConfigDeviceInfoType.GetTargetName,
    }
};
var configTargetDeviceName = (IDisplayConfigInfo) displayConfigTargetDeviceName;
var retval = CCDWrapper.DisplayConfigGetDeviceInfo(ref configTargetDeviceName);

Now there is a problem. retval will be returned "InvalidParameter" value. Why is that? It is because I try to use interface, but I don't understand why.

When I explictly say that DisplayConfigGetDeviceInfo() accepts DisplayConfigTargetDeviceName instead of interface, and pass displayconfigTargetDeviceName to it directly, then it works.

The thing is, I don't want to create 8-9 overloads, for each structure. Note that C++ version has only one overload. It will figure out the rest from pointer I passed.

// structs:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DisplayConfigTargetDeviceName : IDisplayConfigInfo
{
    public DisplayConfigDeviceInfoHeader header;
    public DisplayConfigTargetDeviceNameFlags flags;
    public DisplayConfigVideoOutputTechnology outputTechnology;
    public ushort edidManufactureId;
    public ushort edidProductCodeId;
    public uint connectorInstance;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] 
    public string monitorFriendlyDeviceName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] 
    public string monitorDevicePath;
}

[StructLayout(LayoutKind.Sequential)]
public struct DisplayConfigDeviceInfoHeader
{
    public DisplayConfigDeviceInfoType type;
    public int size;
    public LUID adapterId;
    public uint id;
}

and IDisplayConfig interface is empty.


Solution

  • Thanks to David, I was able to come up with clever solution(imo), it seems to work nicely.

    [DllImport("User32.dll")]
    private static extern StatusCode DisplayConfigSetDeviceInfo(IntPtr requestPacket);
    public static StatusCode DisplayConfigSetDeviceInfo<T>(ref T displayConfig) 
       where T : IDisplayConfigInfo
    {
        return WrapStructureAndCall(ref displayConfig, DisplayConfigSetDeviceInfo);
    }
    
    
    [DllImport("User32.dll")]
    private static extern StatusCode DisplayConfigGetDeviceInfo(IntPtr targetDeviceName);
    public static StatusCode DisplayConfigGetDeviceInfo<T>(ref T displayConfig) 
      where T : IDisplayConfigInfo
    {
        return WrapStructureAndCall(ref displayConfig, DisplayConfigGetDeviceInfo);
    }
    
    public static StatusCode WrapStructureAndCall<T>(ref T displayConfig,
        Func<IntPtr, StatusCode> func) where T : IDisplayConfigInfo
    {
        var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(displayConfig));
        Marshal.StructureToPtr(displayConfig, ptr, false);
    
        var retval = func(ptr);
    
        displayConfig = (T)Marshal.PtrToStructure(ptr, displayConfig.GetType());
    
        Marshal.FreeHGlobal(ptr);
        return retval;
    }