How to convert BSTR to std::string in Visual Studio C++ 2010? - c++

I am working on a COM dll. I wish to convert a BSTR to a std::string to pass to a method that takes a const reference parameter.
It seems that using _com_util::ConvertBSTRToString() to get the char* equivalent of the BSTR is an appropriate way to do so. However, the API documentation is sparse, and the implementation is potentially buggy:
http://msdn.microsoft.com/en-us/library/ewezf1f6(v=vs.100).aspx
http://www.codeproject.com/Articles/1969/BUG-in-_com_util-ConvertStringToBSTR-and-_com_util
Example:
#include <comutil.h>
#include <string>
void Example(const std::string& Str) {}
int main()
{
BSTR BStr = SysAllocString("Test");
char* CharStr = _com_util::ConvertBSTRToString(BStr);
if(CharStr != NULL)
{
std::string StdStr(CharStr);
Example(StdStr);
delete[] CharStr;
}
SysFreeString(BStr);
}
What are the pros and cons of alternatives to using ConvertBSTRToString(), preferrably based on standard methods and classes?

You can do this yourself. I prefer to convert into the target std::string if possible. If not, use a temp-value override.
// convert a BSTR to a std::string.
std::string& BstrToStdString(const BSTR bstr, std::string& dst, int cp = CP_UTF8)
{
if (!bstr)
{
// define NULL functionality. I just clear the target.
dst.clear();
return dst;
}
// request content length in single-chars through a terminating
// nullchar in the BSTR. note: BSTR's support imbedded nullchars,
// so this will only convert through the first nullchar.
int res = WideCharToMultiByte(cp, 0, bstr, -1, NULL, 0, NULL, NULL);
if (res > 0)
{
dst.resize(res);
WideCharToMultiByte(cp, 0, bstr, -1, &dst[0], res, NULL, NULL);
}
else
{ // no content. clear target
dst.clear();
}
return dst;
}
// conversion with temp.
std::string BstrToStdString(BSTR bstr, int cp = CP_UTF8)
{
std::string str;
BstrToStdString(bstr, str, cp);
return str;
}
Invoke as:
BSTR bstr = SysAllocString(L"Test Data String")
std::string str;
// convert directly into str-allocated buffer.
BstrToStdString(bstr, str);
// or by-temp-val conversion
std::string str2 = BstrToStdString(bstr);
// release BSTR when finished
SysFreeString(bstr);
Something like that, anyway.

Easy way
BSTR => CStringW => CW2A => std::string.

Related

CryptUnprotectData returns FALSE

CryptUnprotectData returns FALSE, please help me. I try with wstring also, but it returns FALSE too. In which direction to look the error in C++?
#include <wtypes.h>
#include <string>
#include <cstdint>
#include "dpapi.h"
#pragma comment(lib, "Crypt32")
int main()
{
string in = "fdggddgsgds";
LPWSTR pDescrOut = NULL;
DATA_BLOB inData, outData;
inData.pbData = (BYTE*)in.data();
inData.cbData = (DWORD)in.size();
BOOL ok = CryptUnprotectData(&inData, NULL, NULL, NULL, NULL, 0, &outData);
if (inData.pbData != NULL)
LocalFree(inData.pbData);
if (pDescrOut != NULL)
LocalFree(pDescrOut);
if (!ok)
return NULL;
char* str = (char*)malloc(outData.cbData + 1);
memcpy(str, outData.pbData, outData.cbData);
str[outData.cbData] = '\0';
if (outData.pbData != NULL)
LocalFree(outData.pbData);
return 0;
}
(BYTE*)&in
This does not do what you think it does. in is a std::string. This results in a pointer to a std::string. Which has nothing to do, whatsoever, with the contents of this std::string.
The clear intent here is to obtain a pointer to the contents of the std::string, rather than the std::string itself.
To do that, use (BYTE *)in.data(), instead. This may or may not be the only problem with the shown code, since it fails to meet Stackoverflow's requirements for minimal reproducible example, however it is an obvious error.

Strange unicode error when converting Chinese wide strings to regular strings in C++

Some of my Chinese software users noticed a strange C++ exception being thrown when my C++ code for Windows tried to list all running processes:
在多字节的目标代码页中,没有此 Unicode 字符可以映射到的字符。
Translated to English this roughly means:
There are no characters to which this Unicode character can be mapped
in the multi-byte target code page.
The code which prints this is:
try
{
list_running_processes();
}
catch (std::runtime_error &exception)
{
LOG_S(ERROR) << exception.what();
return EXIT_FAILURE;
}
The most likely culprit source code is:
std::vector<running_process_t> list_running_processes()
{
std::vector<running_process_t> running_processes;
const auto snapshot_handle = unique_handle(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
if (snapshot_handle.get() == INVALID_HANDLE_VALUE)
{
throw std::runtime_error("CreateToolhelp32Snapshot() failed");
}
PROCESSENTRY32 process_entry{};
process_entry.dwSize = sizeof process_entry;
if (Process32First(snapshot_handle.get(), &process_entry))
{
do
{
const auto process_id = process_entry.th32ProcessID;
const auto executable_file_path = get_file_path(process_id);
// *** HERE ***
const auto process_name = wide_string_to_string(process_entry.szExeFile);
running_processes.emplace_back(executable_file_path, process_name, process_id);
} while (Process32Next(snapshot_handle.get(), &process_entry));
}
return running_processes;
}
Or alternatively:
std::string get_file_path(const DWORD process_id)
{
std::string file_path;
const auto snapshot_handle = unique_handle(CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, process_id));
MODULEENTRY32W module_entry32{};
module_entry32.dwSize = sizeof(MODULEENTRY32W);
if (Module32FirstW(snapshot_handle.get(), &module_entry32))
{
do
{
if (module_entry32.th32ProcessID == process_id)
{
return wide_string_to_string(module_entry32.szExePath); // *** HERE ***
}
} while (Module32NextW(snapshot_handle.get(), &module_entry32));
}
return file_path;
}
This is the code for performing a conversion from a std::wstring to a regular std::string:
std::string wide_string_to_string(const std::wstring& wide_string)
{
if (wide_string.empty())
{
return std::string();
}
const auto size_needed = WideCharToMultiByte(CP_UTF8, 0, &wide_string.at(0),
static_cast<int>(wide_string.size()), nullptr, 0, nullptr, nullptr);
std::string str_to(size_needed, 0);
WideCharToMultiByte(CP_UTF8, 0, &wide_string.at(0), static_cast<int>(wide_string.size()), &str_to.at(0),
size_needed, nullptr, nullptr);
return str_to;
}
Is there any reason this can fail on Chinese language file paths or Chinese language Windows etc.? The code works fine on regular western Windows machines. Let me know if I'm missing any crucial pieces of information here since I cannot debug or test this on my own right now without access to one of the affected machines.
I managed to test on a Chinese machine and it turns out that converting a file path from wide string to a regular string will produce a bad file path output if the file path contains e.g. Chinese (non-ASCII) symbols.
I could fix this bug by replacing calls to wide_string_to_string() with std::filesystem::path(wide_string_file_path).string() since the std::filesystem API will handle the conversion correctly for file paths unlike wide_string_to_string().

How do I get appdata/local/<MyProgramFolder>/ path in c++

I used fstream and used getenv to get the path of appdata. I used the following codes but it output on appdata/roaming/
QString appdata = getenv("appdata");
appdata += "\\DoDLog.log";
fstream stud;
stud.open(appdata.toStdString().c_str(), ios::app);
What I want to happen is the path of appdata/local//DoDLog.log
MyProgram is the c++ program that i've been running. Please help.
You need to use SHGetSpecialFolderLocation
This is a C solution, it shouldn't be hard to adapt it to C++, QString, std::string or whatever.
#include <shlobj.h>
...
HRESULT GetFolderLocation (int csidl, char* buffer)
{
LPITEMIDLIST pidl = 0;
HRESULT result = SHGetSpecialFolderLocation(NULL, csidl, &pidl);
*buffer = 0 ;
if (result == 0)
{
SHGetPathFromIDList(pidl, buffer);
CoTaskMemFree(pidl);
}
return result;
}
...
char str[_MAX_PATH];
GetFolderLocation(CSIDL_LOCAL_APPDATA, str);
// now str contains "C:\Users\<user>\AppData\Local"
...

WideCharToMultiByte std analog UTF8

This code is workng properly for me:
std::wstring wmsg_text = L"キエオイウカクケコサシスセソタチツテア";
char buffer[100] = { 0 };
WideCharToMultiByte(CP_UTF8, 0, wmsg_text.data(), wmsg_text.size(), buffer, sizeof(buffer)-1, NULL, NULL);
I wonder the cross platform analog of this code. I look to std::wcstombs with std::codecvt_utf8, but can't guess how to use this by right way.
You want to use std::wcsrtombs, something like:
std::wstring wmsg_text = L"キエオイウカクケコサシスセソタチツテア";
const wchar_t* wstr = wmsg_text.data();
std::mbstate_t state = std::mbstate_t();
int len = 1 + std::wcsrtombs(nullptr, &wstr, 0, &state);
std::vector<char> mbstr(len);
std::wcsrtombs(&mbstr[0], &wstr, mbstr.size(), &state);
char* buffer = mbstr.data();
This code is working properly, too:
std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
std::string u8str = conv.to_bytes(msg);

Open utf8 encoded filename in c++ Windows

Consider the following code:
#include <iostream>
#include <boost\locale.hpp>
#include <Windows.h>
#include <fstream>
std::string ToUtf8(std::wstring str)
{
std::string ret;
int len = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), str.length(), NULL, 0, NULL, NULL);
if (len > 0)
{
ret.resize(len);
WideCharToMultiByte(CP_UTF8, 0, str.c_str(), str.length(), &ret[0], len, NULL, NULL);
}
return ret;
}
int main()
{
std::wstring wfilename = L"D://Private//Test//एउटा फोल्दर//भित्रको फाईल.txt";
std::string utf8path = ToUtf8(wfilename );
std::ifstream iFileStream(utf8path , std::ifstream::in | std::ifstream::binary);
if(iFileStream.is_open())
{
std::cout << "Opened the File\n";
//Do the work here.
}
else
{
std::cout << "Cannot Opened the file\n";
}
return 0;
}
If I am running the file, I cannot open the file thus entering into the else block. Even using boost::locale::conv::from_utf(utf8path ,"utf_8") instead of utf8path doesn't work. The code works if I consider using wifstream and using wfilename as its parameter, but I don' want to use wifstream. Is there any way to open the file with its name utf8 encoded? I am using Visual Studio 2010.
On Windows, you MUST use 8bit ANSI (and it must match the user's locale) or UTF-16 for filenames, there is no other option available. You can keep using string and UTF-8 in your main code, but you will have to convert UTF-8 filenames to UTF-16 when you are opening files. Less efficient, but that is what you need to do.
Fortunately, VC++'s implementation of std::ifstream and std::ofstream have non-standard overloads of their constructors and open() methods to accept wchar_t* strings for UTF-16 filenames.
explicit basic_ifstream(
const wchar_t *_Filename,
ios_base::openmode _Mode = ios_base::in,
int _Prot = (int)ios_base::_Openprot
);
void open(
const wchar_t *_Filename,
ios_base::openmode _Mode = ios_base::in,
int _Prot = (int)ios_base::_Openprot
);
void open(
const wchar_t *_Filename,
ios_base::openmode _Mode
);
explicit basic_ofstream(
const wchar_t *_Filename,
ios_base::openmode _Mode = ios_base::out,
int _Prot = (int)ios_base::_Openprot
);
void open(
const wchar_t *_Filename,
ios_base::openmode _Mode = ios_base::out,
int _Prot = (int)ios_base::_Openprot
);
void open(
const wchar_t *_Filename,
ios_base::openmode _Mode
);
You will have to use an #ifdef to detect Windows compilation (unfortunately, different C++ compilers identify that differently) and temporarily convert your UTF-8 string to UTF-16 when opening a file.
#ifdef _MSC_VER
std::wstring ToUtf16(std::string str)
{
std::wstring ret;
int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), NULL, 0);
if (len > 0)
{
ret.resize(len);
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), &ret[0], len);
}
return ret;
}
#endif
int main()
{
std::string utf8path = ...;
std::ifstream iFileStream(
#ifdef _MSC_VER
ToUtf16(utf8path).c_str()
#else
utf8path.c_str()
#endif
, std::ifstream::in | std::ifstream::binary);
...
return 0;
}
Note that this is only guaranteed to work in VC++. Other C++ compilers for Windows are not guaranteed to provide similar extensions.
UPDATE: as of Windows 10 Insider Preview Build 17035, Microsoft now supports UTF-8 as a system-wide encoding that users can set their locale to. And as of Windows 10 Version 1903 (build 18362), applications can now opt in via their app manifest to use UTF-8 as a process-wide codepage, even if the user locale is not set to UTF-8. These features allow ANSI-based APIs (like CreateFileA(), which std::ifstream/std::ofstream use internally) to work with UTF-8 strings. So, in theory, with this feature turned on, you might be able to pass a UTF-8 encoded string to std::ifstream/std::ofstream and it would "just work". I can't confirm that, as it very much depends on the implementation. It would be safer to stick with passing in UTF-16 filenames, since that is Windows' native encoding, which the ANSI APIs will simply convert to internally.
You can use std::filesystem::u8path in C++14/17:
std::filesystem::path pa = std::filesystem::u8path((const char*)yourStdStringPath.c_str());
std::ofstream ofs(pa);
It's deprecated in C++20 since you can use the u8 prefix.