1

我正在使用一个类来增加和减少我所有屏幕的伽玛,然后我启动程序并增加或减少伽玛它工作正常,但过了一段时间(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);
        }
    }
4

4 回答 4

1

这是一个非常简单、经典的内存管理问题。

您需要保留参考

Graphics.FromHwnd(IntPtr.Zero)

周围,​​否则垃圾收集器(顺便说一句 GC)会认为你没有使用你的图形上下文(GC)。

保留一个参考意味着保留一个有效的参考,有时称为“活动对象”。在您的班级 Brightness 案例中,它转换为(从内存中,未编译!)

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(), ...);

关键是只要初始化了Brightness,就需要Graphics对象,所以垃圾回收器不会释放它。

您只记得它的数字句柄值,并且当显式调用 ReleaseDC() 时,您正在执行运行时的工作,当运行时执行相同操作时会崩溃。

于 2015-01-16T16:48:03.957 回答
1

如果您想在代码失败时查看错误代码,可以使用:

[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的图形对象。该对象不再存在,因此出现错误。

获取 hdc:hdc = Graphics.FromHwnd(IntPtr.Zero).GetHdc()每次SetBrightness调用函数时都会解决问题,但 它会崩溃(正如您在评论中所说)调用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.使用GetDCapi:

[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);
于 2015-01-17T20:46:45.577 回答
0

我没有看到使用Graphics.FromHwnd(IntPtr.Zero).GetHdc().ToInt32();两次的具体问题,尽管我希望两次的值相同,除非您的程序外部的某些东西正在影响它。

我会更担心以下部分总是返回错误。

 //For some reason, this always returns false?
 bool retVal = SetDeviceGammaRamp(hdc, gArray);

文档指定

返回值

如果此函数成功,则返回值为 TRUE。

如果此函数失败,则返回值为 FALSE。

所以我会说某些东西无法正常工作,该调用始终返回 false。

于 2013-09-03T07:36:13.343 回答
0

我猜这是因为返回的设备上下文句柄GetHdc()必须在使用后释放,假设您会尽快这样做;您应该能够在SetDeviceGammaRamp()通过调用后立即释放它ReleaseHdc()。基本上任何时候你得到一个 DC,你都应该尽快释放它。

C# 在 Win32 GDI API 上只提供了一层薄薄的外衣,因此适用的规则是GetDC()ReleaseDC(),您可以GetDC()使用 IntPtr.Zero 进行 pinvoke 以达到相同的效果(并且具有相同的问题)。

GDI 就是这样挑剔的,你只是“借用”句柄,而你得到的分配的句柄通常来自一定大小的池,一旦用完,你就不会再得到任何 DC,因为你没有' t发布了以前的。如果重复分配 GDI 画笔或钢笔进行绘画,情况也是如此。最终你用完了。此外,保留池句柄通常效果不佳。我不能确切地告诉你 GDI 内部的问题是什么,但我之前遇到过类似的问题,除非驱动程序出现问题,否则我希望你的方法能够工作,如果你得到 DC,设置坡道,然后释放直流电。

顺便说一句,这些成对规则有一些例外,如 MSDN 中所述,例如对于通过 Win32 分配给窗口的私有 DC CS_OWNDC;这些不是来自游泳池,因此您可以随心所欲地保留它们。

顺便说一句,除非您强制 x86 构建,否则我建议使用 IntPtr 而不是 Int32 in SetDeviceGammaRamp(),因为这是一个指针大小的整数,而不是始终为 32 位的值。而不是void*,我会定义RAMP结构或MarshalAs固定长度的数组。但我认为这些都不会导致您的问题。当您使用它时,您可以切换到调用GetDC()ReleaseDC()通过 pinvoke 并剪切出您不使用的 Graphics 对象。PInvoke.net(在撰写本文时)对SetDeviceGammaRamp()以及GetDC()和有一个更安全的定义ReleaseDC()

于 2015-01-13T22:41:52.023 回答