Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Issue with getting a screenshot in Win 10 from within TIdHTTPServer GET handler
#2
(06-29-2022, 04:31 PM)Ahmed Sayed Wrote: I am having a weird issue regarding taking a desktop screenshot and sending it via http GET request on Windows 10. This works fine on windows 7 but I don't know for some reason it does not work the same on windows 10. It always produces a white image.

This is not a Windows issue, or even an Indy issue. It is a VCL threading issue, and one that has existed for a very long time, so you probably just didn't notice it before due to the timing of your code.

In a nutshell, the VCL maintains a global cache of HDC handles for the Vcl::Graphics::TBitmapCanvas and Vcl::Controls::TControlCanvas classes. In a GUI project, the main message loop will frequently (ie, after every message processed!) free unused HDC handles in that cache.

Which means, in your case, that any TBitmap object you are using outside of the main UI thread risks having its GDI resources rip out from behind its back unexpectedly. That is why you are getting trashed images.

To avoid this, you MUST call the Canvas->Lock() and Canvas->Unlock() methods of the TBitmap when it is used in a worker thread. The main UI thread will not free resources for locked canvases.

(06-29-2022, 04:31 PM)Ahmed Sayed Wrote: at first I thought it did not work because of the function is called inside a separate thread

That is exactly why it is not working correctly.

(06-29-2022, 04:31 PM)Ahmed Sayed Wrote: but I tested it with another thread call and it worked just fine

Only due to a fluke in timing. When you click on your button to create the thread, and then the button handler returns control back to the main message loop, the main thread frees unlocked canvas resources, which is likely occurring either before or after your thread is using them, so your test does not get invalidated.

(06-29-2022, 04:31 PM)Ahmed Sayed Wrote: What am I doing wrong?

You are not locking access to the TBitmap::Canvas to protect its resources.

Try something more like this:

Code:
struct HDC_deleter
{
    using pointer = HDC;
    void operator()(HDC hdc) { ReleaseDC(nullptr, hdc); }
};

struct CanvasLock
{
    TCanvas *m_canvas;
    CanvasLock(TCanvas *canvas) : m_canvas(canvas) { m_canvas->Lock(); }
    ~CanvasLock() { m_canvas->Unlock(); }
};

std::unique_ptr<TStream> GetDesktopScreenShotStream()
{
    auto Stream = std::make_unique<TMemoryStream>();

    std::unique_ptr<HDC, HDC_deleter> hDC{ GetDC(nullptr) };
    /* alternatively:
    auto hdc_deleter = [](HDC hdc){ ReleaseDC(nullptr, hdc); };
    std::unique_ptr<std::remove_pointer_t<HDC>, decltype(hdc_deleter)> hDC{ GetDC(nullptr), hdc_deleter };
    */

    auto VCLBitmap = std::make_unique<Vcl::Graphics::TBitmap>();
    CanvasLock lock{ VCLBitmap->Canvas };

    VCLBitmap->PixelFormat = pf24bit;
    VCLBitmap->SetSize(Screen->Width, Screen->Height);
    ::BitBlt(VCLBitmap->Canvas->Handle, 0, 0, VCLBitmap->Width, VCLBitmap->Height, hDC.get(), 0, 0, SRCCOPY);
    VCLBitmap->SaveToStream(Stream.get());
    Stream->Position = 0;

    return std::move(Stream);
}

Code:
void TForm1::SaveScreen()
{
    auto Stream = GetDesktopScreenShotStream();
    static_cast<TMemoryStream*>(Stream.get())->SaveToFile(_D("test.bmp"));
}

void __fastcall TForm1::IdHTTPServer1CommandGet(TIdContext *AContext,
    TIdHTTPRequestInfo *ARequestInfo, TIdHTTPResponseInfo *AResponseInfo)
{
    AResponseInfo->ContentStream = GetDesktopScreenShotStream().release();
    AResponseInfo->ContentType = _D("image/bmp");

    /* alternatively:
    auto Stream = GetDesktopScreenShotStream();
    AResponseInfo->ContentStream = Stream.get();
    AResponseInfo->FreeContentStream = false;
    AResponseInfo->ContentType = _D("image/bmp");
    AResponseInfo->WriteHeader();
    AResponseInfo->WriteContent();
    */
}

void __fastcall TForm1::Button2Click(TObject *Sender)
{
    Thread(FuncBind( &TForm1::SaveScreen, this));
}

The alternative is to simply not use TBitmap in a worker thread to begin with. Prepare and save the BMP data using direct Win32 API calls only, or maybe use GDI+ instead, or find a 3rd-party graphics library that is thread-safe.

Reply


Messages In This Thread
RE: Issue with getting a screenshot in Win 10 from within TIdHTTPServer GET handler - by rlebeau - 06-30-2022, 04:49 PM

Forum Jump:


Users browsing this thread: 1 Guest(s)