程序从TCL exec正常工作

时间:2013-12-31 22:42:13

标签: c++ shell tcl

我有以下编写/借用的C ++程序,它根据窗口名称截取窗口的截图。

当我通过Windows命令提示符运行程序时,它可以正常工作。但是,当我使用exec命令在TCL脚本中调用该程序时,它会使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 个答案:

答案 0 :(得分:2)

正如您在评论中提到的,真正的问题是,当您尝试执行调用屏幕截图程序(使用exec)的Tcl进程所拥有的GUI窗口的屏幕截图时,代码会挂起。 这是因为Windows希望进程为其消息泵提供服务--Tcl映射到事件循环 - 但是消息处理已经停止,因为Tcl忙于阻塞等待子进程完成。 (这就是exec的工作原理以及它是如何工作的。)这会阻止所有事情陷入僵局,而且听起来解锁不会得到很好的处理。

由于你的程序没有读取任何输入或产生任何输出(好吧,它确实做了两个,但是通过参数和“众所周知的”文件)你需要的是一种在后台运行程序的方法能够找出它何时完成。这意味着我们不想使用exec;只有两种操作模式:阻塞等待或完全断开异步(如果最后一个参数是&)。前者是问题,后者没有给我们提供所需的信息。

相反,我们需要一个管道,以便我们可以在后台中截取屏幕截图,同时仍然处理事件,我们需要设置一个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's some data to read, or
    #  2. the pipe gets closed.
    # It's option 2 that happens here.
    close $pipe
    puts "screenshot now available in $filename"
}

由于基于Tk的Tcl程序无论如何都运行了一个事件循环,我们不会使用vwait来创建一个。 (将代码编写为异步有时可能会有点大脑弯曲;想想“回调”而不是“程序流”。除非你使用Tcl 8.6,否则我们可以使用协程来解开事物。)

附注:您可能希望不将$filename传递给screenshot.exe,而是[file nativename [file normalize $filename]],但这不是您在这里遇到的问题。而且您无需在$windowname周围手动添加引号;如果需要,Tcl运行时将执行此操作(假设您正在使用MSVC运行时进行截屏程序,而不是对您自己进行奇怪的处理)。