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

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によるリソース切り替えが実現できます。

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)のシグネチャを持つ関数を呼び出すことができます。サンプルはこちら。

2009年6月7日日曜日

Visual Studio 2010 beta1 C++でのinclude

なんでもVC++が進化しているそうなので試してみたく。
プログラムを書こうとして#include <...>まで書いて気がついた。はてさて、header fileのdirectoryはどう設定するの?

Visual Studio 2008までは ツール - オプション - プロジェクトおよびソリューション - VC++ディレクトリ という設定項目があり、そこにディレクトリを入力できた。が、2010 beta1ではそこがなくなっている。
もちろんプロジェクトのプロパティに設定してもコンパイルできることにはできるが、システムにインストールしたheader fileのdirectoryをプロジェクトごとに書くのはおかしい。

プロジェクトファイルを参照したところ答えがあった。

<ImportGroup Label="PropertySheets">
<Import Project="$(LocalAppData)\Microsoft\VisualStudio\10.0\Microsoft.Cpp.$(Platform).user.props"
Condition="exists('$(LocalAppData)\Microsoft\VisualStudio\10.0\Microsoft.Cpp.$(Platform).user.props')" />
</ImportGroup>
つまり$(LocalAppData)\Microsoft\VisualStudio\10.0\Microsoft.Cpp.$(Platform).user.propsに書けば取り込まれます。