ラベル Win32 の投稿を表示しています。 すべての投稿を表示
ラベル Win32 の投稿を表示しています。 すべての投稿を表示

2018年4月11日水曜日

MUIによるアプリケーションの国際化

ffftpで使われている技術、第一弾はMUIによるアプリケーションの国際化です。
Windowsではリソースファイルによる国際化が提供されていました。しかし、リソースファイルには単に言語情報が付与されているだけで切り替え機能が提供されていないため、言語切り替えはアプリケーションの責任となっていました。
しかし、Windows VistaからMUI; Multilingual User Interfaceというリソース切り替え機構が導入されました。MUIを適切に構成すると、LoadLibraryでリソースを読み込むだけで適切な言語のリソースを開くことができます。またMUI未対応環境のために互換APIとしてLoadMUILibraryが提供されています。

ffftpでは github:sayurin/ffftp/blob/v3.0/main.cpp#L278-L287 のようにWindows Vista未満を対象とするビルドに対しては、hInstanceをLoadMUILibraryで得られたリソースハンドルに置き換えています。Windows Vista以降を対象とする場合、そもそもWinMainで受け取るhInstanceはリソース切り替え済みのハンドルとなっているためコードは一切必要ありません。
なお、LoadMUILibraryはmuiload.h及びmuiload.libで提供されるため静的リンクしています。ここでmuiload.libはVisual C++ 2013以前を対象にビルドされているためVisaul C++ 2015以降で使用するためにはlegacy_stdio_definitions.libも併せてリンクする必要があります。

リソースは通常RC; Resource Compilerを使用してコンパイルしますし、Visual Studioではビルドの一環として組み込まれています。しかしMUI対応リソースはMUIRCTを使ってコンパイルする必要があり、Visual Studioのビルド手順には組み込まれていません。
そこでffftpではMuiResourceCompile.targetsを用意しました。単にビルド手順に含めるだけでなく次のようにプロパティページも表示することができます。


(まだ改良の余地はありますが、それはそれ…。)
ともあれ、これでビルドを行うと
  • ffftp.exe
  • en-US\ffftp.exe.mui
  • ja-JP\ffftp.exe.mui
が得られます。これらを配置することでMUIによるリソース切り替えが実現できます。

ffftpのメンテナンスを引き継ぎました

ffftpのメンテナンスを引き継ぎました。github:sayurin/ffftpで公開しています。メンテナンス方針としてはGUIを大きく変えることはしません。最新のVisual Studioを使用し、動作環境をWindows XP以降としています。この環境で利用できるC++17やWindows APIを使用してコードをリファクタリングしていきます。また外部ライブラリの利用を排し、Windowsが提供する機能を使用します。これによりセキュリティアップデートをWindows Updateに任せることができます。
不具合を見つけた際には github:sayurin/ffftp/issues まで報告いただけたら幸いです。

このブログでは、ffftpで使用しているC++機能やWindows APIなどを順次紹介していきたいと思います。

2016年8月21日日曜日

DLLで関数のexport

今更な話題かもしれませんが、あまり知られていないと思うので記事にしておきます。サンプルコードとして

#include <Windows.h> BOOL APIENTRY DllMain(HMODULE, DWORD, LPVOID) { return TRUE; } extern "C" int __stdcall Add(int a, int b) { return a + b; }

でAdd関数をエクスポートする場合を考えます。

DLLで関数をエクスポートする方法の中でもっともよく使われる方法はモジュール定義(.def)ファイルです。

LIBRARY EXPORTS Add

で作成したファイルをリンカーオプション/DEFで指定します。
この方法の問題点は、定義が分かれてしまい、ソースコードからはどの関数がエクスポートされるか判別できないことです。

そこでソースコード内にエクスポートするかどうかを埋め込む方法が提供されています。__declspec(dllexport)を指定する方法です。

extern "C" __declspec(dllexport) int __stdcall Add(int a, int b) { return a + b; }

__declspec(dllexport)をextern "C"の後~__stdcallの前の辺りに挿入します。
この方法の問題点は、x86においてエクスポートされる関数名が_Add@8(装飾名)となってしまうことです。これは呼び出し規約において呼び出し先関数が引数のスタックを復元するため、復元するサイズを呼び出し元に通知する必要があるからです。

そこでこの問題を解消する別の解決策を模索します。

以上を総合すると

extern "C" int __stdcall Add(int a, int b) { #pragma comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__) return a + b; }

これでモジュール定義ファイルを用いることなく、ソースコード内での記述で、関数をエクスポートすることができまし…たんですが、IntelliSenseがexpected a ')'と警告します。余計なお世話ですがこれも解消しましょう。

  • #pragmaはマクロ内で使えないが__pragma()であればマクロで使える。
  • IntelliSenseは__EDG__を定義するがコンパイラーは定義しないのでコード分岐できる。

以上を踏まえて最終的に

// ヘッダーなどに #ifdef __EDG__ #define DLLEXPORT #else #define DLLEXPORT __pragma(comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)) #endif // 関数内に extern "C" int __stdcall Add(int a, int b) { DLLEXPORT; return a + b; }

と書くことでスマートに記述できるようになりました。

2016年4月19日火曜日

Windowsの各種Timerの精度について

WindowsはいくつかのTimerを提供しています。

があり、それぞれに特徴があります。
などはよく知られていると思います。しかし実際どうなっているのか気になって調べたところOn WinAPI timers and their resolutionという比較記事を見つけました。しかしOSバージョンが明示されていないなど疑問は解消しなかったため、自分で比較してみることにしました。

2016年2月2日火曜日

C++からWindows APIを呼び易くする

Windows APIの多くはC言語を前提としています。次のように戻り値がHRESULTなどのエラーコードとなり、真の戻り値は関数の最後の引数にポインターとして返される構造をしているものが多々あります。

HRESULT Direct3DCreate9Ex( UINT SDKVersion, IDirect3D9EX **ppD3D );
これをC++言語から扱いやすくしたいと思います。
一般的には次のようなcheck()関数で異常値については例外を投げることになるでしょう。
void check(HRESULT hr){ if(FAILED(hr)) throw hr; }
本題は真の戻り値です。
API関数には任意の引数があるため最後の引数を扱うのは困難です。幸いC++言語には可変長テンプレート引数があり、それをうまく扱うstd::tupleクラスとstd::tuple_elementクラスがあります。次のようなlastクラスを定義できます。
template<class... Args> struct last : std::tuple_element<sizeof...(Args)-1, std::tuple<Args...>> {};
これは例えば int, double, std::string のような型リストがあった場合にまずはstd::tuple<int, double, std::string>型を作り、これに対して最後の型を取り出します。その結果、last<int, double, std::string>::typeはstd::stringに展開されます。
ここまでくれば関数については簡単に書けるかもしれません。テンプレートと特殊化を使います。
template<class Func> struct last_argument; template<class Ret, class... Args> struct last_argument<Ret(Args...)> : last<Args...> {};
これで例えば last_argument<Direct3DCreate9Ex>::typeはlast<UINT, IDirect3D9EX**>::typeに展開され最終的にIDirect3D9EX**が得られます。
ところが、Visual C++には様々な呼び出し規約があるため、このままではデフォルトの呼び出し規約の関数にしか対応できていません。
幸いVisual C++自身もこの問題に直面していてこれを解決するマクロを使用しているため、ここではそれを流用します。ついでにメンバー関数にも対応しておきます。
template<class Func> struct last_argument; #define LAST_ARGUMENT(CALL_OPT) \ template<class Ret, class... Args> struct last_argument<Ret CALL_OPT(Args...)> : last<Args...> {}; \ template<class Ret, class... Args> struct last_argument<Ret (CALL_OPT*)(Args...)> : last<Args...> {}; _NON_MEMBER_CALL(LAST_ARGUMENT) #undef LAST_ARGUMENT #define LAST_ARGUMENT(CALL_OPT, CV_OPT, REF_OPT) \ template<class Class, class Ret, class... Args> struct last_argument<Ret(CALL_OPT Class::*)(Args...) CV_OPT REF_OPT> : last<Args...> {}; _MEMBER_CALL_CV_REF(LAST_ARGUMENT) #undef LAST_ARGUMENT
さてこれらを使った関数を用意します。std::invoke()を使うと関数呼び出しが簡単に表現できます。
template<class Func, class... Args, class Result = std::remove_pointer_t<last_argument<Func>::type>> auto get(Func func, Args&&... args) { Result result; check(std::invoke(func, std::forward(args)..., &result)); return result; } #define GET(OBJECT, METHOD, ...) get(&std::remove_reference_t<decltype(OBJECT)>::METHOD, OBJECT, __VA_ARGS__)
以上を使うと
HRESULT hr; IDirect3D9EX* d3d; hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &d3d); if (FAILED(hr)) throw hr; IDirect3DDevice9Ex* d3device; hr = d3d->CreateDeviceEx(引数, いろ, いろ, &d3device); if (FAILED(hr)) throw hr;
と書いていたものが
auto d3d = get(Direct3DCreate9Ex, D3D_SDK_VERSION); auto d3device = GET(d3d, CreateDeviceEx, 引数, いろ, いろ);
と書けるようになりました。
改めてまとめると
void check(HRESULT hr){ if(FAILED(hr)) throw hr; } namespace details { template<class... Args> struct last : std::tuple_element<sizeof...(Args)-1, std::tuple<Args...>> {}; template<class Func> struct last_argument; #define LAST_ARGUMENT(CALL_OPT) \ template<class Ret, class... Args> struct last_argument<Ret CALL_OPT(Args...)> : last<Args...> {}; \ template<class Ret, class... Args> struct last_argument<Ret (CALL_OPT*)(Args...)> : last<Args...> {}; _NON_MEMBER_CALL(LAST_ARGUMENT) #undef LAST_ARGUMENT #define LAST_ARGUMENT(CALL_OPT, CV_OPT, REF_OPT) \ template<class Class, class Ret, class... Args> struct last_argument<Ret(CALL_OPT Class::*)(Args...) CV_OPT REF_OPT> : last<Args...> {}; _MEMBER_CALL_CV_REF(LAST_ARGUMENT) #undef LAST_ARGUMENT } template<class Func, class... Args, class Result = std::remove_pointer_t<details::last_argument<Func>::type>> auto get(Func func, Args&&... args) { Result result; check(std::invoke(func, std::forward(args)..., &result)); return result; } #define GET(OBJECT, METHOD, ...) get(&std::remove_reference_t<decltype(OBJECT)>::METHOD, OBJECT, __VA_ARGS__)
このコードはVisual Studio 2015 Update1のC++コンパイラーにて正常動作するとともにIDEのIntelliSenseでも正しく解釈されることを確認しています。 ただし、x64についてはIntelliSenseの問題により解釈できないようです。原因はx64では__cdeclと__stdcallの呼び出し規約が同一視されるにもかかわらず、IntelliSenseは異なる関数として扱うためテンプレート展開に失敗するためです。

2009年3月29日日曜日

/NODEFAULTLIB:msvcmrt.lib

どこかでVS2008のC++/CLIだと.NET Frameworkを使用しているのにVC++ランタイムライブラリとの依存関係が切れなくて、結局VC++ランタイムライブラリもインストール必要になってしまう&x86とx64で共存できない、というような記述を見かけた…気がしました。
後になって探したのですがどこで見かけたのやら。

で、方法 : CRT ライブラリ DLL との依存関係を削除して部分信頼アプリケーションを作成するによると/NODEFAULTLIB:msvcmrt.libを指定することにより依存関係を切れるとのこと。ただしこれでは必要な処理が足りていないのでptrustm.libかptrustmd.libのどちらかとリンクすること。

えっとこの.libによってx86かx64のどちらかに固定されてしまうのか。でも.NET Frameworkだけで動作できるようになる、VC++ランタイムライブラリのインストール不要となるならそれはそれで大きいかな。

あ、ちなみに結果がどうなるか試してません。

2008年11月4日火曜日

swprintf()とwsprintf()

Cランタイムライブラリにsprintf()があります。同様の機能を持つwsprintf()がUSER32.dllにもあります。実はsprintf()はntdll.dllにもあります。

これらの関係を整理すると

宣言DLLTCHAR.HのルーチンANSI版Unicode版
tchar.hMSVCRT90.dll_stprintf()sprintf()swprintf()
windows.hUSER32.dllwsprintf()wsprintfA()wsprintfW()
-ntdll.dll-sprintf()swprintf()
となります。

ちなみに注意点がいくつか。
swprintf()の第2引数はバッファサイズで書式指定文字列は第3引数にずれています。他のものは第2引数が書式指定文字列です。代わりに他のものと互換のある_swprintf()も第2引数が書式指定文字列になっています。
wsprintf()は指定できる書式に制限があります。例えば浮動小数(%f)がサポートされていません。
ntdll.dllに含まれるsprintf()は使用方法が公開されてなく、サポートされている機能も公開されていません。ただし、全てのプロセスにロードされるDLLなので何かの役に立つかも。

2008年11月3日月曜日

CallNextHookEx()のHHOOK引数

SetWindowsHookEx( WH_CBT, CBTProc, ... )でDLL injectionしています。このCBTProc()のドキュメントによると、正確な型宣言は

LRESULT CALLBACK CBTProc( int nCode, WPARAM wParam, LPARAM lParam );
です。そしてこのhookプロシージャの中ではnCodeが負のときはCallNextHookEx()を呼び更にその戻り値を返すこととあります。

さてCallNextHookEx()のドキュメントによると、正確な型宣言は
LRESULT CallNextHookEx( HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam );
です。ちなみに記述が矛盾していて、CallNextHookEx()の呼び出しはoptionalだが、呼び出すことを強く推奨するそうです。CBTProc()側では負の時呼べばいいのに、こう書かれてしまうと0または正の時にも呼ばざるを得なくなります。仕方がないので呼ぶことにしましょう。

問題はここからです。CallNextHookEx()の第一引数hhk。hookプロシージャは進入した各プロセスで実行されます。にもかかわらずCBTProc()には存在しないパラメータが要求されています。とても面倒くさいですが、SetWindowsHookEx()の戻り値を共有メモリに格納しておき、それを読み出して使っていました。

ところが今日ふと気がついたら、
Windows NT/XP/2003: Ignored.
などと書かれているではありませんか。うーん…今まで共有メモリにわざわざ準備していたhhkは無駄でしたか…。

2008年11月1日土曜日

ウィンドウプロシージャとメンバ関数その2

ウィンドウプロシージャとメンバ関数を書いてみたものの、1つのthreadに複数のWindow Procedureが混在する場合、うまく振り分けできませんね。
どうすればいいんだろう…? まぁ、複数Windowを扱ったことがないからいいけど…。

2008年10月26日日曜日

ウィンドウプロシージャとメンバ関数

コールバック関数の1つウィンドウプロシージャWindowProc()をクラスメンバ関数にしたいという話。
調べたところ、ATLはlinked listでクラスインスタンスを管理しておき、atlbase.inlファイルのAtlWinModuleExtractCreateWndData()関数でthread idをキーにして検索をしていました。これウィンドウメッセージが届くたびにやってる気がします。
読んで勉強になったのは、CreateWindowを呼び出したスレッドにウィンドウメッセージが届くのね。だからthread idがキーとして使える、と。

なら1歩進めてthread local storageに情報を置いてしまえばいいのでは?

class Window{
LRESULT WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam,LPARAM lParam ){
// ここが本体
...;
}
static __declspec(thread) Window* window;
static LRESULT CALLBACK StaticWindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ){
return window->WindowProc( hwnd, uMsg, wParam, lParam );
}
void CreateWindowOnThisThread(){
Window::window = this;
CreateWindowEx( .../* 適切に */ );
}
};
Window* Window::window = NULL;

こうすれば無難な関数に仕上がりました。ちなみにStaticWindowProc()はかなり負担の少ない実装になっていました。
mov eax, DWORD PTR _lParam$[esp-4]
mov ecx, DWORD PTR _wParam$[esp-4]
mov edx, DWORD PTR fs:__tls_array
push ebx
mov ebx, DWORD PTR _hwnd$[esp]
push eax
mov eax, DWORD PTR _uMsg$[esp+4]
push ecx
mov ecx, DWORD PTR [edx]
mov ecx, DWORD PTR Window::window[ecx]
call Window::WindowProc
pop ebx
ret 16
; 一部構文に矛盾がありますが、読みやすさのためのデマングルですので…
呼び出し規約がCALLBACKから__thiscallに変わるので、無意味なスタックの積み直しが行われてますが、まぁ仕方がありませんね。

2008年8月31日日曜日

IEでのinherit

ググったけど話題になっていなかったので。CSSには親要素から値を継承するinherit値がありますが、実はIEではこれがあまり動作しません。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>test</title>
<style type="text/css">
body{ font-weight: normal; }
span{ font-weight: bold; }
span{ font-weight: inherit; }
</style>
</head>
<body>
<span>normalが継承される</span>
</body>
</html>
spanはbold指定を打ち消し、bodyからnormalを継承しなければなりませんが、IE7ではinherit行自体が指定されていなかったものと見なし、boldで表示されてしまいます。

ちなみにIE8 beta2で確認したところ、こちらは正しくnormalで表示されていました。

2008年5月16日金曜日

Boost.Asio

BoostというC++ライブラリが気になっていたのですが、1.35がリリースされ、Boost.Asioという非同期I/Oライブラリが追加されていることを知りました。このライブラリはWindows、Linux、各種Unix上で動作し、各種コンパイラをサポートしています。さらによく見たらOpenSSLによるSSLまでサポートしていました。

まずは試しに同期I/Oでhttps clientを書いてみました。

io_service i;
ssl::context c( i, ssl::context::sslv3_client );
ssl::stream s( i, c );
s.lowest_layer().connect( *ip::tcp::resolver(i)
.resolve( ip::tcp::resolver::query( "ホスト名", "https" ) ) );

s.handshake( ssl::stream_base::client );

write( s, buffer( "GET / HTTP/1.0\r\n\r\n" ) );

asio::streambuf res;
error_code e;
while( read( s, res, transfer_at_least(1), e ) )
cout << &res;
うーん、これだけで動いちゃいました。

2008年4月26日土曜日

Windows XP SP3

Windows XP SP3が公開されたようです。Release To Manufacturing(RTM)後、1週間ほどでRelease To Web(RTW)されるそうです。
SP2と互換は高いという噂なので特に言及することもなさそうでしたが…1点、すでにWindows Vistaがリリースされていることもあり、Windows XP with SP3は作られないとかなんとか。というわけで後からSP3を追加する形のみになるようです。…ちなみにSP3のインストール条件にSP1が入っていました。つまり素のXPには一旦SP1aかSP2を入れた後にSP3を入れることになるようです。

2008年3月29日土曜日

インサイドWindows第4版下

インサイドWindows第4版上に続いてようやくインサイドWindows第4版下を買えました。近くの本屋さんにはいつ見ても上しか置いてなくて…でも注文するのは負けた気がするし。
読んでいて一つ参考になった話題を。大容量のメモリを搭載しているWindows XPでもタスクマネージャのシステムキャッシュを見ると512MBで抑えられている場合があります。このPCは68MBとしょぼしょぼですが(;;
こういう場合、パフォーマンス オプションで「システム キャッシュ」を選択すると512MB以上のメモリをシステムキャッシュに割り当てられるようになります。メモリが余ってる人はどうぞ。
64bit版やWindows Vistaはこのリミッターが最初から解除されています。

HTTP Server API

以前からWindows OSにはIISが含まれていたけど、Windows XP SP2、Windows Server 2003以降にはOS側にWebサーバ機能が組み込まれていて、IISもこれを使う設計らしい。
試しにこのHTTP Server APIを使ってみた。

HTTPAPI_VERSION version = HTTPAPI_VERSION_1;
HttpInitialize( version, HTTP_INITIALIZE_SERVER, NULL );

HANDLE handle;
HttpCreateHttpHandle( &handle, NULL );

HttpAddUrl( handle, L"http://+:80/", NULL );

while( !stop ){
HTTP_REQUEST_ID requestId;
HTTP_SET_NULL_ID( &requestId );
char buffer[ 4096 ] = {};
PHTTP_REQUEST request = reinterpret_cast<PHTTP_REQUEST>( buffer );
ULONG size;
HttpReceiveHttpRequest( handle, requestId, 0, request, sizeof buffer, &size, NULL );

HTTP_RESPONSE response;
memset( &response, 0, sizeof response ); // デフォルトコンストラクタ用意してください(;;)
HTTP_DATA_CHUNK chunk;
HANDLE file = INVALID_HANDLE_VALUE;
if( request->Verb != HttpVerbGET )
response.StatusCode = 503;
else{
// URLパスの先頭の'/'をスキップ
file = CreateFileW( &request->CookedUrl.pAbsPath[1], FILE_READ_DATA, 0, NULL, OPEN_EXISTING, 0, NULL );
if( file == INVALID_HANDLE_VALUE )
response.StatusCode = 404;
else{
chunk.DataChunkType = HttpDataChunkFromFileHandle;
chunk.FromFileHandle.ByteRange.StartingOffset.QuadPart = 0;
chunk.FromFileHandle.ByteRange.Length.QuadPart = HTTP_BYTE_RANGE_TO_EOF;
chunk.FromFileHandle.FileHandle = file;
response.StatusCode = 200;
response.EntityChunkCount = 1;
response.pEntityChunks = &chunk;
}
}
HttpSendHttpResponse( handle, request->RequestId, 0, &response, NULL, &size, NULL, 0, NULL, NULL );
if( file != INVALID_HANDLE_VALUE )
CloseHandle( file );
}
HttpRemoveUrl( handle, L"http://+:80/" );
CloseHandle( handle );
HttpTerminate( HTTP_INITIALIZE_SERVER, NULL );
サンプルなのでエラー処理は省いてます。機能的には、カレントディレクトリのファイルを返します。
stop = trueとしてもすぐには終了しません。HttpReceiveHttpRequest()でブロックされているので、次のリクエストを処理したところでループを抜けます。

2008年3月2日日曜日

fwrite()と改行コード

fwrite()は任意のバイナリデータをstreamに書き込むライブラリ関数だと理解していましたが、今更ながらstreamがテキストモードの場合、任意文字列を書き込むものだと知りました。具体的にはバイナリデータを文字列と見なし、その中からNL(0x0A)をCR-LF(0x0D-0x0A)に置き換えられてはまりました。
テキストとバイナリを混在させたstreamの場合、その都度モードを切り替える必要があるそうです。

FILE* stream;
_setmode( _fileno( stream ), _O_BINARY );

2008年2月27日水曜日

WPADのContent-Type

Web Proxy Auto-Discovery ProtocolいわゆるWPADですが、なんとWindows Update ClientはContent-Typeをちゃんと見ていました。application/x-ns-proxy-autoconfigに設定されていないと、無効と見なされてしまうようです。
これ気づいてる人は少ないようで、proxycfg.exeに頼ってる記述が多々ありました。

2008年2月9日土曜日

ハードディスクの問題が検出されました

時々、OSからハードディスクが認識されなくて、先週からは起動時のBIOSからも認識されなくなって、やばいと思っていました。週末ということもあり、バックアップして徹底的にチェック! …まずはバックアップを~こんなダイアログが出ました。Windows VistaってSelf-Monitoring, Analysis and Reporting Technology、いわゆるS.M.A.R.T.に対応してたのね。
ググると遭遇している方が結構いらっしゃるようです。でも…この警告を無視するコメントが多数…う~ん、無理に使ってデータが壊れても自己責任ですよ。

Windows Vista SP1 RTM

2/4にWindows Server 2008とWindows Vista SP1のRelease To Manufacturing、RTMがアナウンスされました。それと同時にMSDNでWindows Server 2008の提供も開始されましたが、Vista SP1に関しては3月中旬に公開されるそうです。
…と、これでは寂しいのでVista SP1のRTM版を入手してインストールしました。普通にインストールできました。ハイ、それだけです。

robocopy

ファイルを多数コピーしたくなりました。Explorerでは遅いからここはxcopyコマンドだと思い、まずはコマンドヘルプを…

C:\Users\sayuri>xcopy /?
ファイルとディレクトリ ツリーをコピーします。

注意: Xcopy は現在推奨されていません。Robocopy を使用してください。

XCOPY 送り側 [受け側] [/A | /M] [/D[:日付]] [/P] [/S [/E]] [/V] [/W]
[/C] [/I] [/Q] [/F] [/L] [/G] [/H] [/R] [/T] [/U]
[/K] [/N] [/O] [/X] [/Y] [/-Y] [/Z] [/B]
[/EXCLUDE:ファイル1[+ファイル2][+ファイル3]...]
…ん?
C:\Users\sayuri>robocopy /?

-------------------------------------------------------------------------------
ROBOCOPY :: Windows の堅牢性の高いファイル コピー

-------------------------------------------------------------------------------

これはバックアップとか便利に使えそうなコマンドのようです。