Folder DisplayName on Windows

Brief: I am looking for the correct Declare for SHGetLocalizedName

I need to get the localized name of special folder items on Windows. Unfortunately, the FolderItem.DisplayName does not provide it. So I decided I need to resort to calling a Windows API to help out. I Googled and determined SHGetLocalizedName to be, what I need. I later found, that this function likely does not provide me with what I was looking for, but rather, where I need to look, in order to find what I need.

So I changed strategy and successfully used SHGetFileInfoW.

I still would like to know why I could not get SHGetLocalizedName to even return something other than different kinds of (Windows API) error messages. Its definition is somewhat strange, as one parameter is specified as UINT cch (no pointer, no [out]) but according to the description returning a value:

SHSTDAPI SHGetLocalizedName(
  [in]  PCWSTR pszPath,
  [out] PWSTR  pszResModule,
        UINT   cch,
  [out] int    *pidsRes
);

cch

Type: UINT

When this function returns, contains the size of the string, in WCHARs, at pszResModule.

So, how is the Declare statement for this function supposed to look like?

I created a project where you can see what I came up with, and possibly as a starting point for your own investigation. I have tried variants of what is currently there, but, as I said, never managed to get back a display name (or a path to a dll or exe I need to extract the resource from).

For those who do not want to download the project, or can spot my error in the Declare statement right away, here it is:

Declare Function SHGetLocalizedName Lib "Shell32" (pszPath As WString, ByRef szResModule As Ptr, cch As UInt32, ByRef pidsRes As Ptr) As Int32

Dim DisplayName As String
Dim pszPath As WString = path
Dim szResModule As New MemoryBlock(261*2)  // MAX_PATH + 1 as WideStr
Dim pszResModule As Ptr = szResModule
Dim cch As UInt32
Dim midsRes As New MemoryBlock(4)
Dim pidsRes As Ptr = midsRes
Dim idsRes As Int32
Dim hResult As Int32

hResult = SHGetLocalizedName(pszPath, pszResModule, cch, pidsRes)

FolderDisplayName.zip (10.5 KB)

The szResModule parameter should not be ByRef, and the pidsRes parameter should be a ByRef Int32:

  Declare Function SHGetLocalizedName Lib "Shell32" (pszPath As WString, szResModule As Ptr, ByRef cch As UInt32, ByRef pidsRes As Int32) As Int32
  
  Dim DisplayName As String
  Dim pszPath As WString = path
  Dim szResModule As New MemoryBlock(261*2)  // MAX_PATH + 1 as WideStr
  ' Dim pszResModule As Ptr = szResModule
  Dim cch As UInt32
  ' Dim midsRes As New MemoryBlock(4)
  ' Dim pidsRes As Ptr = midsRes
  Dim idsRes As Int32
  Dim hResult As Int32
  
  hResult = SHGetLocalizedName(pszPath, szResModule, cch, idsRes)

This will give you the path to the DLL file that contains the string, and the index of the string in the file. You’ll need to load the DLL and read the string:

  Declare Function SHGetLocalizedName Lib "Shell32" (pszPath As WString, szResModule As Ptr, ByRef cch As UInt32, ByRef pidsRes As Int32) As Int32
  Declare Function ExpandEnvironmentStringsW Lib "Kernel32" (ToExpand As WString, Buffer As Ptr, BufferSize As Int32) As Int32
  Declare Function LoadLibraryW Lib "Kernel32" (LibPath As WString) As Int32
  Declare Function FreeLibrary Lib "Kernel32" (hModule As Int32) As Boolean
  Declare Function LoadStringW Lib "User32" (hModule As Int32, StringID As UInt32, Buffer As Ptr, BufferSize As Int32) As Int32
  
  Dim DisplayName As String
  Dim targetpath As WString = path
  Dim buffer As New MemoryBlock(65536)  // MAX_PATH + 1 as WideStr
  Dim cch As UInt32
  Dim pidsRes, hResult As Int32
  
  ' find the .DLL that contains the localized name
  hResult = SHGetLocalizedName(targetpath, buffer, cch, pidsRes)
  
  If hResult = 0 Then
    Dim dllPath As String = buffer.WString(0)
    ' the path might have environment variables like %SystemRoot% that need to be expanded
    hResult = ExpandEnvironmentStringsW(dllPath, buffer, buffer.Size)
    dllPath = DefineEncoding(buffer.StringValue(0, hResult * 2), Encodings.UTF16)
    
    ' load the DLL
    Dim hModule As Int32 = LoadLibraryW(dllPath)
    If hModule <> 0 Then
      ' read the string identified by the pidsRes parameter
      hResult = LoadStringW(hModule, pidsRes, buffer, 0)
      If hResult <> 0 Then
        buffer = buffer.Ptr(0)
        DisplayName = DefineEncoding(buffer.StringValue(0, hResult * 2), Encodings.UTF16)
      End If
      ' unload the dll
      Call FreeLibrary(hModule)
    End If
  End If

Thank you @Andrew_Lambert , that works.

Experimenting a little more I now believe cch is actually a Int32 parameter that specifies the buffer size szResModule points to and does not return anything. It is not to be passed ByRef.

So, according to my understanding, Microsoft’s documentation for this function is wrong in many respects:

  • The main description “Retrieves the localized name of a file in a shell folder” is incorrect.
    • It does not retrieve a localized name, but rather data you can use to retrieve that localized name
    • I don’t think there are localized names for any “file in a shell folder”. Instead, shell folders may have localized names.
  • pszResModule will not get populated with “a pointer to a string resource” but instead the buffer, pszResModule points to, when you call the function, is populated with the path to a file containing the string resource, which, to be used, needs to be treated by ExpandEnvironmentStrings first.
  • cch does not contain the size of the string at pszResModule when the function returns, but instead needs to specify the size of the buffer pszResModule points to (in WCHARs, including the trailing nul-character)