arraysdelphiinterfacecom+

Defining arrays in Delphi, for use with Windows API and COM+ interfaces


In the Windows Known Folders API, IKnownFolderManager::GetFolderIds is defined as:

HRESULT GetFolderIds([out] KNOWNFOLDERID **ppKFId, [in, out] UINT *pCount);

After a successful call, ppKFId points to an array of KNOWNFOLDERIDs (GUIDs), which the caller must eventually free using CoTaskMemFree - so far, so standard.

The Delphi version in ShlObj.pas (Delphi Alexandria 11.2) is defined as:

function GetFolderIds(ppKFId: array of TKnownFolderID; var pCount: UINT): HRESULT; stdcall;

where TKnownFolderId types to a TGUID.

My question is how do I define a variable in Delphi that will satisfy both the "array of TKnownFolderID" parameter, but still be able to free the array with CoTaskMemFree (which requires a pointer)?

What I've tried:

var FolderList: array of TKnownFolderID;

Gets a protection exception because Delphi passes the contents of FolderList (nil) instead of the address of FolderList (which is what I'd expect, since a dynamic array under Delphi is a pointer).

type FolderListType = array of KNOWNFOLDERID;
var FolderList: FolderListType;

produces exactly the same result, as you'd expect.

var FolderList: array[0..0] of KNOWNFOLDERID;

gives a compiler error on the CoTaskMemFree, because array[0..0] is not a pointer. Typecasting FolderList to a pointer fails with "invalid typecast".

type
FolderListType = array of KNOWNFOLDERID;
PFolderListType = ^FolderListType;
var
FolderList: PFolderListType;

gives a compiler error on the GetFolderIDs with incompatible types.

type FolderListType = array of KNOWNFOLDERID;
var FolderList: FolderListType;
begin
  SetLength(FolderList, 1000);
...GetFolderIds(FolderList, ...

fails with a protection exception, as does GetFolderIds(FolderList[0],...

type
  FolderListType = array[0..0] of KNOWNFOLDERID;
  FolderListRec = record
  case boolean of
    true: (FL: FolderListType);
    false: (FLP: pointer);
  end;
var
  FolderListPointer: PKNOWNFOLDERID;
  FolderList: array of FolderListRec;
begin
  FolderList.FLP := @FolderListPointer;
  GetFolderIds(FolderList.FL,...

fails with a protection exception.

var
  FolderListPointer: PKNOWNFOLDERID;
  FolderList: array of KNOWNFOLDERID;
begin
  pointer(FolderList) := @FolderListPointer;
  ...GetFolderIds(FolderList, ...);
  ...
  CoTaskMemFree(FolderListPointer);
  pointer(FolderList) := nil;

retrieves the list of Known Folders successfully, but perculiarly the second parameter (count of Known Folders) is untouched. However, I really don't like typecasting dynamic arrays to pointers like this.

Overriding the supplied definition of IKnownFolderManager.GetFolderIDs to

function GetFolderIds(var ppKFId: PKnownFolderID; var pCount: UINT): HRESULT; stdcall;

and using

var FolderList: PKnownFolderID;
begin
  ...GetFolderIds(FolderList, ...);

works fine. Compiles cleanly, retrieves the list and the count perfectly, and CoTaskMemFree is happy.

So, am I, through a lack of knowledge, missing something about defining Delphi vars that would work in this situation, or is it just a bad translation from the Windows header? I work with interfaces in Delphi a lot, and frequently find what I consider to be poor translations (such as an optional out parameter being defined as "var" instead of a pointer - which means you can't pass "nil", so under Delphi it's no longer optional) but I can't see a way of making this one, as defined, work at all, so it makes me wonder if I don't know Delphi as well as I thought I did.

Thanks.


Solution

  • The declaration of GetFolderIds() in the ShlObj unit is wrong (and has been wrong since it was first introduced in D2010). It is not compatible with how the API works.

    The API allocates a new array and returns a pointer to that array to the caller. An array of ... (aka, an Open Array) parameter is NOT capable of receiving that pointer.

    The correct declaration should have been this instead:

    function GetFolderIds(var ppKFId: PKnownFolderID; var pCount: UINT): HRESULT; stdcall;
    

    And then, the proper usage would look like this:

    var
      FolderList: PKnownFolderID;
      Count: UINT;
    begin
      ...
      OleCheck(Mgr.GetFolderIds(FolderList, Count));
      ...
      CoTaskMemFree(FolderList);
    end;
    

    I have reported this bug to Embarcadero:

    RSP-40106: Declaration of IKnownFolderManager.GetFolderIds() in ShlObj.pas is wrong

    Until Embarcadero fixes this in ShlObj, you are going to have to copy the declaration of the IKnownFolderManager interface into your own code and fix the mistake there, and then use your copied interface as needed.