Search code examples
c#interoppinvoke

How to marshall an array of structs?


using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    class Program
    {
        const int SystemPowerInformation = 11;
        const uint STATUS_SUCCESS = 0;

        [StructLayout(LayoutKind.Sequential)]
        struct PROCESSOR_POWER_INFORMATION
        {
            public uint  Number;
            public uint MaxMhz;
            public uint CurrentMhz;
            public uint MhzLimit;
            public uint MaxIdleState;
            public uint CurrentIdleState;
        }

        [DllImport("powrprof.dll")]
        static extern uint CallNtPowerInformation(
            int InformationLevel,
            IntPtr lpInputBuffer,
            int nInputBufferSize,
            [MarshalAs(UnmanagedType.LPArray)]
            out byte[]  lpOutputBuffer,
            int nOutputBufferSize
        );

        static void Main(string[] args)
        {


            byte[] buffer = new byte[4 * Marshal.SizeOf(typeof(PROCESSOR_POWER_INFORMATION))];
            uint retval = CallNtPowerInformation(
                SystemPowerInformation,
                IntPtr.Zero,
                0,
                out  buffer,
                4 * Marshal.SizeOf(typeof(PROCESSOR_POWER_INFORMATION))
            );

            if (retval == STATUS_SUCCESS)
                Console.WriteLine(buffer);
       }
    }
}

I am trying to get some data out of CallNtPowerInformation. I tried to create a struct and call CallNtPowerInformation and marshal the data from it, but that didn't work. So I am trying to see if I can get the data into a byte array, but I get the following:

Object reference not set to an instance of an object.

I believe I am allocating the memory to the buffer.

I am not sure why. Any pointers would be helpful.


Solution

  • Your constant named SystemPowerInformation with value 11 has the wrong name. It should be named ProcessorInformation.

    You should declare the p/invoke like this:

    [DllImport("powrprof.dll")]
    static extern uint CallNtPowerInformation(
        int InformationLevel,
        IntPtr lpInputBuffer,
        int nInputBufferSize,
        [Out] PROCESSOR_POWER_INFORMATION[] processorPowerInformation,
        int nOutputBufferSize
    );
    

    In order to call the function you need to allocate a suitably sized array of PROCESSOR_POWER_INFORMATION structs. Like this:

    PROCESSOR_POWER_INFORMATION[] powerInfo = 
        new PROCESSOR_POWER_INFORMATION[procCount];
    

    The documentation for CallNtPowerInformation tells you to use GetSystemInfo to work out how many processors you have. You can use Environment.ProcessorCount.

    Then you call the function like this:

    uint retval = CallNtPowerInformation(
        ProcessorInformation,
        IntPtr.Zero,
        0,
        powerInfo,
        powerInfo.Length*Marshal.SizeOf(typeof(PROCESSOR_POWER_INFORMATION))
    );
    

    Here's a complete program:

    using System;
    using System.Runtime.InteropServices;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            const int ProcessorInformation = 11;
            const uint STATUS_SUCCESS = 0;
    
            [StructLayout(LayoutKind.Sequential)]
            struct PROCESSOR_POWER_INFORMATION
            {
                public uint Number;
                public uint MaxMhz;
                public uint CurrentMhz;
                public uint MhzLimit;
                public uint MaxIdleState;
                public uint CurrentIdleState;
            }
    
            [DllImport("powrprof.dll")]
            static extern uint CallNtPowerInformation(
                int InformationLevel,
                IntPtr lpInputBuffer,
                int nInputBufferSize,
                [Out] PROCESSOR_POWER_INFORMATION[] lpOutputBuffer,
                int nOutputBufferSize
            );
    
            static void Main(string[] args)
            {
                int procCount = Environment.ProcessorCount;
                PROCESSOR_POWER_INFORMATION[] procInfo =
                    new PROCESSOR_POWER_INFORMATION[procCount]; 
                uint retval = CallNtPowerInformation(
                    ProcessorInformation,
                    IntPtr.Zero,
                    0,
                    procInfo,
                    procInfo.Length * Marshal.SizeOf(typeof(PROCESSOR_POWER_INFORMATION))
                );
                if (retval == STATUS_SUCCESS)
                {
                    foreach (var item in procInfo)
                    {
                        Console.WriteLine(item.CurrentMhz);
                    }
                }
            }
        }
    }