HSTRING完全に理解した

HSTRINGというのはUWPやWindowsAppSdkで使われる、Windows Runtimeの文字列です。 Windows APIでは文字列の種類がいろいろあります。LPTSTRやらBSTRやら……これらの新顔で、オーバーヘッド大き目のかわりに柔軟です。

HSTRINGは基本的には<winstring.h>ヘッダーに含まれる WindowsCreateString APIで作成します。

HSTRING str = nullptr;
auto hr = ::WindowsCreateString(L"hoge", 4, &str);

このときヒープに以下のようにメモリが確保されます。

HSTRING_HEADERは、hstring.hに以下のように定義されています。 内部にポインタがあるので、32bit環境と64bit環境ではサイズが異なります(20byteと24byte)。

// Declare the HSTRING_HEADER
typedef struct HSTRING_HEADER
{
    union{
        PVOID Reserved1;
#if defined(_WIN64)
        char Reserved2[24];
#else
        char Reserved2[20];
#endif
    } Reserved;
} HSTRING_HEADER;

byte数だけを定義しているようなものなので、画像内のHSTRING_HEADER内のレイアウトやメンバ名は私が勝手につけたものになります。正確さは保証しません。

HSTRINGを使い終わったら、WindowsDeleteString APIで参照カウントを減らします。参照カウントが0になったら削除されます。

メモリレイアウト的には、HSTRINGHSTRING_HEADER構造体へのポインタであり、HSTRING_HEADER内のptrRawBufferポインタがWCHAR*のNULL終端文字列を指していれば成立します。 よって、プログラマが任意の場所にHSTRING_HEADERとWCHAR*のNULL終端文字列分のメモリを確保すれば作成することができます。 スタック、つまりローカル変数を用いてもOKです。スタックを用いる場合はヒープにメモリを確保するコストがないのでfast stringなどと呼ばれるようです。 メモリを自前で管理してHSTRINGを作成するには、WindowsCreateStringReference APIを使用します。

wchar_t raw[5] = L"hoge";
HSTRING_HEADER header{};
HSTRING str = nullptr;
hr = ::WindowsCreateStringReference(raw, 5, &header, &str);

bFastStringのフラグがたって1になり、HSTRING_HEADERとRawBufferが不連続となり、WindowsCreateStringのときは間にあった参照カウント領域が存在しません。

WindowsCreateStringReferenceは渡されたメモリがどのように確保されたかを知りませんから、WindowsDeleteStringが適切にメモリを解放することは不可能なので、そもそも管理できないなら参照カウントもなくしてしまえということでしょう。実際にWindowsCreateStringReferenceで作成されたHSTRINGをWindowsDeleteStringに渡しても何もおきません。HSTRING_HEADERとRawBufferのメモリについてはWindowsCreateStringReference呼び出し側が解放の責務を負います。スタックを用いた場合はもちろん明示的な解放は不要です。

スタックから作成したHSTRINGを戻り値で返したい場合は、解放されてしまうのでそのまま返すわけにはいきません。その場合はWindowsDuplicateString APIで文字列を複製します。WindowsCreateStringReferenceで作成されたHSTRINGを複製すると、同内容のものがWindowsCreateStringで作られたときと同じようにヒープに作成され、参照カウントで管理されます。

HSTRINGはImmutable、つまり不変なのでWindowsCreateStringで作られたHSTRINGに対してWindowsDuplicateStringを呼ぶと、複製が作られずに参照カウントだけが増えます。WindowsCreateStringReferenceで作成されたHSTRINGの場合は、寿命管理外のものを寿命管理下のものに変更するためにコピーをしますが、もともと寿命管理下のものはコピーする意味がありません。