Mark Williams 14 Posted October 24, 2020 I want to use the IPropertyStore interface to read media file tags. I couldn't find any Delphi examples of how to do this, but I did find a C++ example: C++ Example I am not familiar with C++, but using this example I have got working Delphi code: var Store: IPropertyStore; v: PropVariant; begin if openDialog1.Execute then begin CoInitialize(Nil); SHGetPropertyStoreFromParsingName(pWideChar(openDialog1.FileName), Nil, GPS_READWRITE, IPropertyStore, store); store.GetValue(PKEY_Music_AlbumTitle, v); Showmessage(v.bstrVal); Couninitialize; end; end; This works without complaint (needs the following added to the uses clause WinApi.ShlOBj, WinAPI.ActiveX, WinAPI.PropKey, WinAPI.PropSys;). The C++ example says there is a very important undocumented function: store->release(). The IPropertyStore does not have this function. Does anyone know what if anything I need to do to clean up the IPropertyStore in Delphi after use? Share this post Link to post
Guest Posted October 24, 2020 13 minutes ago, Mark Williams said: The C++ example says there is a very important undocumented function: store->release(). It is documented, from https://docs.microsoft.com/en-us/windows/win32/api/propsys/nn-propsys-ipropertystore Quote The IPropertyStore interface inherits from the IUnknown interface. and from https://docs.microsoft.com/en-us/windows/win32/api/unknwn/nn-unknwn-iunknown Quote Method Description IUnknown::AddRef Increments the reference count for an interface pointer to a COM object. You should call this method whenever you make a copy of an interface pointer. IUnknown::QueryInterface A helper function template that infers an interface identifier, and calls QueryInterface(REFIID,void). IUnknown::QueryInterface Retrieves pointers to the supported interfaces on an object. IUnknown::Release Decrements the reference count for an interface on a COM object. IPropertyStore should be released at its own like any Interface, just you need to refactor the code between CoInitialize and Couninitialize into new function, to make sure that local variables ("Store: IPropertyStore") in the new function is released before calling Couninitialize, that is the safe and sure way to cleanup COM in Delphi. Share this post Link to post
David Heffernan 2347 Posted October 24, 2020 You don't need to call Release in Delphi code. The compiler manages that for you. 1 Share this post Link to post
Remy Lebeau 1409 Posted October 26, 2020 (edited) On 10/24/2020 at 10:11 AM, Kas Ob. said: you need to refactor the code between CoInitialize and Couninitialize into new function, to make sure that local variables ("Store: IPropertyStore") in the new function is released before calling Couninitialize, that is the safe and sure way to cleanup COM in Delphi. Ideally yes. Or, you can just reset the variables locally before CoUninitialize() is called (don't forget to clear the PROPVARIANT, too), eg: var Store: IPropertyStore; v: PropVariant; InitHr: HResult; begin if OpenDialog1.Execute then begin InitHr := CoInitialize(nil); if FAILED(InitHr) and (InitHr <> RPC_E_CHANGED_MODE) then OleError(InitHr); try OleCheck(SHGetPropertyStoreFromParsingName(PChar(OpenDialog1.FileName), nil, GPS_READWRITE, IPropertyStore, store)); try OleCheck(store.GetValue(PKEY_Music_AlbumTitle, v)); try if v.vt = VT_EMPTY then Showmessage('Album Title not found') else Showmessage(v.bstrVal); finally PropVariantClear(v); // <-- add this end; finally store := nil; // <-- calls Release() end; finally if SUCCEEDED(InitHr) then CoUninitialize; end; end; end; However, Co(Un)Initialize() really should not be called more than 1 time per thread. For instance, in this case, they should be called at program startup/cleanup only, eg: private InitHr: HResult; procedure TMainForm.FormCreate(Sender: TObject); begin InitHr := CoInitialize(nil); if FAILED(InitHr) and (InitHr <> RPC_E_CHANGED_MODE) then OleError(InitHr); end; procedure TMainForm.FormDestroy(Sender: TObject); begin if SUCCEEDED(InitHr) then CoUninitialize; end; // the above would be better handled by moving Co(Uninitialize)() into // the main DPR directly, before TApplication is initialized, and after // TApplication.Run() is finished, respectively... ... procedure TMainForm.DoSomething; var Store: IPropertyStore; v: PropVariant; begin if OpenDialog1.Execute then begin OleCheck(SHGetPropertyStoreFromParsingName(PChar(openDialog1.FileName), nil, GPS_READWRITE, IPropertyStore, store)); OleCheck(store.GetValue(PKEY_Music_AlbumTitle, v)); try if v.vt = VT_EMPTY then Showmessage('Album Title not found') else Showmessage(v.bstrVal); finally PropVariantClear(v); // <-- still needed end; end; end; Edited October 26, 2020 by Remy Lebeau 1 Share this post Link to post