Программа работает правильно из TCL exec

0

У меня есть следующая 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;
}
  • 1
    Можете ли вы добавить немного больше информации о природе аварии? Кроме того, точная версия Tcl, которую вы используете. (В вашем коде нет ничего, что было бы очевидным курящим оружием, по крайней мере для меня, и я использую другую платформу, поэтому я не могу попробовать для себя.)
  • 0
    @DonalFellows Я работаю на Win7 64bit, и когда я запускаю команду exec, как показано в примере, программа wish.exe зависает, и когда я пытаюсь закрыть программу, Win7 сообщает, что приложение неожиданно прервано. Я использую Tcl 8,6.
Показать ещё 4 комментария
Теги:
tcl

1 ответ

2
Лучший ответ

Как вы отметили в комментариях, настоящая проблема заключается в том, что код висит, когда вы пытаетесь сделать снимок экрана окна 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 для вашей программы скриншотов, вместо того, чтобы выполнять нечетную собственную обработку).

Ещё вопросы

Сообщество Overcoder
Наверх
Меню