Introduction

There are a lot of parts of the Windows API that are difficult to interface with through the .NET framework due to the liberal use of pointer references. This article provides an example of interfacing to a Windows API call that utilizes a pointer to a large buffer (3 * 256 * 2 bytes large). We also look at how StackAlloc works and using the unsafe context in our code.

Background

I work in the offshore oil field and marine industry, developing user interfaces to the hardware my company develops. Working offshore, a major concern to those on the bridge of a vessel is the preservation of their night vision. This requires dimming screens down as far as they can go, but many industrial panel mount monitors don't provide brightness controls on the front panel. Using Windows API calls, we can dim the screen down about as far as the hardware dimmer will go.

Using the Code

The Microsoft Windows API provides a call titled SetDeviceGammaRamp that takes two arguments: an HDC (hardware device context), and a pointer to an array of gamma values to load into the video card. Not all video card architectures support the SetDeviceGammaRamp, so your mileage with this API call may vary.

Let's take a look at the prototype declaration for the Windows API call:

As you can see, there are a number of parts here. First is the DllImport; this is contained in the System.Runtime.InteropServices namespace, and provides the hook into the GDI32.dll where the SetDeviceGammaRamp function is contained. The function has two arguments, hdc and ramp. The first argument, hdc, is the pointer to the "hardware device context" that we are setting the gamma ramp on. The second argument ramp is the array of new gamma values, which are stored as a short array with dimensions [3][256].

Let's take a look at the entire class code so we can see everything in context:

The first function InitializeClass is used because we have a static class and we need to store some variables that only need to be found once. Because the class is static, after the InitializeClass function is called, the variables will be initialized for any caller in the same application context. The line of interest is:

hdc = Graphics.FromHwnd(IntPtr.Zero).GetHdc().ToInt32();

This is where we find our hardware device context. We use the Graphics class to create a Graphics object from an Hwnd of null (IntPtr.Zero). This returns a device context that references the entire graphics display. We then use the GetHdc function to get an IntPtr to the HDC, and then convert that to an Int32 value that we can pass to the Windows API function.

The function with the meat of this article is SetBrightness. Let's look at the function declaration:

publicstaticunsafebool SetBrightness(short brightness)

Here, the function is declared as public, static, unsafe, and returns a type of bool. The unsafe keyword means that the entire function is to be considered unsafe; that is, type checking is turned off and pointer use is allowed.

Next, we call InitializeClass to make sure that the class has been initialized. If the initialization is already done, the function call returns immediately. We then do some checking on the input data (always, always assume that the input data is bad, and clean it as necessary).

short* gArray = stackalloc short[3 * 256];

This line uses stackalloc to allocate a section of memory on the stack where we can do our work. We can't declare multiple dimension arrays with stackalloc, so we just set aside as much space as we need for the entire element count. The actual size in bytes of the array is 3 * 256 * sizeof(short).

After that, we need to declare an indexing variable that will traverse the allocated array:

short* idx = gArray;

This is initialized to point at the first element in gArray. We will use this variable to traverse down the array and fill it with the values needed.

Here, we loop through all the elements in the array and fill it with the desired Gamma values. Idx++ at the end of the loop automatically increases the pointer sizeof(short) so that we go to the next element. This is the magic of pointer arithmetic.

All that is left is to call the API function with our HDC and Gamma array:

which is supposed to return true or false depending on if the call succeeds, but I've found on my machine that the function call always returns false, regardless of the function working or not.

And there we have it. It's best to compile this into a separate DLL that you've marked to compile as unsafe (either add the /unsafe escape to the compiler line, or go to Properties>Build, and check the "Allow Unsafe Code" box).

Points of Interest

Interfacing back-and-forth with .NET code and pointers isn't the easiest thing in the world. Many of us got away from C/C++ because of pointer arithmetic and "DLL Hell", but occasionally, we have to get sucked back in. This just goes to show you that we can write a lot of code to interface to unmanaged code without having to write complicated wrapper functions in separate languages.

Share

About the Author

I studied Software Engineering at Milwaukee School of Engineering for 2 years before switching to Management of Information Systems for a more business oriented approach. I've been developing software since the age of 14, and have waded through languages such as QBasic, TrueBasic, C, C++, Java, VB6, VB.NET, C#, etc. I've been developing professionally since 2002 in .NET.

I don't think there is any default universal value. You could possibly try getting the information from the GetDeviceGammaRamp[^] function and reverse-engineering it from the for-loop in this article to get the gamma ramp for the particular card you are working on.

The author's suggestion to change contrast didn't work for me, since I want to also reduce power consumption. Here is my actual solution (1), and another I didn't try (2):

(1) Set up a power plan in Control Panel with the display brightness you want. Find this plan (scheme) by parsing the output of:
...new ProcessStartInfo("powercfg.exe", "-list");
Then programmatically switch to it using:
...new ProcessStartInfo("powercfg.exe", "-s " + guid);

(2) Like (1), but write a C++ DLL to use the API functions like EnumPwrSchemes.

Note that you can create a new scheme from code, based on the current one.

to test this I set up a hScrollBar with min/max 0-255 and played with it.
my laptop has a brightness function-control on the keyboard and your code seems to range +/- around the hardware setting but does not reach the limits by itself and neither does my keyboard. the two combined minKey + minCode is much less-bright than just minKey or minCode. its like they both use the same factor of average brightness.

Well like I said, I work in the offshore industry. I was looking for a way to programmatically dim the screen, and all the examples I found were unmanaged C. Looking through the API documentation and the samples I found, along with a bunch of trial-and-error, I arrived at this solution.

Gamma ramp does not effect the brightness of the monitor. You can verify this yourself, go to your display settings and change the gamma ramp. You will see that it just changes the image brightness (colors), does not effect the brightness of the monitor. Also, changing gamma distorts images.

What you need to do is to change the brightness of the monitor through DDC/CI or other means.