potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C++ WinAPIのPSFormatForDisplayAlloc関数の返す文字列がコンソールで異常な文字(Unicode directional characters)を含む

ファイルシステムオブジェクトのプロパティ値をコンソールへ出力していたら、あるオブジェクトの更新日時(System.DateModified)以降が出力されなくなりました。原因はIPropertySystem::FormatForDisplayAllocPSFormatForDisplayAlloc)関数の仕様「フラグ指定しなければ結果(文字列)にUnicode directional characters(\x200e\x200f)を含む」でした。コンソールの文字コード設定も関係あるかもしれません。ユニコードモードに設定すると制御記号ごと表示できます(関連記事)。

解決策はIPropertySystem::FormatForDisplayAllocPSFormatDisplayAlloc)のフラグにPDFF_NOAUTOREADINGORDER (PROPDESC_FORMAT_FLAGS::PDFF_NOAUTOREADINGORDER)を加えることですか上記のユニコードモードの適用ですただし、なお、公式ドキュメントによるとフラグを指定してもUnicode directional charactersが追加される可能性は残るそうです。

コンソール出力の途切れは以下のコードで再現できるかもしれません。

#include <io.h>
#include <fcntl.h>

#include <iostream>
#include <string>

#pragma comment(lib, "propsys.lib")
#define STRICT
#include <Windows.h>
#include <propsys.h>
#include <ShObjIdl.h>

// WIL
#include <wil/com.h>
#include <wil/resource.h>

using namespace wil;
using namespace std;

// PSGetPropertySystem関数のラッパー
com_ptr<IPropertySystem> PSGetPropertySystem()
{
    com_ptr<IPropertySystem> propSys;
    THROW_IF_FAILED(PSGetPropertySystem(IID_PPV_ARGS(&propSys)));
    return move(propSys);
}

// SHGetPropertyStoreFromParsingName関数のラッパー
com_ptr<IPropertyStore> SHGetPropertyStoreFromParsingName(
    PCWSTR path,
    IBindCtx* pbc,
    GETPROPERTYSTOREFLAGS flags)
{
    com_ptr<IPropertyStore> propStore;
    THROW_IF_FAILED(SHGetPropertyStoreFromParsingName(
        path, pbc, flags,
        IID_PPV_ARGS(&propStore)));
    return move(propStore);
}

unique_cotaskmem_string PSFormatPropertyValueFromName(
    IPropertyStore* pps,
    PCWSTR name,
    PROPDESC_FORMAT_FLAGS flags)
{
    PROPERTYKEY key;
    THROW_IF_FAILED(PSGetPropertyKeyFromName(name, &key));
    unique_prop_variant value;
    THROW_IF_FAILED(pps->GetValue(key, value.addressof()));

    unique_cotaskmem_string s;
    THROW_IF_FAILED(PSFormatForDisplayAlloc(key, value, flags, &s));
    return std::move(s);
}

int main()
{
    // ユニコードモードの設定
    // アンコメントするとユニコード文字が制御記号ごと表示されます。
    //_setmode(_fileno(stdout), _O_U16TEXT);

    // プロパティを取得するパス
    auto path = L"C:\\Windows";

    // COMの初期化
    auto couninit = wil::CoInitializeEx(COINIT_APARTMENTTHREADED);

    // プロパティストアの取得
    auto propSystem = PSGetPropertySystem();
    auto propStore = SHGetPropertyStoreFromParsingName(
        path, nullptr, GPS_DEFAULT);

    // System.DateCreatedプロパティを取得して
    // 確認のためにwstringへ変換する。
    wstring dateStr = PSFormatPropertyValueFromName(
            propStore.get(), L"System.DateCreated", PDFF_DEFAULT).get();

    std::wcout << dateStr << std::endl;
    // 出力:(なし)<既定>
    // 出力:・2019/・12/・07/ ・・18:03<ユニコードモード>(制御記号は中黒へ置換)
    // デバッグウィンドウ:"2019/12/07 18:03"
    // 生の値:
    // 8206 '' <- Unicode directional character
    // 50 '2'
    // 48 '0'
    // 49 '1'
    // 57 '9'
    // 47 '/'
    // 8206 '' <- Unicode directional character
    // 49 '1'
    // 50 '2'
    // 47 '/'
    // 8206 '' <- Unicode directional character
    // 48 '0'
    // 55 '7'
    // 32 ' '
    // 8207 '' <- Unicode directional character
    // 8206 '' <- Unicode directional character
    // 49 '1'
    // 56 '8'
    // 58 ':'
    // 48 '0'
    // 51 '3'

    return 0;
}

dateStr変数(std::wstring)をみると、ところどころに8206 (0x200E)と8207 (0x200F)が現れています。おそらくコンソールはここで表示が終了しています。0x200EはLEFT-TO-RIGHT MARK、0x200FはRIGHT-TO-LEFT MARKでどちらも制御コードです。