在.NET框架中,调用Windows API可能因为指针的广泛使用而变得复杂。本文提供了一个示例,展示了如何通过.NET框架调用Windows API来使用指向大缓冲区的指针(3 * 256 * 2字节大小)。同时,也会探讨StackAlloc的工作原理以及在代码中使用不安全上下文的方法。
在海上油田和海洋工业领域工作,负责开发公司硬件的用户界面。在海上工作时,船桥上的工作人员非常关心如何保护他们的夜视能力。这需要尽可能地调暗屏幕,但许多工业面板安装的显示器并没有提供前面板的亮度控制。通过Windows API调用,可以将屏幕调暗到硬件调光器所能达到的程度。
MicrosoftWindows API提供了一个名为SetDeviceGammaRamp的调用,它接受两个参数:一个HDC(硬件设备上下文)和一个指向要加载到显卡中的伽马值数组的指针。并非所有的显卡架构都支持SetDeviceGammaRamp,因此这个API调用的效果可能会有所不同。
以下是Windows API调用的原型声明:
[DllImport("gdi32.dll")]
private unsafe static extern bool SetDeviceGammaRamp(Int32 hdc, void* ramp);
这里有几个部分。首先是DllImport,它包含在System.Runtime.InteropServices命名空间中,并提供了一个钩子到GDI32.dll,其中包含SetDeviceGammaRamp函数。该函数有两个参数,hdc和ramp。第一个参数hdc是正在设置伽马坡度的“硬件设备上下文”的指针。第二个参数ramp是新的伽马值数组,存储为一个维度为[3][256]的short数组。
让看看整个类代码,以便在上下文中看到所有内容:
[DllImport("gdi32.dll")]
private unsafe static extern bool SetDeviceGammaRamp(Int32 hdc, void* ramp);
private static bool initialized = false;
private static Int32 hdc;
private static void InitializeClass()
{
if (initialized)
return;
// 获取屏幕的硬件设备上下文,可以通过获取null的图形对象(IntPtr.Zero)
// 然后获取HDC并将其转换为Int32。
hdc = Graphics.FromHwnd(IntPtr.Zero).GetHdc().ToInt32();
initialized = true;
}
public static unsafe bool SetBrightness(short brightness)
{
InitializeClass();
if (brightness > 255)
brightness = 255;
if (brightness < 0)
brightness = 0;
short* gArray = stackalloc short[3 * 256];
short* idx = gArray;
for (int j = 0; j < 3; j++)
{
for (int i = 0; i < 256; i++)
{
int arrayVal = i * (brightness + 128);
if (arrayVal > 65535)
arrayVal = 65535;
*idx = (short)arrayVal;
idx++;
}
}
// 出于某种原因,这总是返回false?
bool retVal = SetDeviceGammaRamp(hdc, gArray);
// 通过stackalloc分配的内存将由CLR自动释放。
return retVal;
}
第一个函数InitializeClass用于有一个静态类,并且需要存储一些只需要找到一次的变量。因为类是静态的,所以在InitializeClass函数被调用后,变量将为同一应用程序上下文中的任何调用者初始化。
让看看函数声明:
public static unsafe bool SetBrightness(short brightness)
在这里,函数被声明为public、static、unsafe,并返回一个bool类型。unsafe关键字意味着整个函数被认为是不安全的;也就是说,类型检查被关闭,允许使用指针。
接下来,调用InitializeClass以确保类已经被初始化。如果初始化已经完成,函数调用会立即返回。然后对输入数据进行一些检查(总是,总是假设输入数据是坏的,并在必要时进行清理)。
使用stackalloc分配内存的部分:
short* gArray = stackalloc short[3 * 256];
这行使用stackalloc在栈上分配一个内存区域,可以在这里进行工作。不能使用stackalloc声明多维数组,所以只预留了整个元素计数所需的空间。数组的实际大小(以字节为单位)是3 * 256 * sizeof(short)。
之后,需要声明一个索引变量,它将遍历分配的数组:
short* idx = gArray;
这个变量被初始化为指向gArray的第一个元素。将使用这个变量遍历数组并用所需的值填充它。
填充数组的循环:
for (int j = 0; j < 3; j++)
{
for (int i = 0; i < 256; i++)
{
int arrayVal = i * (brightness + 128);
if (arrayVal > 65535)
arrayVal = 65535;
*idx = (short)arrayVal;
idx++;
}
}
在这里,遍历数组中的所有元素并用所需的伽马值填充它。Idx++在循环结束时自动增加指针sizeof(short),这样就可以去到下一个元素。这是指针算术的魔力。
最后,用HDC和伽马数组调用API函数:
bool retVal = SetDeviceGammaRamp(hdc, gArray)
这个调用应该返回true或false,取决于调用是否成功,但发现在机器上,无论函数是否起作用,函数调用总是返回false。