[SHLWAPI][SHLWAPI_APITEST][SDK] Support SHGetFileDescriptionA/W (#9109)

Implementing missing features...
JIRA issue: CORE-19278
- Implement PrettifyFileDescriptionW, SHGetFileDescriptionA,
  and SHGetFileDescriptionW functions.
- Modify shlwapi.spec.
- Add prototypes into <shlwapi_apitest.h>.
- Add SHGetFileDescription testcase.
This commit is contained in:
Katayama Hirofumi MZ
2026-06-07 08:17:49 +09:00
committed by GitHub
parent ddde8c5f93
commit c59ce34030
6 changed files with 420 additions and 3 deletions
+3 -3
View File
@@ -345,8 +345,8 @@
345 stdcall -noname SHAnsiToAnsi(str ptr long)
346 stdcall -noname SHUnicodeToUnicode(wstr ptr long)
347 stdcall -noname RegDeleteValueWrapW(long wstr) advapi32.RegDeleteValueW
348 stub -noname SHGetFileDescriptionW
349 stub -noname SHGetFileDescriptionA
348 stdcall -noname SHGetFileDescriptionW(wstr wstr wstr ptr ptr)
349 stdcall -noname SHGetFileDescriptionA(str str str ptr ptr)
350 stdcall -noname GetFileVersionInfoSizeWrapW(wstr ptr)
351 stdcall -noname GetFileVersionInfoWrapW(wstr long long ptr)
352 stdcall -noname VerQueryValueWrapW(ptr wstr ptr ptr)
@@ -489,7 +489,7 @@
489 stdcall -noname GlobalAddAtomWrapW(wstr) kernel32.GlobalAddAtomW
490 stdcall -noname GlobalFindAtomWrapW(wstr) kernel32.GlobalFindAtomW
491 stdcall -noname SHGetShellKey(long long long)
492 stub -noname PrettifyFileDescriptionW
492 stdcall -noname PrettifyFileDescriptionW(wstr wstr)
493 stdcall -noname SHPropertyBag_ReadType(ptr wstr ptr long)
494 stdcall -noname SHPropertyBag_ReadStr(ptr wstr ptr long)
495 stdcall -noname SHPropertyBag_WriteStr(ptr wstr wstr)
+237
View File
@@ -15,6 +15,7 @@
#define IShellFolder_CompareIDs _disabled_IShellFolder_CompareIDs_
#include "precomp.h"
#include <winver.h>
#include <shellapi.h>
#include <shlwapi.h>
#include <shlobj_undoc.h>
@@ -957,3 +958,239 @@ NextPathW(
return (*pchEnd == L';') ? (pchEnd + 1) : pchEnd;
}
static HRESULT
_AllocValueString(
HKEY hkey,
PCWSTR pszSubKey,
PCWSTR pszValue,
PWSTR* ppszOut)
{
*ppszOut = NULL;
DWORD cbData;
LSTATUS error = SHGetValueW(hkey, pszSubKey, pszValue, NULL, NULL, &cbData);
if (error)
return HRESULT_FROM_WIN32(error);
PWSTR pszData = (PWSTR)LocalAlloc(LPTR, cbData);
if (!pszData)
return HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
error = SHGetValueW(hkey, pszSubKey, pszValue, NULL, pszData, &cbData);
if (error)
{
LocalFree(pszData);
return HRESULT_FROM_WIN32(error);
}
*ppszOut = pszData;
return S_OK;
}
/*************************************************************************
* PrettifyFileDescriptionW [SHLWAPI.492]
*
* @see SHGetFileDescriptionW
*/
VOID WINAPI
PrettifyFileDescriptionW(_Inout_ PWSTR pszTarget, _In_opt_ PCWSTR pszCutList)
{
if (!pszTarget || !*pszTarget)
return;
PWSTR pszFreeList = NULL;
PCWSTR pszList = pszCutList;
PCWSTR pszAssoc = L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileAssociation";
if (_AllocValueString(HKEY_LOCAL_MACHINE, pszAssoc, L"CutList", &pszFreeList) == S_OK)
pszList = pszFreeList;
if (pszList && *pszList)
{
for (PCWSTR pszEntry = pszList; *pszEntry; pszEntry += lstrlenW(pszEntry) + 1)
{
PWSTR pszMatch = StrRStrIW(pszTarget, NULL, pszEntry);
if (!pszMatch)
continue;
if (pszMatch[lstrlenW(pszEntry)])
continue;
*pszMatch = UNICODE_NULL;
while (pszMatch > pszTarget && pszMatch[-1] == L' ')
{
--pszMatch;
*pszMatch = UNICODE_NULL;
}
break;
}
}
if (pszFreeList)
LocalFree(pszFreeList);
}
/*************************************************************************
* SHGetFileDescriptionW [SHLWAPI.348]
*
* @see SHGetFileDescriptionA
* @see PrettifyFileDescriptionW
*/
BOOL WINAPI SHGetFileDescriptionW(
_In_ PCWSTR pszPath,
_In_opt_ PCWSTR pszVerKey,
_In_opt_ PCWSTR pszDisplayName,
_Out_opt_ PWSTR pszOut,
_Inout_ PUINT pcchOut)
{
DWORD pdwAttrs = 0;
if (!PathFileExistsAndAttributesW(pszPath, &pdwAttrs))
return FALSE;
WCHAR szPath[MAX_PATH];
StringCchCopyW(szPath, _countof(szPath), pszPath);
PVOID pvDescription = NULL;
UINT cchDescription = 0;
PVOID pvBlock = NULL;
BOOL bIsFile = !(pdwAttrs & FILE_ATTRIBUTE_DIRECTORY) &&
!PathIsUNCServerW(pszPath) &&
!PathIsUNCServerShareW(pszPath);
if (bIsFile)
{
DWORD dwHandle = 0;
DWORD cbBlock = GetFileVersionInfoSizeW(szPath, &dwHandle);
if (cbBlock)
{
pvBlock = LocalAlloc(LPTR, cbBlock);
if (pvBlock && GetFileVersionInfoW(szPath, dwHandle, cbBlock, pvBlock))
{
WCHAR szSubBlock[60];
BOOL ret = FALSE;
if (pszVerKey)
{
StringCchCopyW(szSubBlock, _countof(szSubBlock), pszVerKey);
ret = VerQueryValueW(pvBlock, szSubBlock, &pvDescription, &cchDescription);
}
if (!ret)
{
PVOID pTranslation = NULL;
UINT cbTranslation = 0;
if (VerQueryValueW(pvBlock, L"\\VarFileInfo\\Translation", &pTranslation,
&cbTranslation) && cbTranslation)
{
UINT langId = ((PWORD)pTranslation)[0];
UINT codePage = ((PWORD)pTranslation)[1];
StringCchPrintfW(szSubBlock, _countof(szSubBlock),
L"\\StringFileInfo\\%04X%04X\\FileDescription",
langId, codePage);
ret = VerQueryValueW(pvBlock, szSubBlock, &pvDescription, &cchDescription);
}
}
if (!ret)
{
// 0x0409: English (United States), 0x04B0: UTF-16 codepage
StringCchCopyW(szSubBlock, _countof(szSubBlock),
L"\\StringFileInfo\\040904B0\\FileDescription");
ret = VerQueryValueW(pvBlock, szSubBlock, &pvDescription, &cchDescription);
}
if (!ret)
{
// 0x0409: English (United States), 0x04E4: Latin 1 codepage
StringCchCopyW(szSubBlock, _countof(szSubBlock),
L"\\StringFileInfo\\040904E4\\FileDescription");
ret = VerQueryValueW(pvBlock, szSubBlock, &pvDescription, &cchDescription);
}
if (!ret)
{
// 0x0409: English (United States), 0x0000: Neutral
StringCchCopyW(szSubBlock, _countof(szSubBlock),
L"\\StringFileInfo\\04090000\\FileDescription");
ret = VerQueryValueW(pvBlock, szSubBlock, &pvDescription, &cchDescription);
}
}
}
}
PWSTR pszDescription = (PWSTR)pvDescription;
if (!pszDescription || !*pszDescription)
{
PathRemoveExtensionW(szPath);
pszDescription = PathFindFileNameW(szPath);
cchDescription = lstrlenW(pszDescription);
}
PrettifyFileDescriptionW(pszDescription, pszDisplayName);
UINT cchResult = lstrlenW(pszDescription) + 1;
if (pszOut)
{
UINT cchCopy = min(cchResult, *pcchOut);
StringCchCopyW(pszOut, cchCopy, pszDescription);
*pcchOut = cchCopy;
}
else
{
*pcchOut = cchResult;
}
if (pvBlock)
LocalFree(pvBlock);
return TRUE;
}
/*************************************************************************
* SHGetFileDescriptionA [SHLWAPI.349]
*
* @see SHGetFileDescriptionW
*/
BOOL WINAPI SHGetFileDescriptionA(
_In_ PCSTR pszPath,
_In_opt_ PCSTR pszVerKey,
_In_opt_ PCSTR pszDisplayName,
_Out_opt_ PSTR pszOut,
_Inout_ PUINT pcchOut)
{
WCHAR szPathW[MAX_PATH], szVerKeyW[MAX_PATH], szDisplayNameW[MAX_PATH], szOutW[MAX_PATH];
CHAR szOutA[MAX_PATH];
BOOL ret;
UINT cchOutW;
SHAnsiToUnicode(pszPath, szPathW, _countof(szPathW));
szPathW[_countof(szPathW) - 1] = UNICODE_NULL;
if (pszVerKey)
{
SHAnsiToUnicode(pszVerKey, szVerKeyW, _countof(szVerKeyW));
szVerKeyW[_countof(szVerKeyW) - 1] = UNICODE_NULL;
}
if (pszDisplayName)
{
SHAnsiToUnicode(pszDisplayName, szDisplayNameW, _countof(szDisplayNameW));
szDisplayNameW[_countof(szDisplayNameW) - 1] = UNICODE_NULL;
}
cchOutW = (UINT)_countof(szOutW);
ret = SHGetFileDescriptionW(szPathW, (pszVerKey ? szVerKeyW : NULL),
(pszDisplayName ? szDisplayNameW : NULL), szOutW, &cchOutW);
if (ret)
{
szOutW[_countof(szOutW) - 1] = UNICODE_NULL;
if (!pszOut)
pszOut = szOutA;
SHUnicodeToAnsi(szOutW, pszOut, *pcchOut);
if (*pcchOut > 0)
pszOut[*pcchOut - 1] = ANSI_NULL;
*pcchOut = lstrlenA(pszOut) + 1;
}
return ret;
}
@@ -21,6 +21,7 @@ list(APPEND SOURCE
PathUnExpandEnvStringsForUser.c
QuerySourceCreateFromKey.cpp
SHAreIconsEqual.c
SHGetFileDescription.c
SHGetRestriction.c
SHInvokeCommandsOnContextMenu.cpp
SHLoadIndirectString.c
@@ -0,0 +1,159 @@
/*
* PROJECT: ReactOS api tests
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* PURPOSE: Tests for SHGetFileDescriptionA/W
* COPYRIGHT: Copyright 2026 Katayama Hirofumi MZ <[email protected]>
*/
#include <apitest.h>
#include <shlwapi.h>
#include <strsafe.h>
static WCHAR g_notepad[MAX_PATH];
static WCHAR g_winver[MAX_PATH];
typedef BOOL (WINAPI *FN_SHGetFileDescriptionW)(PCWSTR, PCWSTR, PCWSTR, PWSTR, PUINT);
static FN_SHGetFileDescriptionW g_fnSHGetFileDescriptionW = NULL;
static BOOL LoadFunctions(void)
{
HMODULE hSHLWAPI = GetModuleHandleW(L"shlwapi.dll");
if (!hSHLWAPI)
hSHLWAPI = LoadLibraryW(L"shlwapi.dll");
if (!hSHLWAPI)
return FALSE;
g_fnSHGetFileDescriptionW =
(FN_SHGetFileDescriptionW)GetProcAddress(hSHLWAPI, MAKEINTRESOURCEA(348));
return g_fnSHGetFileDescriptionW != NULL;
}
static BOOL BuildSystemPaths(void)
{
WCHAR sys[MAX_PATH];
if (!GetSystemDirectoryW(sys, _countof(sys)))
return FALSE;
StringCchPrintfW(g_notepad, _countof(sys), L"%ls\\notepad.exe", sys);
StringCchPrintfW(g_winver, _countof(sys), L"%ls\\winver.exe", sys);
return PathFileExistsW(g_notepad) && PathFileExistsW(g_winver);
}
static void TEST_QuerySizeOnly(void)
{
UINT cch = 0;
BOOL ret = g_fnSHGetFileDescriptionW(g_notepad, NULL, NULL, NULL, &cch);
ok_int(ret, TRUE);
ok(cch > 0, "cch was %u\n", cch);
}
static void TEST_GetDescriptionNotepad(void)
{
WCHAR buf[256] = L"";
UINT cch = _countof(buf);
BOOL ret = g_fnSHGetFileDescriptionW(g_notepad, NULL, NULL, buf, &cch);
ok_int(ret, TRUE);
ok(buf[0] != UNICODE_NULL, "buf was empty\n");
ok(0 < cch && cch <= _countof(buf), "cch was %u\n", cch);
trace("buf: %s\n", wine_dbgstr_w(buf));
}
static void TEST_GetDescriptionWinver(void)
{
WCHAR buf[256] = L"";
UINT cch = _countof(buf);
BOOL ret = g_fnSHGetFileDescriptionW(g_winver, NULL, NULL, buf, &cch);
ok_int(ret, TRUE);
ok(buf[0] != UNICODE_NULL, "buf was empty\n");
ok(0 < cch && cch <= _countof(buf), "cch was %u\n", cch);
trace("buf: %s\n", wine_dbgstr_w(buf));
}
static void TEST_NonExistentFile(void)
{
WCHAR buf[256] = L"";
UINT cch = _countof(buf);
BOOL ret = g_fnSHGetFileDescriptionW(L"C:\\This\\Does\\Not\\Exist.exe", NULL, NULL,
buf, &cch);
ok_int(ret, FALSE);
}
static void TEST_DirectoryPath(void)
{
WCHAR sys[MAX_PATH];
GetSystemDirectoryW(sys, _countof(sys));
WCHAR buf[256] = L"";
UINT cch = _countof(buf);
BOOL ret = g_fnSHGetFileDescriptionW(sys, NULL, NULL, buf, &cch);
ok_int(ret, TRUE);
ok(_wcsicmp(buf, L"System32") == 0, "buf was %s\n", wine_dbgstr_w(buf));
}
static void TEST_TinyBuffer(void)
{
WCHAR buf[1] = { L'X' };
UINT cch = 1;
BOOL ret = g_fnSHGetFileDescriptionW(g_notepad, NULL, NULL, buf, &cch);
ok_int(ret, TRUE);
ok_int(buf[0], UNICODE_NULL);
ok_int(cch, 1);
}
static void TEST_CustomVerKey(void)
{
WCHAR buf[256] = L"";
UINT cch = _countof(buf);
BOOL ret = g_fnSHGetFileDescriptionW(g_notepad,
L"\\StringFileInfo\\040904B0\\FileDescription",
NULL, buf, &cch);
ok_int(ret, TRUE);
ok(buf[0] != UNICODE_NULL, "buf was empty\n");
}
static void TEST_InvalidVerKey(void)
{
WCHAR buf[256] = L"", bufNorm[256] = L"";
UINT cch = _countof(buf), cchNorm = _countof(bufNorm);
BOOL retInvalid = g_fnSHGetFileDescriptionW(g_notepad,
L"\\StringFileInfo\\FFFFFFFF\\NoSuchKey",
NULL, buf, &cch);
BOOL retNormal = g_fnSHGetFileDescriptionW(g_notepad, NULL, NULL, bufNorm, &cchNorm);
ok_int(retInvalid, TRUE);
ok_int(retNormal, TRUE);
ok(buf[0] != UNICODE_NULL, "buf was empty\n");
ok(lstrcmpW(buf, bufNorm) == 0, "buf was %s, bufNorm was %s\n", wine_dbgstr_w(buf),
wine_dbgstr_w(bufNorm));
}
START_TEST(SHGetFileDescription)
{
if (!LoadFunctions())
{
skip("SHGetFileDescription not found\n");
return;
}
if (!BuildSystemPaths())
{
skip("notepad.exe and/or winver.exe not found\n");
return;
}
TEST_QuerySizeOnly();
TEST_GetDescriptionNotepad();
TEST_GetDescriptionWinver();
TEST_NonExistentFile();
TEST_DirectoryPath();
TEST_TinyBuffer();
TEST_CustomVerKey();
TEST_InvalidVerKey();
}
@@ -17,6 +17,7 @@ extern void func_PathUnExpandEnvStrings(void);
extern void func_PathUnExpandEnvStringsForUser(void);
extern void func_QuerySourceCreateFromKey(void);
extern void func_SHAreIconsEqual(void);
extern void func_SHGetFileDescription(void);
extern void func_SHGetRestriction(void);
extern void func_SHInvokeCommandsOnContextMenu(void);
extern void func_SHLoadIndirectString(void);
@@ -44,6 +45,7 @@ const struct test winetest_testlist[] =
{ "PathUnExpandEnvStringsForUser", func_PathUnExpandEnvStringsForUser },
{ "QuerySourceCreateFromKey", func_QuerySourceCreateFromKey },
{ "SHAreIconsEqual", func_SHAreIconsEqual },
{ "SHGetFileDescription", func_SHGetFileDescription },
{ "SHGetRestriction", func_SHGetRestriction },
{ "SHInvokeCommandsOnContextMenu", func_SHInvokeCommandsOnContextMenu },
{ "SHLoadIndirectString", func_SHLoadIndirectString },
+18
View File
@@ -419,6 +419,22 @@ BOOL WINAPI PathFileExistsAndAttributesA(LPCSTR lpszPath, DWORD* dwAttr);
BOOL WINAPI PathFileExistsAndAttributesW(LPCWSTR lpszPath, DWORD* dwAttr);
#endif
VOID WINAPI PrettifyFileDescriptionW(_Inout_ PWSTR pszTarget, _In_opt_ PCWSTR pszCutList);
BOOL WINAPI SHGetFileDescriptionA(
_In_ PCSTR pszPath,
_In_opt_ PCSTR pszVerKey,
_In_opt_ PCSTR pszDisplayName,
_Out_opt_ PSTR pszOut,
_Inout_ PUINT pcchOut);
BOOL WINAPI SHGetFileDescriptionW(
_In_ PCWSTR pszPath,
_In_opt_ PCWSTR pszVerKey,
_In_opt_ PCWSTR pszDisplayName,
_Out_opt_ PWSTR pszOut,
_Inout_ PUINT pcchOut);
LPSTR WINAPI StrCpyNXA(LPSTR lpszDest, LPCSTR lpszSrc, int iLen);
LPWSTR WINAPI StrCpyNXW(LPWSTR lpszDest, LPCWSTR lpszSrc, int iLen);
@@ -426,10 +442,12 @@ LPWSTR WINAPI StrCpyNXW(LPWSTR lpszDest, LPCWSTR lpszSrc, int iLen);
#define PathIsValidChar PathIsValidCharW
#define StrCpyNX StrCpyNXW
#define FixSlashesAndColon FixSlashesAndColonW
#define SHGetFileDescription SHGetFileDescriptionW
#else
#define PathIsValidChar PathIsValidCharA
#define StrCpyNX StrCpyNXA
#define FixSlashesAndColon FixSlashesAndColonA
#define SHGetFileDescription SHGetFileDescriptionA
#endif
BOOL WINAPI