WinAPI File In-/Output with std::strings instead of char arrays? - c++

due to performance reasons I didn't feel like using fstream for just one time. Seems like a very bad idea to use WinAPI functions with a std::string instead of a plain char array. All in all I would like you to tell me why the following snippet just won't work (empty stBuffer stays empty) and what I'd need to do to get it fixed.
Thanks in advance!
std::size_t Get(const std::string &stFileName, std::string &stBuffer)
{
HANDLE hFile = ::CreateFileA(stFileName.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD dwBytesRead = 0;
if(hFile != INVALID_HANDLE_VALUE)
{
DWORD dwFileSize = ::GetFileSize(hFile, NULL);
stBuffer.reserve(dwFileSize + 1);
::ReadFile(hFile, &stBuffer[0], dwFileSize, &dwBytesRead, NULL);
stBuffer[dwFileSize] = '\0';
::CloseHandle(hFile);
}
return dwBytesRead;
}

Because a std::string can contain embedded '\0' characters, it has to keep track of its own length in a separate way.
Your problem is that std::string::reserve() does not change the length of the string. It just pre-allocates some memory for the string to grow into. The solution is to use std::string::resize() and let the WinAPI function overwrite the string contents.
As a side-note: Currently, it is not guaranteed that std::string uses a contiguous buffer, but to my knowledge, all current implementations do use a contiguous buffer and it will be a requirement in the next standard.

Consider difference between reserve() and resize() members. So the solution would be:
stBuffer.resize(dwFileSize + 1);

Related

Windows API ReadFile() skips one out of every two characters

My aim is to read all the text located in a file. For some reason whenever I read from the file and print the result (drawText), the buffer seems to be skipping one character every two positions. HELLO will become HLO and SCAVENGER becomes SAEGR.
This is for Windows API. I wonder if CreateFile() and ReadFile() are just fine and whether it's something else causing the issue.
void init(HDC hdc)
{
HANDLE hFile;
LPCSTR fileName = "c:\\Users\\kanaa\\Desktop\\code\\HW2_StarterCode\\words.txt";
hFile = CreateFileA(fileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD dwFileSize = GetFileSize(hFile, NULL);
DWORD dwBytesRead;
WCHAR* buffer = new WCHAR[dwFileSize / 2 + 1];
buffer[dwFileSize / 2] = 0;
bool read = ReadFile(hFile, buffer, dwFileSize, &dwBytesRead, NULL);
std::wstring wstr(buffer);
std::string str(wstr.begin(), wstr.end());
delete[] buffer;
CloseHandle(hFile);
if (read) parse(str, hdc);
}
void parse(std::string word, HDC hdc)
{
std::string to = word;
std::wstring wword = std::wstring(to.begin(), to.end());
const WCHAR* wcword = wword.c_str();
Graphics graphics(hdc);
drawText(&graphics, wcword);
}
The problem was the WCHAR buffer. Below are the corrections
CHAR* buffer = new CHAR[dwFileSize/sizeof(char) + 1];
bool read = ReadFile(hFile, buffer, dwFileSize, &dwBytesRead, NULL);
buffer[dwBytesRead] = 0;
You are processing the file data using a wchar_t[] buffer. wchar_t is 2 bytes in size on Windows. So, in the statement:
std::string str(wstr.begin(), wstr.end());
You are iterating through the file data 2 bytes at a time, interpreting each byte pair as a single wchar_t that gets truncated to a 1-byte char, discarding the other byte. That is why your str ends up skipping every other character.
Process the file data using a char[] buffer instead. However, there are easier ways to read 7/8-bit file data into a std::string.
Lastly, in this statement:
std::wstring wword = std::wstring(to.begin(), to.end());
This is not the correct way to convert a std::string to a std::wstring. All you are doing is iterating through the chars converting each one as-is into a 2-byte wchar_t. Windows APIs expect wchar_t strings to be encoded in UTF-16, which your code is not converting to. You need to use MultiByteToWideChar(), std::wstring_convert, or other equivalent Unicode library call to perform that conversion. In which case, you first need to know the encoding of the source file in order to convert it to Unicode correctly.

C++ Overwriting Contents of a Handle

I am trying to get a number from a HANDLE file, store it in an int, and possibly replace it in the same file. My code right now looks like this
HANDLE numFile = INVALID_HANDLE_VALUE; //Just in case file not found
numFile = CreateFile("numFile.txt",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
ReadFile(numFile, input, sizeof(char), &bytesRead, NULL);
int myNumber = input[0];
I know that there exists a WriteFile method in the API, but it looks like it will append the file as opposed to overwriting the contents. I have briefly considered deleting and recreating the file each time, but this seems unnecessarily complex for this problem. Any ideas out there?
Use the function SetFilePointer prior WriteFile for moving back to the begin
SetFilePointer(numFile, 0, NULL, FILE_BEGIN);

Creating a file with the same name as registry

I want to create a text file with the same name as a registry.
Say, I get the variable valueName, and I want it's value to be the name of a .txt file in C:\ How can I do that?
Almost final code:
void EnumerateValues(HKEY hKey, DWORD numValues)
{
for (DWORD dwIndex = 0; dwIndex < numValues; dwIndex++)
{BOOL bErrorFlag = FALSE;
char valueName[64];
DWORD valNameLen = sizeof(valueName);
DWORD dataType;
DWORD dataSize = 0;
DWORD retval = RegEnumValue(hKey, dwIndex, valueName, &valNameLen,
NULL, &dataType, NULL, &dataSize);
if (retval == ERROR_SUCCESS)
{//pregatesc calea
char* val = new char[strlen(valueName)];
sprintf(val, "C:\\%s.txt", valueName);
printf("S-a creat fisierul: %s\n", val);
//creez/suprascriu fisierul
HANDLE hFile;
hFile=CreateFile(val,GENERIC_WRITE | GENERIC_READ,FILE_SHARE_READ,
NULL, CREATE_ALWAYS , FILE_ATTRIBUTE_NORMAL,NULL);
if (hFile == INVALID_HANDLE_VALUE)
{ printf("Eroare la creat fisierul %s!\n",val);
}
//sciru in fisier
char str[] = "Example text testing WriteFile";
DWORD bytesWritten=0;
DWORD dwBytesToWrite = (DWORD)strlen(str);
bErrorFlag=WriteFile(hFile, str, dwBytesToWrite, &bytesWritten, NULL);
if (FALSE == bErrorFlag)
{
printf("Eroare la scriere in fisier\n");
}
//inchid fisierul
CloseHandle(hFile);
}
//eroare regenumv
else printf("\nError RegEnumValue");
}
}
The fundamental problem is that you seem to want to convert a registry key, HKEY into a path. And there's no API to do that. You will need to keep track of the path and pass it to the function in the question, along with the HKEY.
You are passing uninitialized values to RegEnumValue, specifically dataSize. Since you don't care about the data, don't ask for it. Pass NULL for the data pointer, and zero for data size.
Your call to new is not allocating enough memory. You need space for the directory name, the file extension, and the null-terminator.
These problems are exacerbated by your complete neglect for error checking. That might sound harsh, but frankly you need some shock treatment. In order to be able to fail gracefully you need to check for errors. More pressing for you, in order to be able to debug code, you need to check for errors.
You've tagged the code C++ but write as if it were C. If you really are using C++ then you can use standard containers, std::string, avoid raw memory allocation and the result leaks. Yes, you code leaks as it stands.
first of all your program is more C like than C++, but if you want to solve this in C++ you can use stringstream in the following way:
std::stringstream stream;
stream << "C:\\";
stream << valueName;
stream << ".txt";
std::string filename(stream.str());
HANDLE hFile=CreateFile(filename.c_str() ,GENERIC_READ,FILE_SHARE_READ,
NULL, CREATE_NEW , FILE_ATTRIBUTE_NORMAL,NULL);
Also you need a include:
#include <sstream>

std::string to LPBYTE and RegEnumValueA

What is the proper way to cast std::string to LPBYTE to make this code work?
string Name;
string Value;
RegEnumValueA(hKey, 0, const_cast<char*>(Name.c_str()), &dwSize, NULL, NULL, (LPBYTE)const_cast<char*>(Value.c_str()), &dwSize2);
When I try to user this code everythins is okey with the string name, but there's a bad pointer error in the string value
The proper way od getting std::string with data you want
//alloc buffers on stack
char buff1[1024];
char buff2[1024];
//prepare size variables
DWORD size1=sizeof(buff1);
DWORD size2=sizeof(buff2);
//call
RegEnumValueA(hKey, 0, buff1, &size1, NULL, NULL, (LPBYTE)buff2, &size2);
//"cast" to std::string
std::string Name(buff1);
std::string Value(buff2);
The pointer parameters should point to valid buffers that will be modified by a function. The c_str() of an unitialized string does not point to anything valid.
Use char buffers instead of a strings. This is a very good case of const_cast<> being totally uncalled for.
char Name[200], Value[200]; //Sizes are arbitrary
DWORD dwSize = sizeof(Name), dwSize2 = sizeof(Value);
RegEnumValueA(hKey, 0, Name, &dwSize, NULL, NULL, (LPBYTE)Value, &dwSize2);
If you really want to "cast" std::string as you said, you could consider the following piece of code. It should work on any std::string implementation I know, but still it is a bit "hacky" and I'd not recommend it.
string Name(1024, ' ');
string Value(1024, ' ');
DWORD dwSize=Name.size();
DWORD dwSize2=Value.size();
RegEnumValueA(hKey, 0, const_cast<char*>(&Name[0]), &dwSize, NULL, NULL, (LPBYTE)const_cast<char*>(&Value[0]), &dwSize2);

Trouble using ReadFile() to read a string from a text file

How can I make the code below to read correct text. In my text file has Hello welcome to C++, however at the end of the text, it has a new line. With the code below, my readBuffer always contains extra characters.
DWORD byteWritten;
int fileSize = 0;
//Use CreateFile to check if the file exists or not.
HANDLE hFile = CreateFile(myFile, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile != INVALID_HANDLE_VALUE)
{
BOOL readSuccess;
DWORD byteReading;
char readBuffer[256];
readSuccess = ReadFile(hFile, readBuffer, byteReading, &byteReading, NULL);
if(readSuccess == TRUE)
{
TCHAR myBuffer[256];
mbstowcs(myBuffer, readBuffer, 256);
if(_tcscmp(myBuffer, TEXT("Hello welcome to C++")) == 0)
{
FindClose(hFile);
CloseHandle(hFile);
WriteResultFile(TRUE, TEXT("success!"));
}
}
}
Thanks,
There are a few problems:
You're passing uninitialized data (byteReading) as the "# of bytes to read" parameter to ReadFile().
Depending on how you created the file, the file's contents may not have a terminating 0 byte. The code assumes that the terminator is present.
FindClose(hFile) doesn't make sense. CloseHandle(hFile) is all you need.
You need to call CloseHandle if CreateFile() succeeds. Currently, you call it only if you find the string you're looking for.
This isn't a bug, but it's helpful to zero-initialize your buffers. That makes it easier to see in the debugger exactly how much data is being read.
HANDLE hFile = CreateFile(myfile, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile != INVALID_HANDLE_VALUE)
{
BOOL readSuccess;
DWORD byteReading = 255;
char readBuffer[256];
readSuccess = ReadFile(hFile, readBuffer, byteReading, &byteReading, NULL);
readBuffer[byteReading] = 0;
if(readSuccess == TRUE)
{
TCHAR myBuffer[256];
mbstowcs(myBuffer, readBuffer, 256);
if(_tcscmp(myBuffer, TEXT("Hello welcome to C++")) == 0)
{
rv = 0;
}
}
CloseHandle(hFile);
}
I see two things:
byteReading isn't initialized
you are reading bytes so you have to terminate the string by 0.
CloseHandle is sufficient
Either remove the new line character from the file or use _tcsstr for checking the existence of the string "Hello Welcome to C++".