delphiwinapiwindows-10file-typefile-association

Why doesn't AssocQueryString find executable associated to image extensions?


I'm using AssocQueryString in order to get the executable associated to certain extensions.

It works good for extensions like .pdf and .txt. But I've noticed that it doesn't return anything for all image extensions I've tried (.bmp, .png, .jpg, .ico).

uses
  ShLwApi, Windows, Dialogs;

const
    // ASSOCF enumerated values mapped to integer constants
    ASSOCF_INIT_NOREMAPCLSID = $00000001;
    ASSOCF_INIT_BYEXENAME = $00000002;
    ASSOCF_OPEN_BYEXENAME = $00000002;
    ASSOCF_INIT_DEFAULTTOSTAR = $00000004;
    ASSOCF_INIT_DEFAULTTOFOLDER = $00000008;
    ASSOCF_NOUSERSETTINGS = $00000010;
    ASSOCF_NOTRUNCATE = $00000020;
    ASSOCF_VERIFY = $00000040;
    ASSOCF_REMAPRUNDLL = $00000080;
    ASSOCF_NOFIXUPS = $00000100;
    ASSOCF_IGNOREBASECLASS = $00000200;
    
var
   Buffer: array [0..1024] of char;
   BufSize: DWord;
begin
   BufSize := Sizeof(Buffer);
   Buffer[0] := #0;
   AssocQueryString(
         ASSOCF_NOTRUNCATE,
         ASSOCSTR_EXECUTABLE,
         '.bmp',
         'open',
         Buffer,
         @BufSize
   );
   ShowMessage(Buffer);
end;

Further informations:

It also works with image extensions, but only if asking for the executable associated to 'edit' instead of 'open'.

Double-clicking on a .bmp file causes the file is opened with the default Windows 10 photo viewer.

Update

Currently, my code is:

var
  Buffer: array [0..1024] of Char;
  BufSize: DWord;
  Res: HResult;
begin
  BufSize := Length(Buffer);
  Res := AssocQueryString(
    ASSOCF_REMAPRUNDLL or ASSOCF_NOTRUNCATE,
    (*ASSOCSTR_DELEGATEEXECUTE missing on Delphi 2007*) 18,
    '.bmp',
    nil,
    Buffer,
    @BufSize
  );
  If Res = S_OK then
    ShowMessage(Buffer)
  else
    ShowMessage('Error ' + IntToStr(Res) + sLineBreak + SysErrorMessage(Res));

It shows "{4ED3A719-CEA8-4BD9-910D-E252F997AFC2}". How to have the same result seen on Windows 7? (A dll or an executable filename)

Furthermore, I noticed that after changing .bmp into an non-existent (like '.abcde') return a similar result. For this I can't even know if there's an associated program.


Solution

  • As stated in comments, your machine's registrations for image file extensions are not using an application to open the files, they are using a DLL invoked by Rundll32 instead.

    Per the ASSOCSTR documentation:

    ASSOCSTR_EXECUTABLE
    An executable from a Shell verb command string. For example, this string is found as the (Default) value for a subkey such as HKEY_CLASSES_ROOT\ApplicationName\shell\Open\command. If the command uses Rundll.exe, set the ASSOCF_REMAPRUNDLL flag in the flags parameter of IQueryAssociations::GetString to retrieve the target executable.

    Caution
    Not all app associations have executables. Do not assume that an executable will always be present.

    Per the ASSOCF documentation:

    ASSOCF_REMAPRUNDLL
    Instructs IQueryAssociations methods to ignore Rundll.exe and return information about its target. Typically IQueryAssociations methods return information about the first .exe or .dll in a command string. If a command uses Rundll.exe, setting this flag tells the method to ignore Rundll.exe and return information about its target.

    Also, when calling AssocQueryString(), try setting the pszExtra parameter to NULL instead of a specific verb.

    Also, pay attention to the documentation for the last parameter of AssocQueryString():

    cchOut [in, out]
    Type: DWORD*

    A pointer to a value that, when calling the function, is set to the number of characters in the pszOut buffer. When the function returns successfully, the value is set to the number of characters actually placed in the buffer.

    You are setting your BufSize variable to a byte count not a character count. Your code is assuming Sizeof(Char) is 1, but that is only true in Delphi 2007 and earlier. In Delphi 2009 and later, Sizeof(Char) is 2 instead.

    And always check return values for errors.

    Try this:

    var
      Buffer: array [0..1024] of Char;
      BufSize: DWord;
      Res: HResult;
    begin
      BufSize := Length(Buffer);
      Res := AssocQueryString(
        ASSOCF_REMAPRUNDLL or ASSOCF_NOTRUNCATE,
        ASSOCSTR_EXECUTABLE,
        '.bmp',
        nil,
        Buffer,
        @BufSize
      );
      If Res = S_OK then
        ShowMessage(Buffer)
      else
        ShowMessage('Error ' + IntToStr(Res));
    end;
    

    Alternatively:

    var
      Buffer: string;
      BufSize: DWord;
      Res: HResult;
    begin
      BufSize := 0;
      Res := AssocQueryString(
        ASSOCF_REMAPRUNDLL or ASSOCF_NOTRUNCATE,
        ASSOCSTR_EXECUTABLE,
        '.bmp',
        nil,
        nil,
        @BufSize
      );
      if Res = S_FALSE then
      begin
        SetLength(Buffer, BufSize-1);
        Res := AssocQueryString(
          ASSOCF_REMAPRUNDLL or ASSOCF_NOTRUNCATE,
          ASSOCSTR_EXECUTABLE,
          '.bmp',
          nil,
          PChar(Buffer),
          @BufSize
        );
      end;
      If Res = S_OK then
        ShowMessage(Buffer)
      else
        ShowMessage('Error ' + IntToStr(Res));
    end;