I have an error logging mechanism, which constructs a buffer using vsntprintf_s.
Unfortunately it so happens that if it sees a "%" symbol, then there is an exception while constructing the buffer.
TCHAR szBuffer[2000];
if (lpszFormat != NULL)
_vsntprintf_s(szBuffer, _countof(szBuffer), lpszFormat, args);
Whole function -
bool someFunction::TraceLog (LPCTSTR lpszFormat, ...)
{
va_list args = nullptr;
va_start (args, lpszFormat);
TCHAR szBuffer[2000];
if (lpszFormat != NULL)
_vsntprintf_s(szBuffer, _countof(szBuffer), lpszFormat, args);
else
_tcsncpy_s (szBuffer, _T("NULL format for TraceGen"), _TRUNCATE);
}
where, if the input string, lpszFormat contains a '%' it fails. The "%" is not meant to be an operator, but is rather for something within the string itself. E.g. Test%Test
What can be the best way to handle this?
The best way to handle this is to always have format string under your control (and by you I mean the code that you write). You must not have a format string like "Test%Test", because that is against the rules for a format string.
If you want to print that exact string then corresponding format string should be "Test%%Test".
If the contents of the string is not under your control then the format string should just be "%s" and the actual string should be given as function's next parameter.
Related
I am trying to concatenate an array of strings into a character array - but one of the strings is in a foreign language (why I need UTF8). I can see the UTF8 string in their appropriate language in the debugger (Visual Studio) after I read it from the database and put it into the wxString array, but when I try to concatenate the string to the array, t never gets put in there.
I have tried variable.mb_str()
variable.mb.str().data(). Neither seems to work in the strcat
for my Language data. The other data is concatenated fine. All of the data comes from a MariaDB database call.
int i, numRows;
wxString query;
wxString sortby;
wxString group_list;
wxString *stringGroups;
char holdString[400];
/* Try UTF Force */
query.Printf(_("set names 'utf8'"));
mysql_query(mDb, query.mb_str());
result = mysql_store_result(mDb);
mysql_free_result(result);
query.Printf(_("select GROUP_NAME from USER_PERMS where USER_NAME =
\"%s\"
ORDER BY GROUP_NAME "), riv_getuser().c_str() );
mysql_query(mDb, query.mb_str());
result = mysql_store_result(mDb);
numRows = mysql_num_rows(result);
stringGroups = new wxString[numRows + 1];
i = 0;
while ((row = mysql_fetch_row(result)))
{
stringGroups[i] = wxString(row[0], wxConvUTF8);
i++;
}
mysql_free_result(result);
i = 0;
strcpy (holdString,"IN (\'");
while (i < numRows)
{
if (i != 0) strcat(holdString, "\', \'");
strcat(holdString, (const char *)stringGroups[i].mb_str().data());
i++;
}
strcat (holdString," \')");
-- END OF CODE --
--ACTUAL stringGroup that fails -- Debugger Watch Output
stringGroups[2] {m_impl=L"文字化け"...
I expect to get:
IN ( 'test' , 'test' , '文字化け' )
what I get
IN ( 'test','test2','' )
Don't use strcpy() and strcat() with wxString, this is just needlessly error-prone. If you use wxString in the first place, build the entire string you need and then utf8_str() method to get the buffer containing UTF-8 string contents which you can then pass to whatever function you need.
Do keep in mind that this buffer is temporary, so you can't rely on it continuing to exist if you don't make a copy of it or at least extend its lifetime, i.e.
auto const& buf = some_wx_string.utf8_str();
... now you can use buf.data() safely until the end of scope ...
To get UTF8 from wxString you need to call ToUTF8(). Similarly, for getting UTF8 into wxString there is FromUTF8(). Both are members of wxString and documented.
wxString::mb_str() converts to a multi-byte string in your current locale. Presumably the characters in your string aren't representable in your locale so the conversion fails and an empty string is returned.
You should pass wxConvUTF8 as a parameter or simply call utf8_str or ToUTF8 instead.
I am writing a program to edit the windows registry key by C++, but when I try to pass a string value to library function RegSetValueEx(), there is a file start with TEXT() which could only be hardcode value in it.
Parts of my code:
string region;
string excelserver_type;
string keyname = region + excelserver_type;
if (RegSetValueEx(key64, TEXT("XXXXXXXXX"), 0, REG_SZ, (LPBYTE)TEXT("XXXXXXXXXX"), 100) != ERROR_SUCCESS)
{
RegCloseKey(key);
cout << "Unable to set registry value in HKEY_LOCAL_MACHINE\\Software" << endl;
}
When I try to replace "XXXXXXXX" by keyname, it gives me an error. How do I pass value of keyname in RegSetValueEx()?
You need to use the std::wstring type instead. This will give you a wide (Unicode) string, based on the wchar_t type, which is how Windows stores strings internally.
Then, you can simply use the c_str() member function to retrieve a pointer to a C-style string, and pass this directly to the RegSetValueEx function. The size() member function gives you the length of the string, which you can pass as the cbData parameter, except for two caveats:
cbData expects the length of the string to include the terminating NUL character, so you will need to add 1 to the length returned by size().
cbData expects the size of the string in bytes, not the number of characters, so for a wide string, you will need to multiply the value returned by size() by the length of a wchar_t.
bool SetStringValue(HKEY hRegistryKey,
const std::wstring& valueName,
const std::wstring& data)
{
assert(hRegistryKey != nullptr);
return (RegSetValueExW(hRegistryKey,
valueName.c_str(),
0,
REG_SZ,
(LPBYTE)(data.c_str()),
(data.size() + 1) * sizeof(wchar_t)) == ERROR_SUCCESS);
}
If you absolutely have to use narrow (ANSI) strings (and you shouldn't, because you're interfacing directly with the operating system here, not working with user data), you can do the same thing but explicitly call the ANSI version of RegSetValueEx, which has an A suffix. Here, you still need to add 1 to the length, but the size in bytes is equivalent to the number of characters, so no scaling is necessary.
bool SetStringValue_ANSI(HKEY hRegistryKey,
const std::string& valueName,
const std::string& data)
{
assert(hRegistryKey != nullptr);
return (RegSetValueExA(hRegistryKey,
valueName.c_str(),
0,
REG_SZ,
(LPBYTE)(data.c_str()),
data.size() + 1) == ERROR_SUCCESS);
}
Internally, RegSetValueExA will convert the string to Unicode and then perform the same task as RegSetValueExW.
I am trying to get a substring from a CString using C++. For that I am using strstr function. But it is not working at al
CString str = m_sectionDataList->GetNext(pos);
char* chToMatch = (char*)(LPCTSTR)str;
char *match = "=";
//char * sMatched = strstr(ch, match);
if (strstr(match, chToMatch) != NULL) {
MessageBox(NULL, str, L"Done", 1);
}
You are passing arguments in the incorrect order. strstr expects first argument to be scanned string, and second should be a match. Right now you are searching your target string in the one byte = template, which will most certainly fail.
Finally I've found it. Need to use a macro of C++ and you will found it converted.
CT2A ascii(str, CP_UTF8);
now you can just access it using ascii.m_psz and its buffer also.
I'm trying to understand why a segmentation fault (SIGSEGV) occurs during the execution of this piece of code. This error occurs when testing the condition specified in the while instruction, but it does not occur at the first iteration, but at the second iteration.
LPTSTR arrayStr[STR_COUNT];
LPTSTR inputStr;
LPTSTR str;
// calls a function from external library
// in order to set the inputStr string
set_input_str(param1, (char*)&inputStr, param3);
str = inputStr;
while( *str != '\0' )
{
if( debug )
printf("String[%d]: %s\n", i, (char*)str);
arrayStr[i] = str;
str = str + strlen((char*)str) + 1;
i++;
}
After reading this answer, I have done some research on the internet and found this article, so I tried to modify the above code, using this piece of code read in this article (see below). However, this change did not solve the problem.
for (LPTSTR pszz = pszzStart; *pszz; pszz += lstrlen(pszz) + 1) {
... do something with pszz ...
}
As assumed in this answer, it seems that the code expects double null terminated arrays of string. Therefore, I wonder how I could check the contents of the inputStr string, in order to check if it actually contains only one null terminator char.
NOTE: the number of characters in the string printed from printf instruction is twice the value returned by the lstrlen(str) function call at the first iteration.
OK, now that you've included the rest of the code it is clear that it is indeed meant to parse a set of consecutive strings. The problem is that you're mixing narrow and wide string types. All you need to do to fix it is change the variable definitions (and remove the casts):
char *arrayStr[STR_COUNT];
char *inputStr;
char *str;
// calls a function from external library
// in order to set the inputStr string
set_input_str(param1, &inputStr, param3);
str = inputStr;
while( *str != '\0' )
{
if( debug )
printf("String[%d]: %s\n", i, str);
arrayStr[i] = str;
str = str + strlen(str) + 1;
i++;
}
Specifically, the issue was occurring on this line:
while( *str != '\0' )
since you hadn't cast str to char * the comparison was looking for a wide nul rather than a narrow nul.
str = str + strlen(str) + 1;
You go out of bounds, change to
str = str + 1;
or simply:
str++;
Of course you are inconsistently using TSTR and strlen, the latter assuming TCHAR = char
In any case, strlen returns the length of the string, which is the number of characters it contains not including the nul character.
Your arithmetic is out by one but you know you have to add one to the length of the string when you allocate the buffer.
Here however you are starting at position 0 and adding the length which means you are at position len which is the length of the string. Now the string runs from offset 0 to offset len - 1 and offset len holds the null character. Offset len + 1 is out of bounds.
Sometimes you might get away with reading it, if there is extra padding, but it is undefined behaviour and here you got a segfault.
This looks to me like code that expects double null terminated arrays of strings. I suspect that you are passing a single null terminated string.
So you are using something like this:
const char* inputStr = "blah";
but the code expects two null terminators. Such as:
const char* inputStr = "blah\0";
or perhaps an input value with multiple strings:
const char* inputStr = "foo\0bar\0";
Note that these final two strings are indeed double null terminated. Although only one null terminator is written explicitly at the end of the string, the compiler adds another one implicitly.
Your question edit throws a new spanner in the works? The cast in
strlen((char*)str)
is massively dubious. If you need to cast then the cast must be wrong. One wonders what LPTSTR expands to for you. Presumably it expands to wchar_t* since you added that cast to make the code compile. And if so, then the cast does no good. You are lying to the compiler (str is not char*) and lying to the compiler never ends well.
The reason for the segmentation fault is already given by Alter's answer. However, I'd like to add that the usual style of parsing a C-style string is more elegant and less verbose
while (char ch = *str++)
{
// other instructions
// ...
}
The scope of ch is only within in the body of the loop.
Aside: Either tag the question as C or C++ but not both, they're different languages.
When I use sscanf() in the following code, it is taking the whole line and placing it in the first string for some reason, and I do not see any problems with it. The output from Msg() is coming out like PatchVersion=1.1.1.5 = °?¦§-
The file looks like this (except each is a new line, not sure why it shows as one on StackOverflow)
PatchVersion=1.1.1.5
ProductName=tf
appID=440
Code:
bool ParseSteamFile()
{
FileHandle_t file;
file = filesystem->Open("steam.inf", "r", "MOD");
if(file)
{
int size = filesystem->Size(file);
char *line = new char[size + 1];
while(!filesystem->EndOfFile(file))
{
char *subLine = filesystem->ReadLine(line, size, file);
if(strstr(subLine, "PatchVersion"))
{
char *name = new char[32];
char *value = new char[32];
sscanf(subLine, "%s=%s", name, value);
Msg("%s = %s\n", name, value);
}
else if(strstr(subLine, "ProductName"))
{
char *name = new char[32];
char *value = new char[32];
sscanf(subLine, "%s=%s", name, value);
Msg("%s = %s\n", name, value);
}
}
return true;
}
else
{
Msg("Failed to find the Steam Information File (steam.inf)\n");
filesystem->Close(file);
return false;
}
filesystem->Close(file);
return false;
}
One solution would be to use the (rather underused, in my opinion) character group format specifier:
sscanf(subLine, "%[^=]=%s", name, value);
Also, you should use the return value of sscanf() to verify that you did indeed get both values, before relying on them.
%s is "greedy", i.e. it keeps reading until it hits whitspace (or newline, or EOF). The '=' character is none of these, so sscanf just carries on, matching the entire line for the first %s.
You're probably better off using (for example) strtok(), or a simple character-by-character parser.
From the manpage of scanf, regarding %s:
Matches a sequence of non-white-space characters; the next pointer must be a pointer to character array that is long enough to hold the input sequence and the terminating null character ('\0'), which is added automatically. The input string stops at white space or at the maximum field width, whichever occurs first.
%s will read characters until a whitespace is encountered. Since there are no whitespaces before/after the '=' sign, the entire string is read.
Your use of arrays is very poor C++ technique. You might use streams but if you insist on using sscanf and arrays then at least use vector to manage your memory.
You might print out exactly what is in subLine and what Msg does. Is this your own code because I have never heard of FileHandle_t. I do know that it has a method that returns a char* that presumably you have to manage.
Regular expressions are part of the boost library and will soon be in the standard library. They are fairly "standard" and you might do well to use it to parse your line.
(boost::regex or tr1::regex if you have it, VS2008 has it)