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などを順次紹介していきたいと思います。

2017年2月5日日曜日

cross browserでのタッチ操作

Webサイトをタッチ操作に対応させようとしたところ、非常に苦労したため、得られた成果をまとめておこうと思います。
目指すゴールは手持ちのデバイスでドラッグ・スライドを実現することです。Webでは変化が目まぐるしいのでこの記事が対象とする現時点での環境を挙げておきます。

  • SurfacePro / Windows 10 / mouse, touch, pen
    • Internet Explorer 11
    • Edge 14
    • Chrome 56
    • Firefox 51
  • MacBook Air / OS X / mouse
    • Safari
    • Chrome 56
  • iPhone SE / iOS 10.2 / touch
    • Safari
  • Xperia / Android 4.1.2 / touch, mouse
    • Chrome
    • Firefox
  • Fire HD 8 / Fire OS 5.3.2.1 / touch, mouse
    • Chrome
    • Firefox

ブラウザーは従来よりMouse Eventsを提供していましたが、最近はmouse・touch・pen等を統一的に扱えるPointer Eventsが提供されています。ただし対応状況としてはIE11 / Edge / Chromeが対応、Firefox / Safariが未対応のようです。これとは別にTouch Eventsが提供されていますが、こちらはChrome / iOS / Androidが対応、IE11 / Edge / Firefoxが未対応のようです。総合すると
  1. Pointer Eventsに対応ならPointer Eventsのみ
  2. Pointer Eventsに未対応でTouch Eventsに対応ならTouch EventsとMouse Eventsの両方
  3. Pointer EventsとTouch Eventsに未対応ならMouse Eventsのみ
と分岐しつつ複数を併用する必要がありそうです。

さてイベントの登録・削除は従来はIEに制限がありましたがIE9以降はaddEventListenerのみで実現できます。しばしば第3引数useCaptureにfalseが渡されますが、対応していなかったのはFirefox 6未満のようですので省略して構わないでしょう。

タッチ操作の場合、ページのスクロールなどブラウザー側の処理と競合します。この問題についてはtouch-actionというCSSプロパティが用意されています。対応状況としてはIE11 / Edge / Chromeが対応、Firefoxが次バージョンで対応、Safariが一部のみ対応ということで、これとは別の対策も必要です。実のところSafariではpreventDefault()メソッドを使って抑止することになっています。

ブラウザー側の処理にはこれ以外もあり、Pointer EventsやTouch Eventsが処理されなかった場合は互換のためにMouse Eventsが呼ばれることになっています。これらについてもpreventDefault()メソッドで抑止できることになっています。

以上を踏まえて書いたコードがこちら

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年7月9日土曜日

Visual C++ CoroutineとBoost Coroutine

以前にBoost.Asioを使ってみたんですが、その時は非同期機能を使っていませんでした。非同期機能の場合、完了のコールバックを処理する必要がありますが、そのコーディングはどうしても煩雑になってしまいます。
この点に関してC#言語ではVS2013でasync機能によるサポートが行われるようになりました。この機能は非同期呼び出し時のコンテキストをコンパイラーが保持しておき、完了コールバック時にコンテキストを復元することで一続きの関数のように処理するものです。この機能は一般的にはCoroutine; コルーチンと呼ばれるようです。

実はC++言語でもコルーチンを標準に導入するべく検討がされているらしく、VS2015からサポートされています。VS2015 Update1からはプラットフォームを選ばず機能提供されています。それとは別にBoostライブラリでもBoost.Coroutineによるコルーチンが提供されています。こちらはコンパイラー側のサポートなしにアセンブラでスタックを強引に書き換えることで実現されているらしいです(詳しくはわかりません)。その影響でWindowsプラットフォームではBoost.Coroutineを使うためには/SAFESEH:NOオプションを付け安全な例外ハンドラーが存在しない旨を宣言する必要があります。

そこで、C++言語ネイティブなコルーチンが提供される環境ではそちらを、提供されない環境ではBoost.Coroutineに切り替え、ソースコードを共通化できるライブラリを用意してみました。


これを使うとcoroutine::result<Result> func(..., coroutine::handler handler)のシグネチャを持つ関数を呼び出すことができます。サンプルはこちら。

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は異なる関数として扱うためテンプレート展開に失敗するためです。