安全取消CopyFileEx进程

时间:2018-01-15 14:21:22

标签: c++ winapi

我创建了一个带有进度条和取消按钮的对话框,使用CreateDialogParam显示复制多个文件时的状态(使用CopyFileEx)。

如何正确使用CopyFileEx取消进程,从按对话框中的取消按钮开始?无论如何我可以不使用全局变量吗?我如何正确处理返回的PROGRESS_CANCEL?我在下面的代码中提供了一些问题,以便更清楚地了解我需要哪些帮助。

//copy function
BOOL copy(HWND &hWnd, std::vector <FILECONSOLIDATEPARAMS> &vec)
{
    //pass vector as lparam to dialogbox proc
    LPARAM lp = reinterpret_cast<LPARAM>(&vec);

    HWND hCopy = CreateDialogParam(GetModuleHandle(NULL), 
        MAKEINTRESOURCE(IDD_DIALOG1),
        hwndmain, (DLGPROC)dlgboxcopyproc, lp);

    static HWND hIDC_STATIC, hIDC_STATIC4;
        hIDC_STATIC = GetDlgItem(hCopy, IDC_STATIC);
        hIDC_STATIC4 = GetDlgItem(hCopy, IDC_STATIC4);
        LPBOOL pbCancel = FALSE;

    size_t s;
    for (s = 0; s != vec.size(); s++)
    {
        SendMessage(hIDC_STATIC, WM_SETTEXT, 0, (LPARAM)vec[s].filename);
        SendMessage(hIDC_STATIC4, WM_SETTEXT, 0,(LPARAM)vec[s].destination);

        BOOL b = CopyFileEx(vec[s].filename, vec[s].destination,
            &CopyProgressRoutine,(LPVOID)hCopy,pbCancel, NULL);

        //how to catch and process PROGRESS_CANCEL?

        if (!b)
        {
            DWORD dw = GetLastError();
            ShowErrMsg(dw);
        }
}

    PostMessage(hCopy, WM_DESTROY, 0, 0);

    return TRUE;
}


//dialogbox procedure
INT_PTR CALLBACK dlgboxcopyproc(HWND hWndDlg,UINT Msg,WPARAM wParam,LPARAM 
    lParam)
{
    //translate passed lparam back to vector
    std::vector<FILECONSOLIDATEPARAMS>& vect = 
    *reinterpret_cast<std::vector<FILECONSOLIDATEPARAMS>*>(lParam);

    INITCOMMONCONTROLSEX _icex;
    _icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    _icex.dwICC = ICC_PROGRESS_CLASS;
    InitCommonControlsEx(&_icex);

    static HWND hParent;
    static HWND hIDCancel;
    static HWND hIDC_PROGRESS1;
    static HWND hIDC_STATIC;

    hParent = GetParent(hWndDlg);
    hIDCancel = GetDlgItem(hWndDlg, IDCANCEL);
    hIDC_PROGRESS1 = GetDlgItem(hWndDlg, IDC_PROGRESS1);

    switch (Msg)
    {
        case WM_INITDIALOG:
        {
            SendMessage(hIDC_PROGRESS1, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
        }
        return (INT_PTR)TRUE;

        case WM_COMMAND:
        {
            switch (LOWORD(wParam))
            {
                case IDCANCEL:
                EndDialog(hWndDlg, FALSE); //how to make pbCancel = TRUE?
                return (INT_PTR)TRUE;
            }
        }
        break;

        case WM_DESTROY:
        {
            DestroyWindow(hWndDlg);
        }
    }
    return FALSE;
}

//copyprogressroutine callback function
DWORD CALLBACK CopyProgressRoutine(LARGE_INTEGER TotalFileSize, 
    LARGE_INTEGER TotalBytesTransferred, LARGE_INTEGER StreamSize, 
    LARGE_INTEGER StreamBytesTransferred, DWORD dwStreamNumber, DWORD 
    dwCallbackReason, HANDLE hSourceFile, HANDLE hDestinationFile, LPVOID 
    lpData)
{   
    HWND hWndDlg = (HWND)lpData;

    static HWND hwndIDC_PROGRESS1;
    hwndIDC_PROGRESS1 = GetDlgItem(hWndDlg, IDC_PROGRESS1);

    DOUBLE Percentage = ((DOUBLE)TotalBytesTransferred.QuadPart / 
        (DOUBLE)TotalFileSize.QuadPart) * 100;

    switch (dwCallbackReason)
    {
        case CALLBACK_CHUNK_FINISHED:
        SendMessage(hwndIDC_PROGRESS1, PBM_SETPOS, (WPARAM) Percentage, 0);
        break;

        case CALLBACK_STREAM_SWITCH:
        Percentage = 0;
        break;
    }
    return PROGRESS_CONTINUE; //how to make conditional return PROGRESS_CANCEL?
}

1 个答案:

答案 0 :(得分:0)

代码

LPBOOL pbCancel = FALSE;
CopyFileEx(, pbCancel, )

无意义。实际上我们只是简单地传递0 pbCancel 并且没有能力取消操作。 我们需要 alocate 一些BOOL类型的变量(让它命名为 bCancel )并将此变量的指针传递给CopyFileEx 像这样的东西:

BOOL bCancel = FALSE;
CopyFileEx(, &bCancel, )

CopyFileEx将通过传递指针定期查询此变量的值,如果它变为true - 中断操作并返回ERROR_REQUEST_ABORTED错误。

下一个 - CopyFileEx直到操作完成才返回 - 这是同步api - 因此无法从GUI线程调用它(或者它只是阻止GUI)。需要从单独的线程中调用它。

这么基本的解决方案 - 分配一些数据结构,这里放置BOOL bCancel,以及我们将在复制期间使用的其他数据。我们将从2个线程 - gui线程(来自对话框程序)和单独的线程(将调用CopyFileEx)访问此数据结构。为了正确实现这一点 - 需要在结构上使用引用计数。 lpProgressRoutine 需要向gui线程发送消息以通知它有关进度的信息(gui线程可以在此通知上移动进度条)。对话框可以包含例如Cancel按钮。当用户点击它时 - GUI线程只需设置bCancel = TRUE,结果CopyFileEx中止下一个块的操作。开始复制 - 需要启动新线程,调用CopyFileEx。基于所需的逻辑 - 可以从WM_INITDIALOG执行此操作,或者说当用户按下对话框中的某个按钮时

基本代码:

struct CopyDlg 
{
    enum {
        WM_COPYRESULT = WM_APP, WM_PROGRESS
    };

    struct ProgressData {
        LARGE_INTEGER TotalFileSize;
        LARGE_INTEGER TotalBytesTransferred;
        LARGE_INTEGER StreamSize;
        LARGE_INTEGER StreamBytesTransferred;
        DWORD dwStreamNumber;
    };

    PCWSTR m_lpExistingFileName, m_lpNewFileName;
    HWND m_hwnd, m_hwndFile, m_hwndStream;
    ULONG m_shift;
    LONG m_dwRefCount;
    LONG m_hwndLock;
    BOOL m_bCancel;

    CopyDlg()
    {
        m_dwRefCount = 1;
    }

    void AddRef()
    {
        InterlockedIncrement(&m_dwRefCount);
    }

    void Release()
    {
        if (!InterlockedDecrement(&m_dwRefCount))
        {
            delete this;
        }
    }

    void LockWnd()
    {
        InterlockedIncrement(&m_hwndLock);
    }

    void UnlockWnd()
    {
        if (!InterlockedDecrement(&m_hwndLock))
        {
            // want m_hwnd be valid at PostMessage(p->m_hwnd,) time
            EndDialog(m_hwnd, 0);
        }
    }

    void OnProgress(DWORD dwCallbackReason, ProgressData* p)
    {
        if (dwCallbackReason == CALLBACK_STREAM_SWITCH)
        {
            if (p->dwStreamNumber == 1)
            {
                m_shift = 0;

                LONGLONG QuadPart = p->TotalFileSize.QuadPart;

                while (QuadPart > MAXLONG)
                {
                    m_shift++;
                    QuadPart >>= 1;
                }

                SendMessage(m_hwndFile, PBM_SETRANGE32, 0, (LPARAM)QuadPart);
                SendMessage(m_hwndFile, PBM_SETPOS, 0, 0);
            }

            SendMessage(m_hwndStream, PBM_SETRANGE32, 0, (LPARAM)(p->StreamSize.QuadPart >> m_shift));
            SendMessage(m_hwndStream, PBM_SETPOS, 0, 0);
        }
        else
        {
            SendMessage(m_hwndStream, PBM_SETPOS, (LPARAM)(p->StreamBytesTransferred.QuadPart >> m_shift), 0);
            SendMessage(m_hwndFile, PBM_SETPOS, (LPARAM)(p->TotalBytesTransferred.QuadPart >> m_shift), 0);
        }
    }

    ULONG StartCopy(PCWSTR lpExistingFileName, PCWSTR lpNewFileName)
    {
        m_bCancel = FALSE;
        m_lpExistingFileName = lpExistingFileName; 
        m_lpNewFileName = lpNewFileName;

        LockWnd();
        AddRef();
        if (HANDLE hThread = CreateThread(0, 0, reinterpret_cast<PTHREAD_START_ROUTINE>(CopyThread), this, 0, 0))
        {
            CloseHandle(hThread);
            return NOERROR;
        }

        Release();
        UnlockWnd();
        return GetLastError();
    }

    static INT_PTR CALLBACK _DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        if (uMsg == WM_INITDIALOG)
        {
            SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)lParam);
        }

        if (CopyDlg* p = reinterpret_cast<CopyDlg*>(GetWindowLongPtrW(hwndDlg, DWLP_USER)))
        {
            p->AddRef();
            INT_PTR r = p->DialogProc(hwndDlg, uMsg, wParam, lParam);
            p->Release();
            return r;
        }

        return 0;
    }

    INT_PTR DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
        case WM_NCDESTROY:
            Release();
            break;

        case WM_DESTROY:
            m_bCancel = TRUE;
            break;

        case WM_COMMAND:
            switch (wParam)
            {
            case MAKEWPARAM(IDCANCEL, BN_CLICKED):
                m_bCancel = TRUE;
                break;
            }
            break;

        case WM_CLOSE:
            m_bCancel = TRUE;
            UnlockWnd();
            ShowWindow(hwndDlg, SW_HIDE);//for not get wm_close twice
            break;

        case WM_COPYRESULT:
            UnlockWnd();
            // lParam == error code from CopyFileExW
            DbgPrint("CopyFileExW=%u\n", (ULONG)lParam);
            break;

        case WM_PROGRESS:
            OnProgress((DWORD)wParam, reinterpret_cast<ProgressData*>(lParam));
            delete (void*)lParam;
            break;

        case WM_INITDIALOG:
            AddRef();
            m_hwnd = hwndDlg;
            m_hwndStream = GetDlgItem(hwndDlg, IDC_PROGRESS1);
            m_hwndFile = GetDlgItem(hwndDlg, IDC_PROGRESS2);
            m_hwndLock = 1;
            StartCopy(L"**", L"**");
            break;
        }
        return 0;
    }

    static ULONG CALLBACK CopyThread(CopyDlg* p)
    {
        PostMessage(p->m_hwnd, WM_COPYRESULT, 0, CopyFileExW(
            p->m_lpExistingFileName, p->m_lpNewFileName, 
            reinterpret_cast<LPPROGRESS_ROUTINE>(CopyProgressRoutine), 
            p, &p->m_bCancel, 0) ? NOERROR : GetLastError());

        p->Release();

        return 0;
    }

    static DWORD CALLBACK CopyProgressRoutine(
        __in      LARGE_INTEGER TotalFileSize,
        __in      LARGE_INTEGER TotalBytesTransferred,
        __in      LARGE_INTEGER StreamSize,
        __in      LARGE_INTEGER StreamBytesTransferred,
        __in      DWORD dwStreamNumber,
        __in      DWORD dwCallbackReason,
        __in      HANDLE /*hSourceFile*/,
        __in      HANDLE /*hDestinationFile*/,
        __in_opt  CopyDlg* p
        )
    {
        switch(dwCallbackReason)
        {
        case CALLBACK_CHUNK_FINISHED:
        case CALLBACK_STREAM_SWITCH:

            if (ProgressData* data = new ProgressData)
            {
                data->TotalFileSize = TotalFileSize;
                data->TotalBytesTransferred = TotalBytesTransferred;
                data->StreamSize = StreamSize;
                data->StreamBytesTransferred = StreamBytesTransferred;
                data->dwStreamNumber = dwStreamNumber;

                if (!PostMessage(p->m_hwnd, WM_PROGRESS, dwCallbackReason, (LPARAM)data))
                {
                    delete data;
                    return PROGRESS_CANCEL;
                }
            }
            break;
        }

        // for debugging
        //Sleep(3000);
        //MessageBoxW(0,0,0,0);
        return PROGRESS_CONTINUE;
    }
};

if (CopyDlg* p = new CopyDlg)
{
    DialogBoxParamW((HINSTANCE)&__ImageBase, MAKEINTRESOURCE(IDD_DIALOG1), HWND_DESKTOP, CopyDlg::_DialogProc, (LPARAM)p);
    p->Release();
}