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

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

Windows 10の画像ファイル右クリックメニューに「プレビュー」を追加

無償アップグレードの期限ギリギリになりましたが、ついにWindows 8.1からWindows 10にアップグレードしました。Windows 10は昨年のプレビュー版のときにDirectX 12 APIを少しテストした際に、全体的な完成度の低さが目についたため、それ以来一切触っていませんでしたが、2015年7月のリリースから1年を経てそれなりにこなれてきたようなので、まず念のためWindows 8.1のイメージバックアップを取ってからWindows 10に移行しました。しばらく使い込んでみて、ダメだったら8.1に戻す予定です。いずれはWindows 10に誰しも移行せざるをえませんが、自分の場合現時点でよく使うクリエイティブ系ツールやゲームが正常に動かなければ意味がありません。

Windowsフォトビューアー

本題に入りますが、Windows 7/8.1では、エクスプローラーで画像ファイルを右クリックした際に表示されるコンテキストメニューに、「プレビュー」という項目が含まれていました。このコマンドは、画像を「Windowsフォトビューアー」というデスクトップアプリケーションで開くことができるもので、拡張子すなわち既定の「開く」コマンドに関連付けたアプリケーションとは別に独立して使用できるコマンドです。「Windowsフォトビューアー」は、IrfanViewなどとは違ってフォルダー内の複数ショートカットファイル群を連続プレビューしたり、エクスプローラー上の並び順でファイルを閲覧することができたりといったメリットがありました。全体的な操作性や対応画像の種類などはIrfanViewに若干劣る面がありますが、カラープロファイル対応というのも何気にポイントが高いです。残念ながらWindows 10ではこのコマンドがなくなっているだけでなく、クリーンインストールした際には「Windowsフォトビューアー」自体が無効化されています。今回は幸いWin8.1からのアップグレードインストールだったので、コマンドを復帰するだけで済みました。復帰の手順は下記のようなサイトで紹介されています。

ascii.jp
moshimore.jp
ryus.co.jp

毎回手作業で実行するのは面倒ですので、今回は「プレビュー」コマンド復帰の手順を一発で実行するためのレジストリファイルの内容を記載しておきます。UTF-16LE形式のテキストファイルで.reg拡張子を付けて保存してください。ただしご利用は自己責任でどうぞ。

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\SystemFileAssociations\image\shell\openwpv]
@="プレビュー"

[HKEY_CLASSES_ROOT\SystemFileAssociations\image\shell\openwpv\command]
@="%SystemRoot%\\System32\\rundll32.exe \"%ProgramFiles%\\Windows Photo Viewer\\PhotoViewer.dll\", ImageView_Fullscreen %1"

[HKEY_CLASSES_ROOT\SystemFileAssociations\image\shell\openwpv\DropTarget]
"CLSID"="{FFE2A43C-56B9-4bf5-9A79-CC6D4285608A}"

Windows 10とSAI

今のところ、Win10でSAI v1.2.xの挙動が若干おかしいのが気になります。SAIはファイルI/Oなどの時間がかかる処理をサブスレッドではなくメインスレッド(UIスレッド)で実行しているレガシーアプリケーションなのですが、多数のレイヤーの回転・拡大縮小中とか、ファイルオープンダイアログでのサムネイル自動生成中とか、巨大な画像ファイルの読み書き中とか、UIスレッドが固まる状況でタスクバー部分が一定時間まったく見えなくなる現象が出ます。処理が完了すればタスクバーは復帰しますが、それまでは操作ができなくなります。v1.2.0/v1.2.5で確認しました。Win8.1だと同様にUIスレッドがビジー状態になったときにSAIのウィンドウがボケる(バイリニアフィルターがかかったようになる)現象が出ていましたが、タスクバーが見えなくなるというレベルのひどさではありませんでした。一方、Visual Studio 2012/2013/2015の場合、起動時など「応答なし」状態に陥ることがあるときでもタスクバーまで巻き込むようなことは一切ないので、おそらくSAI自身の設計に起因する固有問題なのだと思いますが、後継となるv2.0もいまだにプレビュー版のままなので、SAIを仕事で使っている人はWindows 10に移行するのは待ったほうがよいかもしれません。画像編集アプリなのに64bit対応が遅れているというネックもあるものの、クリスタでは使い勝手や書き味が違うため、泣く泣く32bit版のSAIを使い続けているという状況なのですが、早いところなんとかして欲しいです。
ちなみに、ちまたで噂されているペンタブレットの遅延ですが、今のところ特に書き味が悪くなったという感じはありません。検証したのはIntuos Pro M (PTH-651) + 6.3.16-2ドライバー環境ですが、それなりに高速なCPUを積んでいるので気にならないだけかも。
※2017-02-05追記:
Windows 10 Anniversary Update (version 1607, build 14393) をインストールして、グラフィックスドライバーをGeForce 378.49 for Win10 x64に更新したらSAIフリーズ時タスクバー問題が解消されたようです。もしかするとWindows 10 November 2015 Update (version 1511, build 10586) もしくは358.91ドライバーのどちらかに問題があったのかもしれません。ちなみにUpdate 1607を適用すると、コンテキストメニューからプレビューコマンドが再び削除されますので、再度登録作業が必要になります。

その他の落穂拾い

Windows 10の電卓は相変わらず使いづらいストアアプリ(UWPアプリ)版のみしか搭載されていません。Windows 10 EnterpriseのLTSB版には従来のデスクトップアプリ版の電卓が「win32calc.exe」という名称で提供されているらしいのですが、Win10 Home/Proにもデスクトップアプリ版をぜひ搭載して欲しいです。

Vulkan SDK付属のGLSLコンパイラー

2016年2月にVulkanの正式仕様とSDKがリリースされ、その後NVIDIAAMDなど大手GPUベンダー各社からもPC向け正式ドライバーがリリースされ始めているので、そろそろ試してみることにしました。まずはシェーダーのコンパイラーまわりから入ります。ちなみにVulkan仕様とSDKは、最初の正式リリース以降かなり頻繁にリビジョンアップが続けられています。

LunarXchange - The source for Vulkan development tools (sponsored by Valve)

Vulkan用GLSLコンパイラ

Vulkan SDKにはGLSLをコンパイルしてSPIR-Vを出力することのできるオフラインコンパイラー「glslangValidator」が付属します。

32bit版 (x86) は下記にインストールされます(Windows環境)。

%VK_SDK_PATH%/Bin32/glslangValidator.exe

64bit版 (x64) は下記にインストールされます。

%VK_SDK_PATH%/Bin/glslangValidator.exe

Visual Studio (Visual C++) の[カスタム ビルド ツール]でプロジェクトのビルド時にGLSLソースファイルからSPIR-Vバイナリを生成するとき、たとえば下記のように[コマンド ライン]の項目を設定します。

echo Now compiling GLSL source file to SPIR-V...
"$(VK_SDK_PATH)\Bin32\glslangValidator.exe" -V "%(Identity)" -o "$(OutDir)VkShaders\%(Filename).spv"

[出力ファイル]には「$(OutDir)VkShaders\%(Filename).spv」を指定します。
例として "VkShaders" サブフォルダーを挟んでいますが、このフォルダーはVisual Studioが勝手に生成してくれます。

なお、glslangValidatorはBOM付きUTF-8を扱えないので注意。BOMなしのUTF-8もしくはASCIIを使う必要があります。

#includeディレクティブ

GLSLは標準で #include ディレクティブをサポートしませんが、Google拡張とARB拡張は存在する模様。

#extension GL_GOOGLE_include_directive : enable
#extension GL_ARB_shading_language_include: enable

しかしVulkan SDK 1.0.13.0付属のglslangValidatorでGoogle拡張を有効にしても、実際に #include を使えるようにはなりませんでした。ARB拡張に至っては有効にすることすらできない模様。これのどこがリファレンスコンパイラーなのか……

なお、Googleは「glslc」と呼ばれるシェーダーコンパイラーを開発して公開している模様です。SPIR-Vにも対応しているらしい。試してはいませんが、こちらは #include が使えるものと思われます。
https://github.com/google/shaderc/tree/master/glslc

ちなみにGoogleのGLSLコンパイラー実装では、

#extension GL_GOOGLE_cpp_style_line_directive : enable

のようにして拡張を有効にすると、

#line 1 "test.frag"

のように #line ディレクティブの第2引数にファイル番号の数値ではなく任意文字列を使えるようになるらしいです。この拡張機能はVulkan SDK 1.0.13.0付属のglslangValidatorでも使える模様。もともと #line はGLSLコンパイル時にエラー/警告メッセージから問題発生個所を特定するなどの目的でプログラマーがファイル番号や行番号を明示的に制御するための機能で、OpenGLではコンパイル対象となる複数のGLSLソース文字列をオンメモリで結合するAPI (glShaderSource) が用意されている関係上、仕方なく存在しているような変な機能なんですが、せっかくオフラインコンパイラーがあるのにVulkan開発でも #line なんぞを明示的に使わないといけない時点でナンセンス極まりないです。ちなみにDirectXのHLSLには #include が完備されていて、#line のような余計な機能もありませんが、コンパイルエラー発生時のエラーメッセージは標準C/C++に比べるとやはり分かりづらいです。

#versionディレクティブ

glslangValidatorで #version 400 - #version 450 を指定したシェーダーをコンパイルすると、下記のような警告が出ます。

Warning, version 450 is not yet complete; most version-specific features are present, but some are missing.

要するにまだGLSL 4.xすなわちOpenGL 4.xの相当機能を完全に実装しきれていないらしい。#version 330 であれば警告は出ません。しかしこれではOpenGL 4.xアプリケーションを満足に移植できない可能性があります。なんとも情けない……
OpenGL 4.0で標準化されたシェーダーサブルーチン(Direct3D 11でいう動的シェーダーリンク)など、本質的にVulkanの仕様と相反する機能であれば使うことができないのも分かりますが、Vulkanと互換性のあるGLSL機能までも実装しきれていないのはダメすぎかと。こんなていたらくではだれも移植を始めてくれないでしょ……

HLSL互換機能

ちなみにVulkan SDK 1.0.13.0付属のglslangValidatorでは、-D オプションを付けることで、なんとHLSLが使えるようになる模様。確かSDK 1.0.5.0では -D オプションはまだ存在していなかったように思われます。Vulkanが要求している入力はSPIR-V中間表現であり、GLSLはあくまでフロントエンドでしかないため、別にHLSLが使えても不思議ではありませんが、もし本当にHLSLが使えるのであれば、多くの機能面で劣るGLSLは要らない子ですね*1

たとえば下記のような最小のHLSLピクセルシェーダーは、普通にコンパイルが通ることを確認しました。Direct3D 10以降のシステム値セマンティクスも使えるようなので、おそらくDirect3D 11およびシェーダーモデル5.0相当の機能は使えるものと思われます。

float4 main() : SV_Target0
{
  return float4(0,0,0,0);
}

ただしソースファイルの拡張子は、GLSLと同様のルールに従う必要があります(*.vert, *.frag など)。
また、#include も使えない模様。結局ただの劣化移植です。本家である fxc.exe には及びません。

結論

総論としては、やはりVulkanはSDKやドライバーを含めてまだまだ未成熟な模様です。実績および開発のしやすさでは、Direct3D 11/12とHLSLに軍配が上がるのは火を見るより明らか。特にせっかくのオフラインシェーダーコンパイラーの実装が現時点で微妙すぎるので、OpenGLから移行する気がまったく起こりません。あとVulkan API自体がDirect3D 12以上にローレベルなので、プログラミングの難易度も相当に高いです。

とはいえ、OpenGLでないがしろにされてきたシェーダーのオフラインコンパイルや、マルチスレッド・マルチGPU対応などに真面目に取り組んでいるVulkan APIの姿勢というか設計思想自体は評価できるので、開発環境も含めて今後の発展と進化に期待したいところです。Vulkanを用いて、Direct3D 11やOpenGL 4に近いインターフェイスを持つ上位ラッパーレイヤーを構築してくれれば、Direct3D/OpenGLからの移植も進むんじゃないでしょうか。あとC++バインディングが欲しい……

*1:コンピュートシェーダーに関しては例外で、エクステンションまで含めると実はGLSLのほうがポテンシャルが高いです。

INIファイルでUnicodeを扱う

今更ですが今日はINIファイルのお話です。初めに断っておきますが、ぶっちゃけWindowsアプリケーションでレガシーなINIファイルを使うのはもうやめましょうWindowsアプリケーション設定の管理には、今後はレジストリXMLファイルなどを使うべきです。

そもそもINIファイルとは?

INIファイルは昔からWindowsアプリケーションの設定管理に使われている簡易テキストフォーマットで、テキストエディターでも簡単に閲覧・編集することができるうえにXMLと比べて構造がシンプルなため、いまだによく使われていますが、その歴史はWin16時代にまでさかのぼります。自分はWin16を使ったことはありませんが、Windowsアプリケーションを開発していると、どうしてもINIファイルのようにWin16時代から引きずられてきた負の遺産に直面せざるを得ないことが多々あります。特にWin9x時代あるいはそれ以前に開発された昔のアプリケーションをメンテナンスする場合には、互換性維持のため泣く泣くINIファイルを使い続けないといけないこともあります。

INIファイルでは、具体的には下記のようにレコードを1行ずつ記録していきます。セクションやキーは複数含むことができますが、階層構造は扱えません。キーと値をつなぐ「=」の前後の空白は無視されます。

[SectionName]
KeyName=Value
; This is a comment.

コメントはセミコロン「;」で始め、改行で終わります。セミコロンではなく「#」をコメント開始記号に使っている人がときどきいますが、マイクロソフト仕様のINIファイルでは通用しないので注意しましょう。詳しくはWikipediaなどを参照してください。

Win32 APIでINIファイルを扱う場合、下記のような関数を使います。

ほかにも、複数のレコードを一括して読み書きするGetPrivateProfileSection/WritePrivateProfileSectionや、バイナリデータを16進数表記文字列で扱うGetPrivateProfileStruct/WritePrivateProfileStructなどもありますが、たいていはXXXString関数で事足りるでしょう。これらのAPIは、一部の引数にNULLを指定すると挙動が変わるなどかなり高機能ですが、ややモノリシックな感は否めません。もし互換APIを自前で書こうとすると、存外にかなり大変だと思います。

なお、.NET FrameworkにはレガシーなINIファイルを扱うAPIは用意されていません。P/Invokeなどを利用して上記Win32 APIを使うか、自前でパーサーを書く必要があります。

INIファイルの欠点

INIファイルをWin32 APIで読み書きする場合、デフォルトではANSIマルチバイトエンコーディングが使われます。つまり、Windowsのシステムロケール設定(CP932/CP1252などのコードページ)に左右されることになります。例えば、日本語版WindowsではCP932 (Shift_JIS) が使われることになります。これは国際化対応が必要となるアプリケーションでは致命的な制約です。ANSIマルチバイトエンコーディングのテキストファイルでは、現在OSに設定されているシステムロケールに対応するANSIマルチバイト文字/文字列にマッピングできないようなUnicode文字/文字列を直接読み書きすることができないからです。セクション名・キー名はもちろん値の文字列もコメントもASCIIのみに限定する、あるいは読み書きの際に自前のエスケープやエンティティ参照・数値文字参照を駆使する、という思い切った運用で回避するという方法もありますが……

しかし、実はUTF-16のBOMを持つテキストファイをあらかじめ作成しておき、そのファイルに対して前述のWin32 APIでアクセスすると、UTF-16のINIファイルを直接扱うことができるようになります。つまり、INIファイルでまともにUnicode文字/文字列を扱えるようになります。どうしてもINIファイルフォーマットを使い続けなければならない場合、UTF-16のINIは選択肢として検討してもよいでしょう。

そのほか、INIファイルでUTF-8エンコーディングを使う方法も一応あるのですが、これは制約の多い禁じ手です。説明と比較のため、UTF-16/UTF-8/ANSIのINIファイルをそれぞれ読み書きするC#サンプルプログラムを下記にアップロードしていますが、ANSI同様にUTF-8を使うのは避けたほうがよいでしょう。Win32 APIではなく自前でINIのパーサーを書くというのであれば、別にUTF-8を使ってもかまいませんが……

UnicodeIniFileTest1.zip

INIファイルの代替

INIファイルはすでに述べているようにレガシーな非推奨技術です。今後いきなりWin32 APIによるサポートが終了するといったことは考えられませんが、少なくとも積極的なサポートや技術革新はされないことくらいは肝に銘じておきましょう。
INIファイルの代替としては、レジストリXMLファイルがあります。

レジストリ

Windowsレジストリは大きく分けてシステムレジストリとユーザーレジストリの2つがあり、前者はオペレーティングシステム全体に影響を与えるため、書き換えには管理者権限(管理者特権)が必要となりますが、後者はWindowsログインユーザーごとにエントリが作成されるため、書き換えに特権は不要です。レジストリの実体は巨大なバイナリデータベースファイルの集合なので、バイナリデータも直接読み書きしやすいのが特徴です。文字列もUnicodeベースで標準的に管理されるため、INIファイルのような制限はありません。ただ、エンドユーザーがレジストリを編集するためには、起動に管理者特権が必要なレジストリエディター(regedit.exe)を使用しないといけないし、バックアップやレストアもファイルの単純コピーというわけにはいかないので、INIファイルほど気軽に扱えるものではないことは確かです。また、レジストリに依存してしまうと、Windows以外のプラットフォームにアプリケーションを移植する際にも問題となります。

XMLファイル

データ記述言語のデファクトスタンダードともいえるのがXMLです。XMLは主にWebで用いられることが多い技術ですが、MSXML/XmlLiteや.NET Framework/WinRTのようなマイクロソフト公式ライブラリによって標準サポートされているため、デスクトップアプリやUWPアプリでも扱いやすくなっています。Unicodeテキストの扱いも標準化されていますし、テキストフォーマットなので人間が直接編集するのにも困りません。バックアップやレストアもファイルの単純コピーで実施できるため、エンドユーザーにとっても扱いやすいです。欠点としてはXML自体がかなり巨大な仕様であるため、単純なアプリケーション設定を管理する方法としてはややオーバースペック気味であること、また特にDOMパーサーが重めであることが挙げられます。

汎用スクリプト言語/データ記述言語

そのほかの代替手段としては、Lua/Squirrel/Python/IronPythonといった汎用スクリプト言語や、JSONのようなデータ記述言語の処理系をアプリケーションに組み込んでしまうという方法も挙げられます。組み込みにはそれなりに手間がかかりますが、これらの言語は柔軟性が高く、スクリプト中で可変長配列データや改行を含むRaw文字列などを扱いやすいため、場合によってはINIやXMLよりも設定ファイルフォーマットとして適していることがあります。

結論

繰り返しになりますが、INIファイルはもうやめましょう

あの、キョーマさん……?


【DimensionW】「あの、キョーマさん……?」イラスト/sygh[sai] [pixiv]

2016年に入ってもう2か月ほど経ちますが、ようやく今年初めての更新になります。「DimensionW」から、百合崎ミラちゃん。このくらいだったらR18タグ付ける必要はないかな……と。DimensionW面白いですね。OPはなんだコレ!?って感じなんですがEDと対になっていて、双方ともめちゃくちゃよく動きます。

今期は面白いアニメが多くて取捨選択に苦労しました。SFは基本観る人ですが、ゲームやライトノベルからアニメ化される作品よりもコミックからアニメ化される作品のほうが好きかもしれません。DimensionWは攻殻機動隊に通じるものがあり、情報化社会に一石を投じるテーマが主軸になっています。アニメ第4話と第5話は難解で、たぶん1回観ただけでは伏線を見逃してしまうと思います。そもそもお風呂シーン→異空間転送→タオル1枚で逃走→鎖で拘束、の流れがインパクトありすぎて話が頭に入ってこないのも一因なんですが……

ちなみにDimensionWではおそらくもうひとつの空間軸を百合崎博士が発見・Wと定義し、コイルによって空間をねじ曲げることで「可能性」という名のエネルギーを取り出すという設定なんだと思いますが、実は我々の世界はすでにX, Y, Zに時間軸tを含めた4次元空間であり、理系人間的には「第4の次元」というのは普通時間軸tを指します。実際「可能性」というのは時間にも深い関わりがあるので、SF的にもかなり興味深い設定だと思います。

自作MetasequoiaプラグインをGitHubで公開しました

かつて2007年~2008年頃、こっそり自分のためだけに開発したMetasequoiaプラグインソースコードがHDDの肥やしになっていたので、Gitの練習も兼ねてGitHubに移管・オープンソース化しました。ライセンスはMITです。

今回ソースコードを公開したのは下記のプラグインです。すべてUV編集関係のミニツールになります。

プラグイン 機能概要
SyghMQUV2SVG.dll UV展開図をSVGファイルとして出力
SyghMQUVBBox.dll UVを境界ボックスベースで数値入力変形
SyghMQUVImplant.dll UVをオブジェクト間で移植
SyghMQUVMatrix.dll UVをSRT行列ベースで数値入力変形
SyghMQUVSelectUtil.dll UV選択操作のユーティリティ

Metasequoia 3/4用のビルド済みバイナリ(32bit/64bit)は下記からダウンロードできます。ヘルプ・取説としてテキストファイルも同梱しています。
github.com

とはいえ、オリジナルはC++を使い始めてまだ2年かそこらのときに書いたコードであり、今改めて見直すとそれはもうひどい有様でとても人様に見せられるレベルではなかったので、Visual C++ 2013とMFCを使って最大限書き直しました。VC 2015はCRTに致命的なバグを抱えているため、今回は見送っています(Visual Studio 2015 Update 1はまだテストしていません)。公開したソースコードはいずれも規模がとても小さいので、MFC標準DLLやC++11規格の入門としては良い教材になるかもしれません。MFC自体すでに枯れた技術で、真新しさや面白みはほとんどありませんが……

なお、今回の公開はあくまでソースコードの共有とメンテナンスが目的です。リポジトリのreadmeやWikiにも記載しているように、新機能の開発に関しては今後行なう予定はありません。理由は察してください。

Metasequoiaの今後

Metasequoiaに関しては3DCGを始めた学生の頃からお世話になっていました。直感的なモデリングに特化したツールで、比較的安価であるにもかかわらずプラグインによる拡張性が高く、果てはボーンやモーフによるアニメーションまでMetasequoia上で実現する強烈なプラグインKeynote」が出現したことにより、当時のホビーCG界隈は最高潮に達していたのではないでしょうか。現役ゲームデザイナーによるHow-to本も多数出版されていたと記憶しています*1。ただ、Metasequoia 4の開発ロードマップをめぐってその後Keynoteの開発者mqdl氏との軋轢が深まるなど、今は全体的に活気がなくなっているような印象を受けます。これまでユーザー開発者から提供・公開されていたおなじみのプラグインも、32bit版Metasequoiaでしか使えず、64bit版Metasequoiaに対応するためにはプラグインの64bit化が必要となるにもかかわらず、一向に64bit対応が進んでいないどころかMetasequoiaプラグイン開発自体が縮小傾向にあるように思えてなりません。かつてプラグインを公開していたサイトが閉鎖されてしまったケースも多々あります。
また、プラグインSDK配布ページに記載されている下記の1文が、さらにプラグイン開発者離れを加速させてしまっているように思います。

「作成したプラグインは自由に配布することができますが、Metasequoia本体 (Standard/EXなど全エディションを含む) に搭載された機能と競合することを目的とした等価なプラグインを作成して配布することを禁止します。」

ほかの3DCGソフトウェアと比べるのはどうかとも思うのですが、このように(たとえ競合といえど)プラグイン開発に制限を課したり禁止したりするなど聞いたことがありません。せめて適用対象をもっと明確にしていただきたいところですが、議論は一向に進んでいないようです。何が怖いかというと、せっかくオープンソース化したソフトウェアが鶴の一声で公開停止を余儀なくされるというのは個人的にも痛いし、またそれ以上にそういった理不尽であいまいな規制はソフトウェアの健全な発展に悪影響を及ぼすことが予想されるからです*2
http://www.metaseq.net/bbs/metaseq/bbs.php?sel=3622&db=metaseq_mqbbs&lang=jp&filter=&query=&tag=

Metasequoiaはその後もバージョンアップを重ね、レイトレーシングやボーン/モーフまでをも独自に実装するなど、これまでプラグインによって実現されてきた機能を着実にネイティブ実装しつつあるようですが、このまま行くとコミュニティ総体としては先細りになってしまうのではないかと危惧しています。すでに現在のMetasequoiaは、2010年頃までのメインユーザー層が想定していた方向性とは違う向きに舵を切りつつあるように思います。ユーザーが欲しがっている(ように見える)ものをただ与えることがクリエイターやエンジニアの仕事なわけではないことは確かですし、個性全開でがむしゃらに突っ走った結果が最終的にユーザーのニーズにマッチして成功するという例は往々にして存在しますが、あえて余計な機能の追加をすることなくシンプルであり続ける、そして足らないものはコミュニティに任せる、というのも一つの選択肢なのではないか、と自分は思います。

閑話休題。とりあえず自分は今後どうするのかというと、LightWaveを持っているんだからさっさと使いこなせるようにがんばります。Metasequoia向けに実装したこのプラグインも、せっかくなのでLightWaveプラグインとして実装し直してみようかと考えています。

*1:Keynoteでのアニメーションは、まるで日曜大工の道具で人が住む家を建てるようなものです。無償のプラグインに依存しすぎるということは、プラグイン開発者の気分次第で開発が停滞・終了したときに技術が廃れたり陳腐化したりする危険性もあり、できれば避けるべきバッドノウハウなのですが、使い慣れたツール上で制約を乗り越えながら別次元の高度な機能を実現すること自体は素晴らしいチャレンジだと思います。

*2:同様に、ソフトウェア特許というものに対しても自分は懐疑的というよりむしろ反対派です。かつて松下電器産業がアイコン特許(笑)を傘にジャストシステムワープロソフト「一太郎」を訴えた問題がありましたが、当時所属していた研究室の助教授は「松下はナンセンス極まりないね」と強く批判していました。

bashでGitのファイル名を一括置換

Git for Windowsインタラクティブシェル(コマンドラインインターフェイス)として付属するbashで、文字列置換を使ってgit mvを一括実行してみよう、という話です。

例えば

./Projects/MyClass1.cpp
./Projects/MyClass1.h
./Projects/MyClass2.cpp
./Projects/MyClass2.h
……

というように命名されたファイルがGit管理下に多数存在したとして、これらを

./Projects/MyInternalClass1.cpp
./Projects/MyInternalClass1.h
./Projects/MyInternalClass2.cpp
./Projects/MyInternalClass2.h
……

というように一括でリネームしたいとき、1つ1つgit mvしたり、TortoiseGitなどのGUIフロントエンドからいちいちファイル名を置換したりするのは気が遠くなるくらい退屈な作業です。

そういうときはbashでループを書きます。

for i in $(find . -iname "MyClass*"); do git mv "$i" "${i/MyClass/MyInternalClass}"; done

実行前に確認してみたいときはechoを入れてみます。

for i in $(find . -iname "MyClass*"); do echo git mv "$i" "${i/MyClass/MyInternalClass}"; done

さらに複雑な置換をするには正規表現を使うとよいでしょう。

Visual Studio 2015とD3DCompiler

Visual Studio 2015でビルドした、DirectX 11.1を使ったMFC/WPF混合アプリをWindows 7エクスプローラーから起動すると、起動直後に勝手に終了する現象が出ました*1
しかし、終了時にWindowsからクラッシュエラーのタスクダイアログも表示されないし、イベントログにも何も記録が残りません。
Windows 8.1では普通に起動します。また、Windows 7でも、Visual Studio 2015 IDEからデバッグ実行した場合は普通に起動します。

調査

まずPowerShell経由で起動して、終了コードを取得してみました。

$result = Start-Process -FilePath "<DX11AppName>.exe" -PassThru -Wait
Write-Host $result.ExitCode.ToString("X8")

結果は「C0000135」となりました。
調べてみると、どうもこのコードはWindows OSの場合「Unknown Hard Error」で、アプリケーションの場合「Unable To Locate Component」らしいです。

DLL依存関係が怪しいと感じたのでDependency Walkerで調べてみたところ、D3DCompiler_47.dllが勝手にリンクされていました。
もちろん明示的なリンク指定はしていないし、同じアプリをVS2012/2013でビルドしていたときはDLLが勝手にリンクされるようなことはありませんでした。

Windows 7の場合、Visual Studio 2012や2013/2015をインストールしても、System32やSysWOW64にはD3DCompiler_46.dllやD3DCompiler_47.dllはインストールされません。
Windows SDKDirectX SDKが統合されたWindows SDK 8.0以降では、D3DCompilerのランタイムは

%ProgramFiles(x86)%\Windows Kits\8.0\bin\x86
%ProgramFiles(x86)%\Windows Kits\8.0\bin\x64
%ProgramFiles(x86)%\Windows Kits\8.1\bin\x86
%ProgramFiles(x86)%\Windows Kits\8.1\bin\x64
%ProgramFiles(x86)%\Windows Kits\10\bin\x86
%ProgramFiles(x86)%\Windows Kits\10\bin\x64

といった場所にインストールされます。スタンドアロンのHLSLシェーダーコンパイラーであるfxc.exeと同じ場所です。ただし、ARM版やARM64版のDLLは存在しないようです。
Visual Studio IDEからの起動時には、この場所にパスを通すのでDLL解決できるんですが、エクスプローラーからの起動時にはDLL解決できなくて強制終了させられる模様。

Windows 8の場合、D3DCompiler_46.dllは標準コンポーネントとしてシステムフォルダーにインストールされていません。Windows 8のストアアプリでは、D3DCompilerの使用が認められていなかったからです。
Windows 8.1の場合、Windows 8.1版のD3DCompiler_47.dllが標準インストールされています。
Windows 10の場合、Windows 10版のD3DCompiler_47.dllが標準インストールされています。
これにより、Windows 8.1ストアアプリあるいはWindows 10ユニバーサルWindowsプラットフォームアプリを配布する場合は、DLLを同梱する必要がなくなります。

ちなみに、Windows SDK 8.1に含まれるD3DCompiler_47.dllのバージョンは「6.3.9600.16384」。
一方、Windows SDK 10に含まれるD3DCompiler_47.dllのバージョンは「10.0.10240.16384」。
ファイルサイズも異なるし、SDK 10版のほうはDirectX 12/11.3 API対応(シェーダーモデル5.1対応)が追加されているので、前方互換性はありません。さらに、システムフォルダーにインストールされているD3DCompiler_47.dllは、Windows Updateか何かで更新されているらしく、SDK同梱版とは末尾のパッチ番号(リビジョン番号)が微妙に異なるようです。ていうかこれまで通り新しいWindows 10バージョンではファイル名のナンバリングを更新しろよ……

結論

Visual Studio 2015でDirectX 11を使ったデスクトップアプリケーションをビルドし、Windows 7/Windows 8.x向けにも配布する場合、DLL依存関係には十分注意したほうがよさそうです。
普段からD3DXやD3DCompilerを使わないように注意していたんですが、もしD3DCompiler_47.dllが何らかの理由で勝手にリンクされるとなると、Windows SDK 10に含まれる同DLLをアプリケーションに同梱する必要があります。D3DCompilerランタイムはレジストリ登録を必要とするCOMライブラリではなく、ただのC言語形式関数がエクスポートされているだけのDLLみたいです。
もちろんDirectX 12/11.3はWindows 7/Windows 8.1にはバックポートされないので、当然それらの機能を使った場合はアウトですが、DirectX 11.1までの機能しか使わず、またD3DCompilerを明示的にリンクしない場合でも、VS2015ではD3DCompilerが勝手にリンクされることがある模様。かなり迷惑な仕様変更です。ちなみにx86/x64版のD3DCompiler (lib/dll) はSDKに同梱されていますが、ARM/ARM64版に関しては*.libのみで、*.dllは同梱されていません。

通例、アーリーバインドしたDLLの解決ができない場合は、アプリ起動時に「DLLが見つからない」という旨のエラーメッセージが出るはずなんですが、今回はメッセージが表示されることなく勝手に終了してしまい、イベントも記録されなかったこと、そしてWindows 8.1には同名のD3DCompiler_47.dllが存在し、エクスプローラーから実行してもDLL解決だけはできるようになっていたことが、エラー原因の発見の遅れにつながりました。

*1:Windows 7 SP1はIE10/IE11などと同時にインストールされるPlatform UpdateプログラムKB2670838を適用することで、DirectX 11.1 APIを使用できるようになります。