ffftpのメンテナンスを引き継ぎました。github:sayurin/ffftpで公開しています。メンテナンス方針としてはGUIを大きく変えることはしません。最新のVisual Studioを使用し、動作環境をWindows XP以降としています。この環境で利用できるC++17やWindows APIを使用してコードをリファクタリングしていきます。また外部ライブラリの利用を排し、Windowsが提供する機能を使用します。これによりセキュリティアップデートをWindows Updateに任せることができます。
不具合を見つけた際には github:sayurin/ffftp/issues まで報告いただけたら幸いです。
このブログでは、ffftpで使用しているC++機能やWindows APIなどを順次紹介していきたいと思います。
2018年4月11日水曜日
ffftpのメンテナンスを引き継ぎました
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
- Pointer Eventsに対応ならPointer Eventsのみ
- Pointer Eventsに未対応でTouch Eventsに対応ならTouch EventsとMouse Eventsの両方
- Pointer EventsとTouch Eventsに未対応ならMouse Eventsのみ
タッチ操作の場合、ページのスクロールなどブラウザー側の処理と競合します。この問題についてはtouch-actionというCSSプロパティが用意されています。対応状況としてはIE11 / Edge / Chromeが対応、Firefoxが次バージョンで対応、Safariが一部のみ対応ということで、これとは別の対策も必要です。実のところSafariではpreventDefault()メソッドを使って抑止することになっています。
ブラウザー側の処理にはこれ以外もあり、Pointer EventsやTouch Eventsが処理されなかった場合は互換のためにMouse Eventsが呼ばれることになっています。これらについてもpreventDefault()メソッドで抑止できることになっています。
ラベル: html, JavaScript
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(装飾名)となってしまうことです。これは呼び出し規約において呼び出し先関数が引数のスタックを復元するため、復元するサイズを呼び出し元に通知する必要があるからです。
そこでこの問題を解消する別の解決策を模索します。
- モジュール定義ファイルにおいてはエクスポートする関数名を明示することができます。
- リンカーオプション/EXPORTはモジュール定義ファイルとほぼ同様の書式で、ドキュメントにはありませんが同じ書式を使用してエクスポートする関数名を明示できます。
- #pragma commentを使用するとリンカーオプション/EXPORTをソースコード内で指定できます。
- 定義済みマクロ__FUNCTION__と__FUNCDNAME__を使用すると関数名と装飾名が得られます。
以上を総合すると
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)のシグネチャを持つ関数を呼び出すことができます。サンプルはこちら。
ラベル: C++, VisualStudio
2016年4月19日火曜日
Windowsの各種Timerの精度について
WindowsはいくつかのTimerを提供しています。
- Multimedia Timer
- Timer Queue
- Threadpool Timer
- 同期オブジェクトを使用するWaitable Timer
- メッセージループを使用するMessage Timer
- Message Timerは分解能が16msなので精度はよくない。
- Multimedia Timerは廃止済みでTimer Queueを推奨している。
- Multimedia TimerはtimeBeginPeriodを呼ぶことで精度が向上する。
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このコードはVisual Studio 2015 Update1のC++コンパイラーにて正常動作するとともにIDEのIntelliSenseでも正しく解釈されることを確認しています。 ただし、x64についてはIntelliSenseの問題により解釈できないようです。原因はx64では__cdeclと__stdcallの呼び出し規約が同一視されるにもかかわらず、IntelliSenseは異なる関数として扱うためテンプレート展開に失敗するためです。(args)..., &result)); return result; } #define GET(OBJECT, METHOD, ...) get(&std::remove_reference_t<decltype(OBJECT)>::METHOD, OBJECT, __VA_ARGS__)
2014年12月24日水曜日
F#における非同期Socket
.NET FrameworkにはVersion 1.0からSocketクラスがあり、APM; Asynchronous Programming Modelと呼ばれるBegin / End系メソッドが用意されています。しかし実際にはIOの度にIAsyncResultオブジェクトを作成する必要があり、ハイパフォーマンスなアプリケーションは実現しづらいものでした。
そのため、Version 2.0 SP1にてSocketAsyncEventArgsクラス及びAsync系のメソッドが新規に追加されました。こちらは内部状態を持つSocketAsyncEventArgsクラスを再利用することで効率の良い非同期処理が行えるものとなっています。なお、Version 4.5で導入された非同期処理とメソッド名の命名規則が一致していますが全くの別物となっています。
これをF#で扱えないものかと検索したところF#-friendly SocketAsyncEventArgsを見つけました。ただし、残念なことにSocketAsyncEventArgsクラスの設計思想を意識されておらず、毎回SocketAsyncEventArgsオブジェクトを再作成するだけの単なるwrapperでしかありませんでした。さらに言えばF#には非同期ワークフローもありますからこちらも利用したいところです。
仕方がないので自作してみました。
書いただけでまだ使っていないので動くかわかりません。
蛇足ですが、Socketクラスは内部でWinsockを使っていますが、このWinsockの機能の一つにaccept()で接続を受け付けると同時にrecv()を行うことができます。また対称にconnect()と同時にsend()もできます。こうすることでHTTPなど一般的なプロトコルでリクエストの送受信ができ、システムコール回数を減らし、システムの応答性能が向上します。Socketクラスはこの機能に対応しているため、今回の拡張メソッドにも含めています。
