読者です 読者をやめる 読者になる 読者になる

syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

SquirrelスクリプトをWindowsストア アプリで使う

前回LuaスクリプトをWindowsストア アプリで使うという記事を書きましたが、それのSquirrel版です。本体はSquirrel Ver.3.0.6C++バインダーには2014年現時点で最も完成度が高く導入しやすいと思われるSqrat Ver.0.9を使いました*1Lua同様Squirrelも若干ストア アプリ向けに修正を加えています。ビルドにはVisual Studio 2012が必要となります。Visual Studio 2013でWin8.1向けにビルドする場合、プロジェクトをアップグレードするだけでいけると思いますが、試してはいません。

Luaは内部文字列処理には常にcharを使うようになっており、デフォルトではテキストエンコードUTF-8しか使えません。一方でSquirrelコンパイルオプションにより、VM内部で使用する文字・文字列をワイド文字・文字列つまりwchar_tにすることができます(いわゆるUnicode対応)。Windows OS内部で使われているのはwchar_tを使ったUTF-16エンコードであり、またGUIとの境界でテキストデータをやりとりするにはwchar_tを使う必要があるので、Windows環境と相性が良いのはLuaではなくSquirrelでしょう。また、SquirrelLuaの実行速度には及ばないもののスクリプト言語としてはかなり高速な部類で、また文法もPascal風のLuaとは違ってC/C++寄りで扱いやすいため、Windowsストア アプリ向けにC++/CXでの開発を補助する目的で使うならばSquirrelをお勧めします。

https://sygh-jp.github.io/content_hosting/my_program_ss/StoreAppSquirrelTest1_ss_2014_06_09a.png

コードの二次利用は自由ですが、Squirrel, Sqratそれぞれのライセンスに従ってください。また、コードの利用は自己責任でお願いします。仮に損害が発生したとしても、当方は一切の責任を負いません。
なお、とるに足らないテスト用アプリではありますが、コレをこのままストアに提出することは絶対にやめてください。コレはあくまでサンプル コードです。そのまま提出してもたぶん審査で落ちるだけだと思いますが、もしコレをベースにして似たようなアプリを作成する場合、必ず何らかの独自機能をきちんと付加した上で、Package.appxmanifestのPublisherとPublisherDisplayNameをきちんと更新してください。この約束が守られていないアプリを発見した場合、しかるべきところに通報させていただきます。

Windowsストア アプリにてスクリプト言語を使う際の注意点に関しては、前回の記事に記載しています。

余談:C/C++におけるswprintf関数の仕様に関して

C/C++には書式化に対応した文字列出力関数sprintf/swprintfがあるんですが、実は古くから存在するPOSIX版と、ISO C/C++版、そしてMSが提唱しているSecure CRT版の3つが存在します。
関数シグネチャは下記のようになっています。

// [A1] レガシーな POSIX 関数。超危険。
sprintf(char* buffer, const char* format, ...);

// [A2] MS Secure CRT 版。出力先バッファのサイズ(配列要素数)を受け取る。_snprintf() 同様に比較的安全。
sprintf_s(char* buffer, size_t count, const char* format, ...);

// [W1] レガシーな POSIX 関数。超危険。
swprintf(wchar_t* buffer, const wchar_t* format, ...);

// [W2] ISO C/C++ 標準対応。出力先バッファのサイズ(配列要素数)を受け取る。_snwprintf() 同様に比較的安全。
swprintf(wchar_t* buffer, size_t count, const wchar_t* format, ...);

// [W3] MS Secure CRT 版。出力先バッファのサイズ(配列要素数)を受け取る。_snwprintf() 同様に比較的安全。
swprintf_s(wchar_t* buffer, size_t count, const wchar_t* format, ...);

ここで問題なのは[A1]と[W1]で、これらは出力先のバッファサイズを渡すパラメータがないため、本質的にバッファオーバーランを防止することができないという致命的なセキュリティ欠陥を抱えています。危険度はgets並。比較的新しいVisual C++(VC2005すなわちVC8以降)ではC++言語のソースとしてコンパイルする場合、関数オーバーロードによって[W1]と[W2]の両方、そして[W3]が使えるようになります。ですが、C言語のソースとしてコンパイルする場合、[W1]は使えず、[W2]と[W3]のみになります*2
で、Squirrelは内部でsprintfとswprintfを使っているんですが、おまけとして付属しているコマンドラインSquirrel Interactive「sq.exe」のソースsq.cをVC8以降の新しいコンパイラC言語ソースとして_UNICODEを定義してコンパイルすると、C4047とC4024という致命的なミスを示唆する警告が発生します(シグネチャの不一致)。実はSquirrelの開発者が動作確認しているコンパイラには古いVisual C++ 6.0, 7.0, 7.1が含まれており、つまりswprintfのインターフェイスとして想定しているのは新しい[W2]ではなく古い[W1]のほうらしく、そのためVC8以降の新しいコンパイラにおけるC言語では第2引数size_tにconst wchar_t*を無理矢理渡すというコンパイル結果になってしまいます。C言語は型チェックが甘すぎるレガシー言語なので、型が一致しなくてもコンパイルが通ってしまい、結果として実行時にメモリー破壊エラーを発端とする不可解なバグが発覚するという世にも恐ろしい結末が待っているのですが、今回のサンプルを作成するにあたって、sq.cは拡張子を変えてsq.cppとし、C言語ではなくC++言語のソースとしてコンパイルすることで暫定対処することにしました。本来であればsprintf/swprintfではなく、最初からsprintf_s/swprintf_sを使うようにすべてのソースコードを修正するべきなのですが、面倒なのでやめました。いずれSquirrel開発者のAlberto Demichelis氏に、新しいVC++環境ではSecure CRTを、最悪の場合でもせめて_snprintf()/_snwprintf()を使うようなコードを実装するようリクエストしておこうと思います。[A1], [W1]はもちろん[W2]も(まぎらわしいので)今後は使うべきではありません。ていうかVC8よりも古いコンパイラはもう捨てちゃってもいいと思います。

なお、Embarcadero RAD Studioの最新版(旧Borland C++ Builder)では、MSVC同様にSecure CRT関数を実装しているようです。すばらしいことだと思います。
sprintf_s、swprintf_s - RAD Studio

ちなみにC言語というのはこういった多数の致命的問題がある欠陥言語なので、自分はどんなことがあってもC言語自体はもう二度と使わないと思います*3C++ではこのようなCの欠陥をほぼ克服しているため、個人的にはC言語よりもC++をお勧めします。たとえオブジェクト指向の初心者であっても、Cではなく最初からC++を使いましょう。

※2017-03-26追記:
Squirrel 3.1を試してみたところ、_WIN32が定義されているとき(つまりWindows環境)は、swprintfではなく_snwprintfが使われるように修正されているようです。gccはISO準拠らしいので気にする必要がないようなのですが、_WIN32ではなく_MSC_VERの有無で判断するべきでは、と思います。

*1:実際に使用したのはSqrat 0.8.92ですが、0.9のツリーに入っているようです。

*2:古いPOSIX関数のほうをどうしても使いたければ、_CRT_NON_CONFORMING_SWPRINTFSを定義すればよいらしいですが、よほどのことがないかぎり使わないほうがよいでしょう。

*3:マイクロソフトVC++においてC言語規格の対応を放置しがちなのですが、C言語はすでに実務用途としては使いものにならない旧世紀の遺物(つまり投資対象外)として半ば見捨てているのではないでしょうか。自分もC言語というのはもはやアマチュアはおろかプロフェッショナルですらも使うべきではない言語だと思いますが、だったらマイクロソフトデバイスドライバー開発の第1級言語にCではなくC++を使えるようにしろよ、とも言いたいです。