...ing logging 4.0

はてなブログに移行しました。D言語の話とかいろいろ。

D言語でMicrosoft Component Object Model (COM) を使う (その5)

D言語 Advent Calendar 2025 9日目の記事です。

その1でCOMインタフェースを使うために必要な宣言や定義の準備ができて、 その2でCOMの初期化と解放ができるようになりました。 その3では実際にファイルを開くダイアログを表示することができ、その4では選択したファイルのパスを取得できました。

次に、IUnknownインタフェースのQueryInterface関数を使って、ひとつのCOMオブジェクトから別のCOMインタフェースを取得します。

まずは準備

IShellItem2 (shobjidl_core.h) - Win32 apps | Microsoft Learn

IShellItem2インタフェースには、GetFileTimeメソッドがあって、これを使うとファイルの日付と時刻を取得できるようです。

新しいCOMインタフェースが出てきたので、またShObjIdl_core.hを読んで自分で宣言します。

EXTERN_C const IID IID_IShellItem2;

#if defined(__cplusplus) && !defined(CINTERFACE)
    
    MIDL_INTERFACE("7e9fb0d3-919f-4307-ab2e-9b1860310c93")
    IShellItem2 : public IShellItem
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE GetPropertyStore( 
            /* [in] */ GETPROPERTYSTOREFLAGS flags,
            /* [in] */ __RPC__in REFIID riid,
            /* [iid_is][out] */ __RPC__deref_out_opt void **ppv) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetPropertyStoreWithCreateObject( 
            /* [in] */ GETPROPERTYSTOREFLAGS flags,
            /* [in] */ __RPC__in_opt IUnknown *punkCreateObject,
            /* [in] */ __RPC__in REFIID riid,
            /* [iid_is][out] */ __RPC__deref_out_opt void **ppv) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetPropertyStoreForKeys( 
            /* [size_is][in] */ __RPC__in_ecount_full(cKeys) const PROPERTYKEY *rgKeys,
            /* [in] */ UINT cKeys,
            /* [in] */ GETPROPERTYSTOREFLAGS flags,
            /* [in] */ __RPC__in REFIID riid,
            /* [iid_is][out] */ __RPC__deref_out_opt void **ppv) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetPropertyDescriptionList( 
            /* [in] */ __RPC__in REFPROPERTYKEY keyType,
            /* [in] */ __RPC__in REFIID riid,
            /* [iid_is][out] */ __RPC__deref_out_opt void **ppv) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE Update( 
            /* [unique][in] */ __RPC__in_opt IBindCtx *pbc) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetProperty( 
            /* [in] */ __RPC__in REFPROPERTYKEY key,
            /* [out] */ __RPC__out PROPVARIANT *ppropvar) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetCLSID( 
            /* [in] */ __RPC__in REFPROPERTYKEY key,
            /* [out] */ __RPC__out CLSID *pclsid) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetFileTime( 
            /* [in] */ __RPC__in REFPROPERTYKEY key,
            /* [out] */ __RPC__out FILETIME *pft) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetInt32( 
            /* [in] */ __RPC__in REFPROPERTYKEY key,
            /* [out] */ __RPC__out int *pi) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetString( 
            /* [in] */ __RPC__in REFPROPERTYKEY key,
            /* [string][out] */ __RPC__deref_out_opt_string LPWSTR *ppsz) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetUInt32( 
            /* [in] */ __RPC__in REFPROPERTYKEY key,
            /* [out] */ __RPC__out ULONG *pui) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetUInt64( 
            /* [in] */ __RPC__in REFPROPERTYKEY key,
            /* [out] */ __RPC__out ULONGLONG *pull) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetBool( 
            /* [in] */ __RPC__in REFPROPERTYKEY key,
            /* [out] */ __RPC__out BOOL *pf) = 0;
        
    };

今使いたいのはGetFileTimeメソッドだけなので、これだけ完全な宣言にします。 使わないメソッドの定義は引数をなしにして宣言します。

使いたいメソッドの引数にある型は宣言する必要があります。

PROPERTYKEY (wtypes.h) - Win32 apps | Microsoft Learn

typedef struct _tagpropertykey {
  GUID  fmtid;
  DWORD pid;
} PROPERTYKEY;

REFPROPERTYKEYは、PROPERTYKEYのポインタ型なので、D言語では次のように宣言します。

struct PROPERTYKEY
{
    GUID fmtid;
    DWORD pid;
}
alias REFPROPERTYKEY = PROPERTYKEY*;

FILETIME構造体は、core.sys.windows.winbaseにあるので、これをインポートするだけです。

ここまでで、D言語では次のようになります。

import core.sys.windows.winbase;

struct PROPERTYKEY
{
    GUID fmtid;
    DWORD pid;
}
alias REFPROPERTYKEY = PROPERTYKEY*;

// uuid("7e9fb0d3-919f-4307-ab2e-9b1860310c93")
interface IShellItem2 : IShellItem
{
extern(Windows):
    HRESULT GetPropertyStore();//(GETPROPERTYSTOREFLAGS flags, REFIID riid, void **ppv);
    HRESULT GetPropertyStoreWithCreateObject();//(GETPROPERTYSTOREFLAGS flags, IUnknown *punkCreateObject, REFIID riid, void **ppv);
    HRESULT GetPropertyStoreForKeys();//(const PROPERTYKEY *rgKeys, UINT cKeys, GETPROPERTYSTOREFLAGS flags, REFIID riid, void **ppv);
    HRESULT GetPropertyDescriptionList();//(REFPROPERTYKEY keyType, REFIID riid, void **ppv);
    HRESULT Update();//(IBindCtx *pbc);
    HRESULT GetProperty();//(REFPROPERTYKEY key, PROPVARIANT *ppropvar);
    HRESULT GetCLSID();//(REFPROPERTYKEY key, CLSID *pclsid);
    HRESULT GetFileTime(REFPROPERTYKEY key, FILETIME *pft);
    HRESULT GetInt32();//(REFPROPERTYKEY key, int *pi);
    HRESULT GetString();//(REFPROPERTYKEY key, LPWSTR *ppsz);
    HRESULT GetUInt32();//(REFPROPERTYKEY key, ULONG *pui);
    HRESULT GetUInt64();//(REFPROPERTYKEY key, ULONGLONG *pull);
    HRESULT GetBool();//(REFPROPERTYKEY key, BOOL *pf);
}

GetFileTimeメソッドの第1引数にはPROPERYKEY変数に何らかの設定をしてから与えるようです。 今回はファイル作成日が欲しいので、System.DateCreatedを使えばいいようです。

System.DateCreated - Win32 apps | Microsoft Learn

propertyDescription
   name = System.DateCreated
   shellPKey = PKEY_DateCreated
   formatID = B725F130-47EF-101A-A5F1-02608C9EEBAC
   propID = 15
   SearchInfo
      IsColumn = true
   typeInfo
      type = DateTime
      IsInnate = true

これを使ってもいいのですが、Windows SDKのpropkey.hで次のとおり定義されているので、こちらをベースにします。

//  Name:     System.DateCreated -- PKEY_DateCreated
//  Type:     DateTime -- VT_FILETIME  (For variants: VT_DATE)
//  FormatID: (FMTID_Storage) {B725F130-47EF-101A-A5F1-02608C9EEBAC}, 15 (PID_STG_CREATETIME)
//
//  The date and time the item was created. The Indexing Service friendly name is 'create'.
DEFINE_PROPERTYKEY(PKEY_DateCreated, 0xB725F130, 0x47EF, 0x101A, 0xA5, 0xF1, 0x02, 0x60, 0x8C, 0x9E, 0xEB, 0xAC, 15);
#define INIT_PKEY_DateCreated { { 0xB725F130, 0x47EF, 0x101A, 0xA5, 0xF1, 0x02, 0x60, 0x8C, 0x9E, 0xEB, 0xAC }, 15 }

これをD言語で書くとこうなります。

const PROPERTYKEY PKEY_DateCreated = {
    { 0xB725F130, 0x47EF, 0x101A, [0xA5, 0xF1, 0x02, 0x60, 0x8C, 0x9E, 0xEB, 0xAC ]}, 15
};

ここまでで準備ができました。

QueryInterfaceを使って別のCOMインタフェースを取得

その4の記事のmain関数の後半を抜粋します。

IShellItem pItem;
hr = pDialog.GetResult(&pItem);
if (FAILED(hr)) return;
scope(exit) pItem.Release();

LPWSTR outPath;
hr = pItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &outPath);
if (FAILED(hr)) return;
scope(exit) CoTaskMemFree(outPath);

import std.conv;
writeln(outPath.to!string);

QueryInterfaceメソッドの第1引数でIID_IShellItem2を指定し、IShellItem2 COMインタフェースを要求します。 第2引数には、COMインタフェースを受け取るpItem2変数のアドレスを指定します。

// 上記の続きに書く
IShellItem2 pItem2;
hr = pItem.QueryInterface(&IID_IShellItem2, cast(void**)&pItem2);
if (FAILED(hr)) return;
scope(exit) pItem2.Release();

これで、IShellItem型のpItemから、IShellItem2型のpItem2を取得することができました。

使ってみる

PROPERTYKEY prop = PKEY_DateCreated;
FILETIME filetime;
SYSTEMTIME time;
pItem2.GetFileTime(&prop, &filetime);
FileTimeToSystemTime(&filetime, &time);
writefln("%s-%s-%s", time.wYear, time.wMonth, time.wDay);

まずは、PROPERTYKEY型のprop変数に、PKEY_DateCreatedを代入して、ファイル作成日時を取得したいことを設定します。 QueryInterfaceメソッドで取得したIShellItem2 COMインタフェースのGetFileTimeを、pItem2を介して呼び出します。

IShellItem2::GetFileTime (shobjidl_core.h) - Win32 apps | Microsoft Learn

filetimeにはファイル時刻が格納されているので、FileTimeToSystemTime関数で我々が欲しいシステム時刻に変換します。

FILETIME (minwinbase.h) - Win32 apps | Microsoft Learn

実行してみる

ちゃんとファイル作成日時が取得できました。

以下、ソース全文です。

import std.stdio;
import core.sys.windows.windef;
import core.sys.windows.com;
import core.sys.windows.objbase;
import core.sys.windows.winbase;

extern(C) extern const IID IID_IModalWindow;

// uuid("b4db1657-70d7-485e-8e3e-6fcb5a5c1802")
interface IModalWindow : IUnknown
{
extern (Windows):
    HRESULT Show(HWND hwndOwner);
}

extern(C) extern const IID IID_IFileDialog;

// uuid("42f85136-db7e-439c-85f1-e4075d135fc8")
interface IFileDialog : IModalWindow
{
extern (Windows):
    HRESULT SetFileTypes();//(UINT cFileTypes, const COMDLG_FILTERSPEC* rgFilterSpec);
    HRESULT SetFileTypeIndex();//(UINT iFileType);
    HRESULT GetFileTypeIndex();//(UINT* piFileType);
    HRESULT Advise();//(IFileDialogEvents pfde, DWORD* pdwCookie);
    HRESULT Unadvise();//(DWORD dwCookie);
    HRESULT SetOptions();//(FILEOPENDIALOGOPTIONS fos);
    HRESULT GetOptions();//(FILEOPENDIALOGOPTIONS * pfos);
    HRESULT SetDefaultFolder();//(IShellItem psi);
    HRESULT SetFolder();//(IShellItem psi);
    HRESULT GetFolder();//(IShellItem* ppsi);
    HRESULT GetCurrentSelection();//(IShellItem* ppsi);
    HRESULT SetFileName();//(LPCWSTR pszName);
    HRESULT GetFileName();//(LPWSTR pszName);
    HRESULT SetTitle();//(LPCWSTR pszTitle);
    HRESULT SetOkButtonLabel();//(LPCWSTR pszText);
    HRESULT SetFileNameLabel();//(LPCWSTR pszLabel);
    HRESULT GetResult(IShellItem* ppsi);
    HRESULT AddPlace();//(IShellItem psi, FDAP fdap);
    HRESULT SetDefaultExtension();//(LPCWSTR pszDefaultExtension);
    HRESULT Close();//(HRESULT hr);
    HRESULT SetClientGuid();//(REFGUID guid);
    HRESULT ClearClientData();
    HRESULT SetFilter();//(IShellItemFilter pFilter);
}

extern(C) extern const IID IID_IFileOpenDialog;

// uuid("d57c7288-d4ad-4768-be02-9d969532d960")
interface IFileOpenDialog : IFileDialog
{
extern(Windows):
    HRESULT GetResults();//(IShellItemArray* ppenum);
    HRESULT GetSelectedItems();//(IShellItemArray* ppsai);
}

extern(C) extern const IID IID_IShellItem;

// uuid("43826d1e-e718-42ee-bc55-a1e261c37bfe")
interface IShellItem : IUnknown
{
extern(Windows):
    HRESULT BindToHandler();//(IBindCtx pbc, REFGUID bhid, REFIID riid, void** ppv);
    HRESULT GetParent();//(IShellItem* ppsi);
    HRESULT GetDisplayName(SIGDN sigdnName, LPWSTR* ppszName);
    HRESULT GetAttributes();//(SFGAOF sfgaoMask, SFGAOF* psfgaoAttribs);
    HRESULT Compare();//(IShellItem psi, SICHINTF hint, int* piOrder);
}

enum SIGDN : int
{
    SIGDN_NORMALDISPLAY = 0,
    SIGDN_PARENTRELATIVEPARSING = 0x80018001,
    SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000,
    SIGDN_PARENTRELATIVEEDITING = 0x80031001,
    SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000,
    SIGDN_FILESYSPATH = 0x80058000,
    SIGDN_URL = 0x80068000,
    SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001,
    SIGDN_PARENTRELATIVE = 0x80080001,
    SIGDN_PARENTRELATIVEFORUI = 0x80094001
}

extern(C) extern const CLSID CLSID_FileOpenDialog;

extern(C) extern const IID IID_IShellItem2;

struct PROPERTYKEY
{
    GUID fmtid;
    DWORD pid;
}
alias REFPROPERTYKEY = PROPERTYKEY*;

// uuid("7e9fb0d3-919f-4307-ab2e-9b1860310c93")
interface IShellItem2 : IShellItem
{
extern(Windows):
    HRESULT GetPropertyStore();//(GETPROPERTYSTOREFLAGS flags, REFIID riid, void **ppv);
    HRESULT GetPropertyStoreWithCreateObject();//(GETPROPERTYSTOREFLAGS flags, IUnknown *punkCreateObject, REFIID riid, void **ppv);
    HRESULT GetPropertyStoreForKeys();//(const PROPERTYKEY *rgKeys, UINT cKeys, GETPROPERTYSTOREFLAGS flags, REFIID riid, void **ppv);
    HRESULT GetPropertyDescriptionList();//(REFPROPERTYKEY keyType, REFIID riid, void **ppv);
    HRESULT Update();//(IBindCtx *pbc);
    HRESULT GetProperty();//(REFPROPERTYKEY key, PROPVARIANT *ppropvar);
    HRESULT GetCLSID();//(REFPROPERTYKEY key, CLSID *pclsid);
    HRESULT GetFileTime(REFPROPERTYKEY key, FILETIME *pft);
    HRESULT GetInt32();//(REFPROPERTYKEY key, int *pi);
    HRESULT GetString();//(REFPROPERTYKEY key, LPWSTR *ppsz);
    HRESULT GetUInt32();//(REFPROPERTYKEY key, ULONG *pui);
    HRESULT GetUInt64();//(REFPROPERTYKEY key, ULONGLONG *pull);
    HRESULT GetBool();//(REFPROPERTYKEY key, BOOL *pf);
}

const PROPERTYKEY PKEY_DateCreated = {
    { 0xB725F130, 0x47EF, 0x101A, [0xA5, 0xF1, 0x02, 0x60, 0x8C, 0x9E, 0xEB, 0xAC ]}, 15
};

void main()
{
    HRESULT hr;

    hr = CoInitializeEx(null, COINIT.COINIT_MULTITHREADED);
    if (FAILED(hr)) return;
    scope(exit) CoUninitialize();

    IFileOpenDialog pDialog;
    hr = CoCreateInstance(&CLSID_FileOpenDialog, null, CLSCTX_INPROC_SERVER, &IID_IFileOpenDialog, cast(void**)&pDialog);
    if (FAILED(hr)) return;
    scope(exit) pDialog.Release();

    hr = pDialog.Show(null);
    if (FAILED(hr)) return;

    IShellItem pItem;
    hr = pDialog.GetResult(&pItem);
    if (FAILED(hr)) return;
    scope(exit) pItem.Release();

    LPWSTR outPath;
    hr = pItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &outPath);
    if (FAILED(hr)) return;
    scope(exit) CoTaskMemFree(outPath);

    import std.conv;
    writeln(outPath.to!string);

    IShellItem2 pItem2;
    hr = pItem.QueryInterface(&IID_IShellItem2, cast(void**)&pItem2);
    if (FAILED(hr)) return;
    scope(exit) pItem2.Release();

    PROPERTYKEY prop = PKEY_DateCreated;
    FILETIME filetime;
    SYSTEMTIME time;
    pItem2.GetFileTime(&prop, &filetime);
    FileTimeToSystemTime(&filetime, &time);
    writefln("%s-%s-%s", time.wYear, time.wMonth, time.wDay);
}