c++ - 使用 C++ Builder 在 Windows 上截取屏幕截图的线程安全

标签 c++ thread-safety c++builder vcl

在 Windows 上截屏不是线程安全的吗?

我的以下代码有时会拍摄一些照片,但在大多数情况下,imgScreenshot(这只是一个 TImage)一直只是纯白色...

我错过了什么吗?

void __fastcall TCaptureThread::Execute()
{
    int CurWidth = 1600;
    int CurHeight = 900;

    std::unique_ptr<TCanvas> Canvas(new TCanvas);
    Canvas->Handle = GetDC(0);    

    FBMP = new TBitmap;                     // private class field
    FBMP->Width     = CurWidth;
    FBMP->Height    = CurHeight;    

    FR = Rect(0, 0, CurWidth, CurHeight);   // private class field

    while(!Terminated)
    {
        FBMP->Canvas->CopyRect(FR, Canvas, FR);       
        Synchronize(&UpdatePicture);

        Sleep(100);                 
    }

    delete FBMP;
    FBMP = NULL;
}

void __fastcall TCaptureThread::UpdatePicture()
{
    FMainForm->imgScreenshot->Canvas->CopyRect(FR, FBMP->Canvas, FR);
}

环境是 C++ Builder 10.1.2 Berlin

最佳答案

Isn't taking a screenshot on Windows thread-safe?

在使用默认情况下不是线程安全的 VCL 包装器类时不是。如果您直接使用普通的 Win32 API 函数,那么可以编写线程安全代码。

您的代码失败的主要原因是因为 VCL 旨在在多个对象之间共享 GDI 资源,而主 UI 线程经常释放未使用/休眠的 GDI 资源。因此,您的工作线程的 TBitmap 图像数据可能会在您调用 Synchronize() 将其复制到您的 TImage 之前被破坏。

也就是说,如果您在 上调用 Lock()/Unlock(),您正在尝试的可以完成工作线程中的 Canvas 对象,例如:

struct CanvasLocker
{
    TCanvas *mCanvas;
    CanvasLocker(TCanvas *C) : mCanvas(C) { mCanvas->Lock(); }
    ~CanvasLocker() { mCanvas->Unlock(); }
};

void __fastcall TCaptureThread::Execute()
{
    int CurWidth = 1600;
    int CurHeight = 900;

    std::unique_ptr<TCanvas> Canvas(new TCanvas);
    std::unique_ptr<TBitmap> BMP(new TBitmap);
    FBMP = BMP.get();

    {
    CanvasLocker lock(Canvas); // <-- add this!
    Canvas->Handle = GetDC(0);    
    }

    {
    CanvasLocker lock(BMP->Canvas); // <-- add this!
    BMP->Width = CurWidth;
    BMP->Height = CurHeight;    
    }

    FR = Rect(0, 0, CurWidth, CurHeight);

    while (!Terminated)
    {
        {
        CanvasLocker lock1(Canvas); // <-- add this!
        CanvasLocker lock2(BMP->Canvas); // <-- add this!
        BMP->Canvas->CopyRect(FR, Canvas.get(), FR);
        }

        Synchronize(&UpdatePicture);

        Sleep(100);                 
    }
}

void __fastcall TCaptureThread::UpdatePicture()
{
    CanvasLocker lock1(FBMP->Canvas); // <-- add this!
    CanvasLocker lock2(FMainForm->imgScreenshot->Canvas); // <-- add this!

    FMainForm->imgScreenshot->Canvas->CopyRect(FR, FBMP->Canvas, FR);
    // or: FMainForm->imgScreenshot->Picture->Bitmap->Assign(FBMP);
}

话虽这么说,因为 TCanvas 是可锁定的,您可能能够完全移除 Synchronize():

void __fastcall TCaptureThread::Execute()
{
    int CurWidth = 1600;
    int CurHeight = 900;

    std::unique_ptr<TCanvas> Canvas(new TCanvas);
    std::unique_ptr<TBitmap> BMP(new TBitmap);

    {
    CanvasLocker lock(Canvas);
    Canvas->Handle = GetDC(0);    
    }

    {
    CanvasLocker lock(BMP->Canvas);
    BMP->Width = CurWidth;
    BMP->Height = CurHeight;    
    }

    TRect r = Rect(0, 0, CurWidth, CurHeight);

    while (!Terminated)
    {
        {
        CanvasLocker lock1(BMP->Canvas);

        {
        CanvasLocker lock2(Canvas);
        BMP->Canvas->CopyRect(r, Canvas.get(), r);
        }

        CanvasLocker lock3(FMainForm->imgScreenshot->Canvas);
        FMainForm->imgScreenshot->Canvas->CopyRect(r, BMP->Canvas, r);
        // or: FMainForm->imgScreenshot->Picture->Bitmap->Assign(BMP);
        }

        Sleep(100);                 
    }
}    

关于c++ - 使用 C++ Builder 在 Windows 上截取屏幕截图的线程安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41149999/

相关文章:

c++ - 找不到 -lperl 在 C++ 上执行 makefile

c++ - Qt 在另一个小部件内调整小部件的大小

java - Java Lambdas线程安全

c++ - 如果线程在不相交的索引范围内读写,std::vector 是线程安全的吗?

c# - 在 C++Builder 应用程序中动态加载 C# .NET 程序集

c++ - Node* 与 main 中的 Node

c++ - 全局操作符 + 覆盖冲突检测

java - gRPC 中的 channel / stub 是否是线程安全的

delphi - 将滚动条隐藏在 Delphi dbgrid 中(即使在调整大小时)

c++ - embarcadero WindowHandleToPlatform c++ 示例