У меня есть следующая C++ программа, написанная/заимствованная, которая берет снимок экрана окна на основе имени окна.
Когда я запускаю программу через командную строку Windows, она работает правильно. Однако, когда я вызываю программу в TCL-скрипте с помощью команды exec
, он вызывает приложение wish86.
Почему программа работает через командную строку, но не с командой exec
?
Пример: screenshot.exe calc.bmp "Calculator"
#include <windows.h>
#include <gdiplus.h>
#include <stdio.h>
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "gdiplus.lib")
using namespace Gdiplus;
// From http://msdn.microsoft.com/en-us/library/ms533843%28VS.85%29.aspx
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = NULL;
GetImageEncodersSize(&num, &size);
if(size == 0)
return -1; // Failure
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if(pImageCodecInfo == NULL)
return -1; // Failure
GetImageEncoders(num, size, pImageCodecInfo);
for(UINT j = 0; j < num; ++j) {
if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 ) {
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
}
}
free(pImageCodecInfo);
return -1; // Failure
}
int wmain(int argc, wchar_t** argv)
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
HDC desktopdc;
HDC mydc;
HBITMAP mybmp;
desktopdc = GetDC(NULL);
if(desktopdc == NULL) {
return -1;
}
// If three arguments were passed, capture the specified window
if(argc == 3) {
RECT rc;
// Convert wchar_t[] to char[]
char title[512] = {0};
wcstombs(title, argv[2], wcslen(argv[2]));
HWND hwnd = FindWindow(NULL, title); //the window can't be min
if(hwnd == NULL) {
return -1;
}
GetWindowRect(hwnd, &rc);
mydc = CreateCompatibleDC(desktopdc);
mybmp = CreateCompatibleBitmap(desktopdc, rc.right - rc.left, rc.bottom - rc.top);
SelectObject(mydc,mybmp);
//Print to memory hdc
PrintWindow(hwnd, mydc, PW_CLIENTONLY);
} else {
// Capture the entire screen
int width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
mydc = CreateCompatibleDC(desktopdc);
mybmp = CreateCompatibleBitmap(desktopdc, width, height);
HBITMAP oldbmp = (HBITMAP)SelectObject(mydc, mybmp);
BitBlt(mydc,0,0,width,height,desktopdc,0,0, SRCCOPY|CAPTUREBLT);
SelectObject(mydc, oldbmp);
}
const wchar_t* filename = (argc > 1) ? argv[1] : L"screenshot.png";
Bitmap* b = Bitmap::FromHBITMAP(mybmp, NULL);
CLSID encoderClsid;
Status stat = GenericError;
if (b && GetEncoderClsid(L"image/png", &encoderClsid) != -1) {
stat = b->Save(filename, &encoderClsid, NULL);
}
if (b)
delete b;
// cleanup
GdiplusShutdown(gdiplusToken);
ReleaseDC(NULL, desktopdc);
DeleteObject(mybmp);
DeleteDC(mydc);
DeleteDC(desktopdc);
return stat == Ok;
}
Как вы отметили в комментариях, настоящая проблема заключается в том, что код висит, когда вы пытаетесь сделать снимок экрана окна GUI, принадлежащего процессу Tcl, вызывающего программу скриншота (с помощью exec
). Это связано с тем, что Windows хочет, чтобы процесс обслуживал свой насос сообщений, который Tcl сопоставляет с циклом событий, но эта обработка сообщений остановилась, поскольку Tcl занят в ожидании блокировки для завершения подпроцесса. (Это как работает exec
и как он всегда работал.) Это блокирует все в тупике, и похоже, что разблокировка не обрабатывается очень изящно.
Так как ваша программа не считывает ввода или не выводит какой-либо результат (ну, он делает и то и другое, но аргументами и "хорошо известным" файлом), то, что вам нужно, - это способ запускать программу в фоновом режиме, сохраняя возможность узнайте, когда он закончит. Это означает, что мы не хотим использовать exec
; который имеет только два режима работы: блокировка ожидания или полностью отключенный асинхронный (если последний параметр &
). Первая проблема, и последняя не дает нам необходимой информации.
Вместо этого нам нужен конвейер, чтобы мы могли снимать скриншот в фоновом режиме, продолжая обрабатывать события, и нам нужно установить fileevent
чтобы мы узнали, когда все будет fileevent
(поскольку это своего рода событие под капотом),
set thepipeline [open |[list screenshot.exe $filename $windowname] r]
fileevent $thepipeline readable [list doneScreenshot $thepipeline $filename]
proc doneScreenshot {pipe filename} {
# Pipelines become readable when either:
# 1. there some data to read, or
# 2. the pipe gets closed.
# It option 2 that happens here.
close $pipe
puts "screenshot now available in $filename"
}
Поскольку Tcl-программы Tcl запускают цикл событий в любом случае, мы не будем использовать vwait
для его создания. (Написание кода для асинхронности иногда может быть немного изгиб мозга, подумайте "обратный вызов", а не "поток программы". Если вы не используете Tcl 8.6, когда мы можем использовать сопрограмму для распутывания вещей.)
Побочные примечания: Вы, вероятно, хотите, чтобы не передать $filename
в screenshot.exe
, а [file nativename [file normalize $filename]]
, но это не проблема, вы испытываете здесь. И вам не нужно вручную добавлять кавычки вокруг $windowname
; время выполнения Tcl сделает это, если необходимо (при условии, что вы используете среду выполнения MSVC для вашей программы скриншотов, вместо того, чтобы выполнять нечетную собственную обработку).