Jump to content
Mark Williams

IPropertyStore

Recommended Posts

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
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.

  • Thanks 1

Share this post


Link to post
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 by Remy Lebeau
  • Like 1
  • Thanks 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×