2014年12月8日月曜日

PictureFan ver.0.30.0 を公開

PictureFan の ver.0.30.0 を公開しました。
ダウンロードはこちら http://www.geocities.jp/iooiau/picturefan.html から。

今回の主な機能追加として、各フィルタ処理の設定を保存/読み込みできるようにしました。

設定の名前を入力して、[+] ボタンを押せば設定が保存されます。
保存された設定は、リストから選択して後で読み込むことができます。

いくつか致命的な不具合を発見・報告頂きましたので、その修正も行いました。

2014年8月10日日曜日

PictureFan ver.0.29.1 を公開

PictureFan の ver.0.29.1 を公開しました。
ダウンロードはこちら http://www.geocities.jp/iooiau/picturefan.html から。

今回は不具合修正のみです。以下の不具合を修正しました。

  • 1ビット画像から24ビット画像への変換が正常に行われない不具合を修正
  • AtoB Converter の出力プラグインで保存しようとした際にエラーが出る不具合を修正
  • パレットの編集を行った際に表示が更新されない不具合を修正
  • 明るさとコントラストのダイアログの、ガンマのスピンが動作しない不具合を修正
  • サムネイルの項目にシェルのコンテキストメニューを使わない場合、「名前の変更」が「コピー」と表示される不具合を修正

不具合報告をくださった方、ありがとうございました。

2014年5月25日日曜日

PictureFan ver.0.29.0 を公開

PictureFan の ver.0.29.0 を公開しました。
更新内容について詳しくは更新履歴をご覧ください。

今回の変更点としては、サムネイルの機能追加、動画/音声の再生関係の刷新、全画面表示の機能追加、JPEG の対応強化などです。

サムネイルはプレビューとファイル情報表示の追加、簡易ビューアでの表示機能の追加などを行いました。

動画と音声の再生に関しては、今まで MCI という古い機能を使っていましたが、一般的な DirectShow を利用したものに書き換えました。
ベースとなるコードは LafPlayer から持ってきましたが、もう何年も前のコードなので色々と問題もあり、かなり修正が必要でした。

前からではありますが、一度にたくさんの変更を盛り込み過ぎな気がしていますので、これからはもう少しまめにリリースしたいと思っています。

2014年4月17日木曜日

構造体のサイズに関する面倒な問題

Windows の API では、構造体にその構造体のサイズを表すメンバを含んで、呼び出し側で構造体のサイズを設定することにより、 将来構造体のメンバが増えても互換性が保てるようにしていることが多いということを前回書きました。

しかし、この方法は面倒な問題をはらんでいます。
例えば、以下のような構造体があったとします。

typedef struct PERSON_TAG {
  DWORD cbSize;
  int   nAge;
} PERSON;

これを利用する際に、先頭のメンバに構造体のサイズを代入するようにします。

PERSON person;
person.cbSize = sizeof(PERSON);
person.nAge   = 17;
SetPerson(&person);

新しい SDK でメンバが増えて、以下のようになったとします。

typedef struct PERSON_TAG {
  DWORD cbSize;
  int   nAge;
#if _WIN32_WINNT >= 0x0900
  int   nWeight;
#endif
} PERSON;

#define PERSON_V8_SIZE CCSIZEOF_STRUCT(PERSON, nAge)

先ほどのコードを新しい SDK を利用してそのままコンパイルすると、cbSize は新しいサイズになりますが、 新しいメンバ nWeight に関しては何も設定していないため、問題が起こる可能性があります。 また、過去のバージョンの OS で動作しなくなってしまいます。

それを避けるためには、_WIN32_WINNT を古いバージョンで定義すればいいわけですが、 しかし他の場所で新しい OS の機能も利用したいという場合、困ってしまいます。
そういう場合は、以下のように書き直すことになります。

person.cbSize = PERSON_V8_SIZE;

これでこの部分に関しては問題なくなるのですが、しかしどの構造体にメンバが追加されたかを調べて、 それを利用している部分を探して書き直して…、と一々やるのは面倒です。

Microsoft もこの辺りの問題を認識しているのでしょう、 問題の発生が多そうな構造体に関しては既存の構造体にメンバを追加するのではなく、 新しい構造体を定義してしまうということがされています。
例えば OSVERSIONINFO はメンバが追加されて OSVERSIONINFOEX に、MONITORINFOMONITORINFOEX に、という具合です。
しかし別の構造体にしてしまうと、API 関数に渡す時にキャストする必要が出てくるのが良くないですね。

2016/4/24追記

いつからか、C++ では MONITORINFOEX は MONITORINFO から派生するようになったため、C++ ではキャストの必要がなくなりました。
しかし、派生したことによってアグリゲートでなくなったため、以下のような初期化がエラーになってしまうという別の問題が発生しています。

// error C2440
MONITORINFOEX mi = {sizeof(MONITORINFOEX)};

追記終わり

私は、そもそも構造体のサイズを指定するのに sizeof を使わせるのが良くないと思います。
PERSON の例で言えば、PERSON_V8_SIZE をメンバが追加された段階で定義するのではなく、最初のバージョンで定義して、 sizeof(PERSON) ではなく PERSON_V8_SIZE を使うようにドキュメントに記述するようにします。
そうすれば、SDK を更新しても書き直す必要は無くなります。

構造体の互換性を保つ方法として、サイズを設定する以外に、どのメンバが有効かフラグで指定する、という方法もあります。
例えば以下のように定義して、

typedef struct PERSON_TAG {
  DWORD mask;
  int   nAge;
} PERSON;

#define PERSON_MASK_AGE 0x0001

以下のように利用します。

PERSON person;
person.mask = PERSON_MASK_AGE;
person.nAge = 17;
SetPerson(&person);

こちらの方法では、サイズを指定する場合のような問題は起こりません。
実際に Windows の CommCtrl.h では、こちらの方法が取られているものも多いです。
(しかし、サイズとフラグの両方を設定するようになっているものもあり、今一ちぐはぐなのですが)
この方法の欠点として、メンバが多い場合フラグの指定が長ったらしくなってしまうというのはあります。

2014年4月16日水曜日

CCSIZEOF_STRUCT がバグっている

Windows では、構造体の先頭のメンバで*1、その構造体のサイズを表すようになっているものが多いです。
API を呼び出す側で構造体のサイズを設定することにより、将来 Windows の機能追加で構造体にメンバが追加されても、古いプログラムの互換性が保てるようになっています。
例えば LVGROUP という構造体は、以下のように定義されています。

typedef struct tagLVGROUP
{
    UINT    cbSize;
    UINT    mask;
    LPWSTR  pszHeader;
    int     cchHeader;
    LPWSTR  pszFooter;
    int     cchFooter;
    int     iGroupId;
    UINT    stateMask;
    UINT    state;
    UINT    uAlign;
#if _WIN32_WINNT >= 0x0600
    LPWSTR  pszSubtitle;
    UINT    cchSubtitle;
    LPWSTR  pszTask;
    UINT    cchTask;
    LPWSTR  pszDescriptionTop;
    UINT    cchDescriptionTop;
    LPWSTR  pszDescriptionBottom;
    UINT    cchDescriptionBottom;
    int     iTitleImage;
    int     iExtendedImage;
    int     iFirstItem;
    UINT    cItems;
    LPWSTR  pszSubsetTitle;
    UINT    cchSubsetTitle;
#endif
} LVGROUP, *PLVGROUP;

_WIN32_WINNT >= 0x0600 ということは、pszSubtitle 以降のメンバは Windows Vista で追加されたということになります。
これを以下のように利用すると、

LVGROUP lvg;
lvg.cbSize = sizeof(LVGROUP);

_WIN32_WINNT が 0x0600 以上の場合、Vista より前の XP などでは動作しなくなります。
そういう時に困らないようにということで、過去のバージョンの構造体のサイズを表す定数が定義されています。
LVGROUP の場合、LVGROUP_V5_SIZE として Vista で追加されたメンバを除いたサイズが、以下のように定義されています。

#define LVGROUP_V5_SIZE CCSIZEOF_STRUCT(LVGROUP, uAlign)

CCSIZEOF_STRUCT というのは、構造体のあるメンバまでのサイズを求めるマクロで、意味としては以下のような計算を行うようになっています。

offsetof(struct, member) + sizeof(struct.member)

しかし、実はこれでは問題があるのです。それは、構造体のパディングが考慮されないという点です。
ある構造体 A があったとして、sizeof(A) は A のメンバのサイズの合計になるでしょうか。
答えは「なる場合もあれば、ならない場合もある」です。
なぜなら、構造体のメンバ間や末尾には、CPU の都合のいいようにパディングと呼ばれる余分な領域が追加されることがあるからです。

これにより、x64 ビルドで _WIN32_WINNT の値を 0x0600 未満に定義した場合(つまり Vista 以降のメンバを除外した場合)、sizeof(LVGROUP) は 56 ですが、 _WIN32_WINNT を 0x0600 以上に定義した場合の LVGROUP_V5_SIZE は 52 となり、サイズが食い違ってしまいます。

これは Windows のヘッダのバグでしょう。
私はこのバグにはまってしまい、x86 ビルドだと動くが x64 ビルドだとなぜか動かないという問題が出て、原因の解明に時間を掛けてしまいました。

構造体とサイズを表すメンバに関係して、微妙な問題が他にもあるので、次に続きます。

*1: サイズを表すメンバが、構造体の途中にあるものも一部存在します。