如何在 win32 或 MFC 中按名称获取字符串资源?
How do I get a string resource by name in win32 or MFC?
我想在获取某个字符串之前查询它是否在字符串 table 中,因为某些项目可能不在字符串 table 中。 为了说明这一点,符号可能已声明也可能未声明,所以我不能只指定字符串 ID,因为它可能存在也可能不存在,如果不存在将导致编译错误' t.
从控制台应用程序库,我试过这个:
ConsoleApplication4.rc:
STRINGTABLE
BEGIN
IDS_APP_TITLE "ConsoleApplication4"
IDS_TEST_Home_HERE "Home here"
END
Resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by ConsoleApplication4.rc
//
#define IDS_APP_TITLE 103
#define IDS_TEST_Home_HERE 104
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 101
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
ConsoleApplication4.cpp:
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
HMODULE hModule = ::GetModuleHandle(NULL);
if (hModule != NULL)
{
// initialize MFC and print and error on failure
if (!AfxWinInit(hModule, NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
_tprintf(_T("Fatal Error: MFC initialization failed\n"));
nRetCode = 1;
}
else
{
// TODO: code your application's behavior here.
}
}
else
{
// TODO: change error code to suit your needs
_tprintf(_T("Fatal Error: GetModuleHandle failed\n"));
nRetCode = 1;
}
// vvvv My code below here vvvv:
HRSRC hResource = FindResource(hModule, _T("IDS_TEST_Home_HERE"), RT_STRING);
HGLOBAL hgString = LoadResource(hModule, hResource);
LPCTSTR string = (LPCTSTR)LockResource(hgString);
cout << string << endl;
// ^^^^ My code above here ^^^^
return nRetCode;
}
然而,FindResource()
调用returnsNULL
。我错过了什么?
编辑:
对于任何感兴趣的人,我写了一种方法来从文件中读入 id 符号名称并将它们转换为符号的数值,只要读入的符号以某种方式基于正在查询的原始符号 id关于。
这样做可以让我查询附加符号的字符串资源值,可以从预先确定的选项列表中选择。
您可以看到解决方案。
您可以将资源 (.rc) 文件中的字符串 table 视为 Key/Value 对。 FindResource 的工作原理是您提供密钥,它 returns 值。
_T("IDS_TEST_Home_HERE") 的正确语法是:
MAKEINTRESOURCE(IDS_TEST_Home_HERE)
您不需要引号。
字符串资源无法通过名称识别,只能通过整数值识别。该值表示它们在字符串中的位置 table.
资源实际上是 table 本身(或者更准确地说,是字符串包)。在某种意义上,table 中的单个字符串不是资源。您可以使用资源 API 函数访问单个字符串这一事实对于这些函数来说确实有点特殊。
好的,所以如果有人感兴趣,这是我使用令牌拼接的解决方案。
#pragma once
#include <vector>
#include <algorithm>
//////////////////////////////////////////////////////////////////////////////
// This header is to allow the capturing of macro token values and is
// accomplished by token splicing.
//
// To be able to determine what the id value is, one must know what the token
// could be ahead of time, even if it is one of a few choices. This can be
// accomplished by splicing a token together so that you can limit your search.
// Example:
//
// #define ID_X 1
// #define IDS_1__ID_X__ 5
//
// // x must be be passed with '__' appended to it to prevent it from being
// // expanded. This only works if no macro with that name already exists.
// //
// // NOTE: This is an intermediate macro which does the heavy lifting, and
// // is called by one or more interface macros.
// #define GET_VALUE(x, idAsString) \
// CMacroIdAsStringToValue().add(CString("IDS_1__")+T_STRINGIZE(x), T_STRINGIZE(CONCAT(IDS_1__, x))) \
// .getValue(idAsString)
//
// // This is an example of an interface macro, which will get the associated
// // value and possibly do other things with the original id.
// #define GET_ASSOCIATED_VALUE(x, idAsString) GET_VALUE(x##__, idAsString)
//
// int x = GET_ASSOCIATED_VALUE(ID_X, _T("IDS_1__ID_X__")); // Will return 5.
// int y = GET_ASSOCIATED_VALUE(ID_X, _T("IDS_1__ID_Y__")); // Will cause an assertion and return -1.
// int y = GET_ASSOCIATED_VALUE(ID_Y, _T("IDS_1__ID_Y__")); // Will cause an assertion and return -1.
//
// The 2nd parameter can be read in from somewhere else, the 1st parameter is
// a hint as to what the 2nd parameter could be.
//
// To get a string from the string table, replace .getValue(idAsString) with
// .getResourceString(idAsString).
//
// If you don't want the code to assert if there is no match found, pass a false
// value to the `CMacroIdAsStringToValue` constructor.
//
// Adding multiple token names to search can be done by stringing multiple add()
// calls together with the dot operator.
//
// - Written by Adrian Hawryluk, Jan 2015 and is hereby given to the public domain.
#ifdef UNICODE
// Expands x and converts it to a wide character array
#define T_STRINGIZE_IMPL(x) L#x
#else
// Expands x and converts it to a character array. NOT to be used directly.
#define T_STRINGIZE_IMPL(x) #x
#endif
// At this level x is not expanded, call IMPL version to have it expanded prior to operating on them.
#define T_STRINGIZE(x) T_STRINGIZE_IMPL(x)
// Token splice x and y together. NOT to be used directly.
#define CONCAT_IMPL(x, y) x##y
// At this level, x and y are not expanded, call IMPL version to have it expanded prior to operating on them.
#define CONCAT(x, y) CONCAT_IMPL(x,y)
class CMacroIdAsStringToValue
{
struct IdToValue
{
CString id; // This is a macro name stored as ASCII.
int value; // This is the macro's value.
};
// List of macros to check against which got expanded into a number.
std::vector<IdToValue> searchList;
#ifdef DEBUG
struct IdFails
{
CString id;
CString expandedId;
};
// List of macros to check against which failed to expand into a number.
// Used only for debugging if something went wrong. If a matching ID is
// NOT found, and there is an item in here, you can see what the token
// got expanded to. If it looks ok, then you forgot to add a macro by
// that name into the resource table.
//
// See also CMacroIdAsStringToValue::testId()
std::vector<IdFails> failedItems;
bool assertOnNoMatch;
#endif
public:
CMacroIdAsStringToValue(bool assertOnNoMatch = true)
#ifdef DEBUG
: assertOnNoMatch(assertOnNoMatch)
#endif
{
}
// Test to see if the macro string got expanded correctly. If an
// assertion is tripped here then you probably attempted to get use
// RIBBON_PANEL_TEXT_ICON() or RIBBON_CATEGORY_TEXT() macros without
// defining CATEGORY macro or passed the wrong panel bareword..
void testId(LPCTSTR macro)
{
static TCHAR const invalidId[] = _T("IDS_RIBBON_TAB_CATEGORY__GET_ELEMENT_CATEGORY");
ASSERT(_tcsncmp(macro, invalidId, _tcslen(invalidId)));
}
// If macroValue has a length < 8, it is a number (macro names will be much greater than 7 characters long)
template <int N>
typename std::enable_if<(N<8), CMacroIdAsStringToValue&>::type add(LPCTSTR macro, TCHAR const (¯oValue)[N])
{
testId(macro);
ASSERT(_istdigit(macroValue[0]));
// macro results in a value
searchList.push_back({ macro, _ttoi(macroValue) });
return *this;
}
// In Release mode, this function prevents the macroValue from being used and will stop it from being added to the .DATA segment.
template <int N>
typename std::enable_if<(N >= 8), CMacroIdAsStringToValue&>::type add(LPCTSTR macro, TCHAR const (¯oValue)[N])
{
#ifdef DEBUG
testId(macro);
failedItems.push_back({ macro, macroValue });
#endif
return *this;
}
// gets the macro's value given the macro's name as a string
//
// NOTE: id is empty? Then the function to get the id returned nothing.
int getValue(LPCTSTR id) const
{
auto result = find_if(searchList.begin(), searchList.end(), [id](IdToValue const& idToValue)
{
return _tcscmp(idToValue.id, id) == 0;
});
if (result == searchList.end())
{
#ifdef DEBUG
if (assertOnNoMatch)
{
ASSERT(FALSE);
}
#endif
return -1;
}
else
{
return result->value;
}
}
// Gets the string associated and hotkey associated with the id seperated by LF ('\n').
//
// NOTE: id is empty? Then the function to get the id returned nothing.
CString getResourceString(LPCTSTR id) const
{
UINT stringId = getValue(id);
CString string;
VERIFY(string.LoadString(stringId));
if (string == _T("*BLANK*"))
{
return "";
}
return string;
}
};
// Takes a string and returns another string that consists of everything in
// the first string that is to the left of the 1st '\n' character. If no such
// character exists in the string, return the whole string.
inline CString leftOfLF(CString string)
{
int index = string.Find('\n');
if (index == -1)
{
return string;
}
return string.Left(index);
}
// Takes a string and returns another string that consists of everything in
// the first string that is to the right of the 1st '\n' character. If no such
// character exists in the string, return an empty string.
inline CString rightOfLF(CString string)
{
int index = string.Find('\n');
if (index == -1)
{
return "";
}
return string.Mid(index + 1);
}
我想在获取某个字符串之前查询它是否在字符串 table 中,因为某些项目可能不在字符串 table 中。 为了说明这一点,符号可能已声明也可能未声明,所以我不能只指定字符串 ID,因为它可能存在也可能不存在,如果不存在将导致编译错误' t.
从控制台应用程序库,我试过这个:
ConsoleApplication4.rc:
STRINGTABLE
BEGIN
IDS_APP_TITLE "ConsoleApplication4"
IDS_TEST_Home_HERE "Home here"
END
Resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by ConsoleApplication4.rc
//
#define IDS_APP_TITLE 103
#define IDS_TEST_Home_HERE 104
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 101
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
ConsoleApplication4.cpp:
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
HMODULE hModule = ::GetModuleHandle(NULL);
if (hModule != NULL)
{
// initialize MFC and print and error on failure
if (!AfxWinInit(hModule, NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
_tprintf(_T("Fatal Error: MFC initialization failed\n"));
nRetCode = 1;
}
else
{
// TODO: code your application's behavior here.
}
}
else
{
// TODO: change error code to suit your needs
_tprintf(_T("Fatal Error: GetModuleHandle failed\n"));
nRetCode = 1;
}
// vvvv My code below here vvvv:
HRSRC hResource = FindResource(hModule, _T("IDS_TEST_Home_HERE"), RT_STRING);
HGLOBAL hgString = LoadResource(hModule, hResource);
LPCTSTR string = (LPCTSTR)LockResource(hgString);
cout << string << endl;
// ^^^^ My code above here ^^^^
return nRetCode;
}
然而,FindResource()
调用returnsNULL
。我错过了什么?
编辑:
对于任何感兴趣的人,我写了一种方法来从文件中读入 id 符号名称并将它们转换为符号的数值,只要读入的符号以某种方式基于正在查询的原始符号 id关于。
这样做可以让我查询附加符号的字符串资源值,可以从预先确定的选项列表中选择。
您可以看到解决方案
您可以将资源 (.rc) 文件中的字符串 table 视为 Key/Value 对。 FindResource 的工作原理是您提供密钥,它 returns 值。
_T("IDS_TEST_Home_HERE") 的正确语法是:
MAKEINTRESOURCE(IDS_TEST_Home_HERE)
您不需要引号。
字符串资源无法通过名称识别,只能通过整数值识别。该值表示它们在字符串中的位置 table.
资源实际上是 table 本身(或者更准确地说,是字符串包)。在某种意义上,table 中的单个字符串不是资源。您可以使用资源 API 函数访问单个字符串这一事实对于这些函数来说确实有点特殊。
好的,所以如果有人感兴趣,这是我使用令牌拼接的解决方案。
#pragma once
#include <vector>
#include <algorithm>
//////////////////////////////////////////////////////////////////////////////
// This header is to allow the capturing of macro token values and is
// accomplished by token splicing.
//
// To be able to determine what the id value is, one must know what the token
// could be ahead of time, even if it is one of a few choices. This can be
// accomplished by splicing a token together so that you can limit your search.
// Example:
//
// #define ID_X 1
// #define IDS_1__ID_X__ 5
//
// // x must be be passed with '__' appended to it to prevent it from being
// // expanded. This only works if no macro with that name already exists.
// //
// // NOTE: This is an intermediate macro which does the heavy lifting, and
// // is called by one or more interface macros.
// #define GET_VALUE(x, idAsString) \
// CMacroIdAsStringToValue().add(CString("IDS_1__")+T_STRINGIZE(x), T_STRINGIZE(CONCAT(IDS_1__, x))) \
// .getValue(idAsString)
//
// // This is an example of an interface macro, which will get the associated
// // value and possibly do other things with the original id.
// #define GET_ASSOCIATED_VALUE(x, idAsString) GET_VALUE(x##__, idAsString)
//
// int x = GET_ASSOCIATED_VALUE(ID_X, _T("IDS_1__ID_X__")); // Will return 5.
// int y = GET_ASSOCIATED_VALUE(ID_X, _T("IDS_1__ID_Y__")); // Will cause an assertion and return -1.
// int y = GET_ASSOCIATED_VALUE(ID_Y, _T("IDS_1__ID_Y__")); // Will cause an assertion and return -1.
//
// The 2nd parameter can be read in from somewhere else, the 1st parameter is
// a hint as to what the 2nd parameter could be.
//
// To get a string from the string table, replace .getValue(idAsString) with
// .getResourceString(idAsString).
//
// If you don't want the code to assert if there is no match found, pass a false
// value to the `CMacroIdAsStringToValue` constructor.
//
// Adding multiple token names to search can be done by stringing multiple add()
// calls together with the dot operator.
//
// - Written by Adrian Hawryluk, Jan 2015 and is hereby given to the public domain.
#ifdef UNICODE
// Expands x and converts it to a wide character array
#define T_STRINGIZE_IMPL(x) L#x
#else
// Expands x and converts it to a character array. NOT to be used directly.
#define T_STRINGIZE_IMPL(x) #x
#endif
// At this level x is not expanded, call IMPL version to have it expanded prior to operating on them.
#define T_STRINGIZE(x) T_STRINGIZE_IMPL(x)
// Token splice x and y together. NOT to be used directly.
#define CONCAT_IMPL(x, y) x##y
// At this level, x and y are not expanded, call IMPL version to have it expanded prior to operating on them.
#define CONCAT(x, y) CONCAT_IMPL(x,y)
class CMacroIdAsStringToValue
{
struct IdToValue
{
CString id; // This is a macro name stored as ASCII.
int value; // This is the macro's value.
};
// List of macros to check against which got expanded into a number.
std::vector<IdToValue> searchList;
#ifdef DEBUG
struct IdFails
{
CString id;
CString expandedId;
};
// List of macros to check against which failed to expand into a number.
// Used only for debugging if something went wrong. If a matching ID is
// NOT found, and there is an item in here, you can see what the token
// got expanded to. If it looks ok, then you forgot to add a macro by
// that name into the resource table.
//
// See also CMacroIdAsStringToValue::testId()
std::vector<IdFails> failedItems;
bool assertOnNoMatch;
#endif
public:
CMacroIdAsStringToValue(bool assertOnNoMatch = true)
#ifdef DEBUG
: assertOnNoMatch(assertOnNoMatch)
#endif
{
}
// Test to see if the macro string got expanded correctly. If an
// assertion is tripped here then you probably attempted to get use
// RIBBON_PANEL_TEXT_ICON() or RIBBON_CATEGORY_TEXT() macros without
// defining CATEGORY macro or passed the wrong panel bareword..
void testId(LPCTSTR macro)
{
static TCHAR const invalidId[] = _T("IDS_RIBBON_TAB_CATEGORY__GET_ELEMENT_CATEGORY");
ASSERT(_tcsncmp(macro, invalidId, _tcslen(invalidId)));
}
// If macroValue has a length < 8, it is a number (macro names will be much greater than 7 characters long)
template <int N>
typename std::enable_if<(N<8), CMacroIdAsStringToValue&>::type add(LPCTSTR macro, TCHAR const (¯oValue)[N])
{
testId(macro);
ASSERT(_istdigit(macroValue[0]));
// macro results in a value
searchList.push_back({ macro, _ttoi(macroValue) });
return *this;
}
// In Release mode, this function prevents the macroValue from being used and will stop it from being added to the .DATA segment.
template <int N>
typename std::enable_if<(N >= 8), CMacroIdAsStringToValue&>::type add(LPCTSTR macro, TCHAR const (¯oValue)[N])
{
#ifdef DEBUG
testId(macro);
failedItems.push_back({ macro, macroValue });
#endif
return *this;
}
// gets the macro's value given the macro's name as a string
//
// NOTE: id is empty? Then the function to get the id returned nothing.
int getValue(LPCTSTR id) const
{
auto result = find_if(searchList.begin(), searchList.end(), [id](IdToValue const& idToValue)
{
return _tcscmp(idToValue.id, id) == 0;
});
if (result == searchList.end())
{
#ifdef DEBUG
if (assertOnNoMatch)
{
ASSERT(FALSE);
}
#endif
return -1;
}
else
{
return result->value;
}
}
// Gets the string associated and hotkey associated with the id seperated by LF ('\n').
//
// NOTE: id is empty? Then the function to get the id returned nothing.
CString getResourceString(LPCTSTR id) const
{
UINT stringId = getValue(id);
CString string;
VERIFY(string.LoadString(stringId));
if (string == _T("*BLANK*"))
{
return "";
}
return string;
}
};
// Takes a string and returns another string that consists of everything in
// the first string that is to the left of the 1st '\n' character. If no such
// character exists in the string, return the whole string.
inline CString leftOfLF(CString string)
{
int index = string.Find('\n');
if (index == -1)
{
return string;
}
return string.Left(index);
}
// Takes a string and returns another string that consists of everything in
// the first string that is to the right of the 1st '\n' character. If no such
// character exists in the string, return an empty string.
inline CString rightOfLF(CString string)
{
int index = string.Find('\n');
if (index == -1)
{
return "";
}
return string.Mid(index + 1);
}