创建整个屏幕的图形
本文关键字:图形 屏幕 创建 | 更新日期: 2023-09-27 18:29:55
我开始使用一个类来增加和减少我所有屏幕的gamma,然后我启动程序并增加或减少gamma,它工作得很好,但过了一段时间(20秒左右)它就不再工作了,我找到了问题,似乎是Graphics.FromHwnd(IntPtr.Zero).GetHdc().ToInt32();
,我需要刷新它,然后它又工作了。在示例代码中,这在初始化期间只做过一次,但为了使其发挥作用,我在SetBrightness()
方法中粘贴了这一行,所以每次都会刷新它。这样做可以吗?还是我会遇到问题?
这是代码:
public static class Brightness
{
[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;
//Get the hardware device context of the screen, we can do
//this by getting the graphics object of null (IntPtr.Zero)
//then getting the HDC and converting that to an Int32.
hdc = Graphics.FromHwnd(IntPtr.Zero).GetHdc().ToInt32();
initialized = true;
}
public static unsafe bool SetBrightness(short brightness)
{
InitializeClass();
hdc = Graphics.FromHwnd(IntPtr.Zero).GetHdc().ToInt32();
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++;
}
}
//For some reason, this always returns false?
bool retVal = SetDeviceGammaRamp(hdc, gArray);
//Memory allocated through stackalloc is automatically free'd
//by the CLR.
return retVal;
}
}
这就是它的名称:
short gammaValue = 128;
void gammaUp_OnButtonDown(object sender, EventArgs e)
{
if (gammaValue < 255)
{
gammaValue += 10;
if (gammaValue > 255)
gammaValue = 255;
Brightness.SetBrightness(gammaValue);
}
}
void gammaDown_OnButtonDown(object sender, EventArgs e)
{
if (gammaValue > 0)
{
gammaValue -= 10;
if (gammaValue < 0)
gammaValue = 0;
Brightness.SetBrightness(gammaValue);
}
}
如果您想在代码失败时查看错误代码,您可以使用:
[DllImport("kernel32.dll")]
public static extern uint GetLastError();
bool retVal = SetDeviceGammaRamp(hdc, gArray);
if (retVal == false)
{
System.Console.WriteLine(GetLastError());
}
error code is 87: ERROR_INVALID_PARAMETER
显然,hdc是无效的参数。发生这种情况是因为在您的:中
hdc = Graphics.FromHwnd(IntPtr.Zero).GetHdc()
Graphics.FromHwnd(IntPtr.Zero)
是图形对象,从中可以获得hdc。此对象已不存在,因此出现错误。
每次调用SetBrightness
函数时获得一个hdc:hdc = Graphics.FromHwnd(IntPtr.Zero).GetHdc()
可以解决问题,但它会通过调用Graphics.FromHwnd(IntPtr.Zero).ReleaseHdc();
而崩溃(正如您在评论中所说)。这是因为Graphics.FromHwnd(IntPtr.Zero)
与第一个不同。
解决方案
有很多:
1。创建图形对象,并在每次调用SetBrightness时从中获取dc句柄,最终释放资源:
public static unsafe bool SetBrightness(short brightness)
{
Graphics gr;
gr = Graphics.FromHwnd(IntPtr.Zero);
IntPtr hdc = gr.GetHdc(); //Use IntPtr instead of Int32, don't need private static Int32 hdc;
...
...
//For some reason, this always returns false?
bool retVal = SetDeviceGammaRamp(hdc, gArray);
//Memory allocated through stackalloc is automatically free'd
//by the CLR.
gr.ReleaseHdc();
gr.Dispose();
return retVal;
}
2。创建图形对象并从中获取InitializeClass()函数中的dc句柄一次,在程序结束时释放资源:
private static IntPtr hdc;
private static Graphics gr;
private static void InitializeClass()
{
if (initialized)
return;
gr = Graphics.FromHwnd(IntPtr.Zero);
hdc = gr.GetHdc();
initialized = true;
}
public static unsafe bool SetBrightness(short brightness)
{
InitializeClass();
//hdc = Graphics.FromHwnd(IntPtr.Zero).GetHdc(); //You don't need it since we get the hdc once.
...
...
//For some reason, this always returns false?
bool retVal = SetDeviceGammaRamp(hdc, gArray);
//Memory allocated through stackalloc is automatically free'd
//by the CLR.
return retVal;
}
3。使用GetDC
api:
[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);
private static IntPtr hdc;
private static void InitializeClass()
{
if (initialized)
return;
hdc = GetDC(IntPtr.Zero);
initialized = true;
}
public static unsafe bool SetBrightness(short brightness)
{
InitializeClass();
//hdc = Graphics.FromHwnd(IntPtr.Zero).GetHdc(); //You don't need it since we get the hdc once.
...
...
//For some reason, this always returns false?
bool retVal = SetDeviceGammaRamp(hdc, gArray);
//Memory allocated through stackalloc is automatically free'd
//by the CLR.
return retVal;
}
在程序发布的最后hdc:
ReleaseDC(IntPtr.Zero, hdc);
这是一个非常简单、经典的内存管理问题。
你需要保留的参考
Graphics.FromHwnd(IntPtr.Zero)
否则垃圾收集器(通常也是GC)会认为您没有使用图形上下文(GC)。
保留引用意味着保留一个有效的引用,有时称为"活动对象"。在你的课堂亮度情况下,它翻译为(来自内存,而不是编译!)
private static bool initialized = false;
private static Graphics gc;
private static void InitializeClass()
{
if (initialized)
return;
//Get the hardware device context of the screen, we can do
//this by getting the graphics object of null (IntPtr.Zero)
gc = Graphics.FromHwnd(IntPtr.Zero);
initialized = true;
}
然后调用SetRamp(gc.GetHdc().ToInt32(),…);
关键的一点是,只要亮度被初始化,就需要Graphics对象,所以垃圾收集器不会释放它
您只记得它的数字句柄值,当显式调用ReleaseDC()时,您正在执行运行时的工作,当运行时执行相同操作时崩溃。
我认为两次使用Graphics.FromHwnd(IntPtr.Zero).GetHdc().ToInt32();
没有任何问题,尽管我希望两次的值都相同,除非程序外部的东西影响了它。
我更担心的是下面的部分总是返回false。
//For some reason, this always returns false?
bool retVal = SetDeviceGammaRamp(hdc, gArray);
文件规定了
返回值
如果此函数成功,则返回值为TRUE。
如果此函数失败,则返回值为FALSE。
所以我想说,有些事情不正常,调用总是返回false。
我想这是因为GetHdc()
返回的设备上下文句柄必须在使用后释放,假设您会尽快这样做;您应该能够在通过ReleaseHdc()
呼叫SetDeviceGammaRamp()
之后立即释放它。从本质上讲,任何时候你得到DC,你都应该尽快发布它。
C#在Win32 GDI API上只提供了一个薄薄的贴面,因此应用的规则是GetDC()
和ReleaseDC()
的规则,您可以使用IntPtr.Zero来pinvoke GetDC()
以达到相同的效果(并且存在相同的问题)。
GDI是这样挑剔的,你只是在"借用"句柄,而你得到的句柄通常是从某个大小的池中分配的,一旦用完,你就不会得到更多的DC,因为你还没有发布以前的DC。如果一个人重复地分配GDI画笔或笔来绘画,也是如此;最终你会筋疲力尽。此外,保留池句柄通常效果不佳;我不能确切地告诉你GDI内部的问题是什么,但我以前也遇到过类似的问题,除了驱动程序的问题,如果你得到DC,设置斜坡,然后立即释放DC,我希望你的方法能奏效。
顺便说一句,MSDN中提到的这些成对规则也有例外,例如,通过Win32的CS_OWNDC
分配给窗口的专用DC;那些不是从游泳池里来的,所以你可以把它们留多久就留多久。
顺便说一句,除非你强制使用x86构建,否则我建议在SetDeviceGammaRamp()
中使用IntPtr而不是Int32,因为这是一个指针大小的整数,而不是一个总是32位的值。而不是void*
,我会将RAMP
结构或MarshalAs
定义为固定长度的数组。但我不认为这两个都是你的问题。当你在做的时候,你可以切换到通过pinvoke调用GetDC()
和ReleaseDC()
,并剪切出一个你不使用的Graphics对象。PInvoke.net(在撰写本文时)对SetDeviceGammaRamp()
以及GetDC()
和ReleaseDC()
有一个更安全的定义。