シェルによるファイルコピー

シェルによるファイルコピーで嵌ったのでメモ.

Win32 API でファイルのコピーを行う方法として,CopyFile() の他にシェルを利用すると言うものがあります.今回 CopyFile() を使用せずにシェルの機能を利用した理由は,「コピーに長い時間を要する際にプログレスバーを表示してくれるから」と言うものでした.

シェルによるコピー自体は,SHFILEOPSTRUCT 構造体に情報を設定して SHFileOperation() を実行するだけなのですが,最初にこれを実行したとき何故かエラーが発生してうまくいきませんでした.何故かなぁと思ってリファレンスを読んでいると,以下の一文を見つけました.

Important You must ensure that the source and destination paths are double-null terminated. A normal string ends in just a single null character. If you pass that value in either the source or destination members, the function will not realize when it has reached the end of the string and will continue to read on in memory until it comes to a random double null value. This can at least lead to a buffer overrun, and possibly the unintended deletion of unrelated data.

// WRONG
LPTSTR pszSource = L"C:\\Windows\\*";

// RIGHT
LPTSTR pszSource = L"C:\\Windows\\*\0";

To account for the two terminating null characters, be sure to create buffers large enough to hold MAX_PATH (which normally includes the single terminating null character) plus 1.

http://msdn.microsoft.com/en-us/library/bb759795.aspx

よく分からないですが,SHFILEOPSTRUCT 構造体の pFrom/pTo に指定する文字列は 2連続 NULL 文字で終端していないとダメだそうです.そんな訳で,シェルによるファイルコピーは以下のような感じになります.

#include <string>
#include <vector>
#include <windows.h>
#include <tchar.h>

bool shell_copy(const std::basic_string<TCHAR>& src, const std::basic_string<TCHAR>& dest) {
    // NOTE: pFrom/pTo に指定する文字列は 2連続 NULL 文字で終端していなければならない.
    std::vector<TCHAR> src_buffer(src.begin(), src.end());
    src_buffer.push_back(0);
    src_buffer.push_back(0);
    
    std::vector<TCHAR> dest_buffer(dest.begin(), dest.end());
    dest_buffer.push_back(0);
    dest_buffer.push_back(0);
    
    SHFILEOPSTRUCT op = {};
    op.hwnd   = NULL;
    op.wFunc  = FO_COPY; // 他に,FO_DELETE, FO_MOVE, FO_RENAME が存在する.
    op.pFrom  = reinterpret_cast<const TCHAR*>(&src_buffer[0]);
    op.pTo    = reinterpret_cast<const TCHAR*>(&dest_buffer[0]);
    op.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI; // 必要に応じて変える.
    
    if (SHFileOperation(&op)) return false;
    return true;
}