Reputation: 10911
I'd like to query if a string is in the string table prior to getting it as some of the items may not be in the string table. To make this clear, the symbol may or may not have been declared, so I cannot just specify the string id because it may or may not exist and will cause a compile error if it doesn't.
From a console application base, I tried this:
STRINGTABLE
BEGIN
IDS_APP_TITLE "ConsoleApplication4"
IDS_TEST_Home_HERE "Home here"
END
//{{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
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;
}
However, the FindResource()
call returns NULL
. What am I missing?
Edit:
For anyone interested, I wrote a way to read in id symbol names from a file and convert them to the symbol's numerical value so long as the read in symbol is somehow based on the the original symbol id that is being queried about.
Doing this would allow me to query about an additional symbol's string resource value, which can be picked from a list of predetermined choices.
You can see the solution below.
Upvotes: 4
Views: 12519
Reputation: 47962
String resources cannot be identified by a name, only by an integer value. The value indicate their position within the string table.
The resource is actually the table itself (or, more precisely, the string bundles). In some sense, individual strings in the table are not resources. The fact that you can access individual strings with with the resource API functions is really a bit a special-casing on the part of those functions.
Raymond Chen covered the internal format of string tables.
Upvotes: 3
Reputation: 10911
Ok, so if anyone is interested, here is my solution using token splicing.
#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);
}
Upvotes: 2
Reputation: 41
You can think of the string table in your resource (.rc) file as Key/Value pairs. And FindResource works by you supplying the Key and it returns the value.
The correct syntax for _T("IDS_TEST_Home_HERE") is:
MAKEINTRESOURCE(IDS_TEST_Home_HERE)
You don't need the quotes.
Upvotes: 3