Files
renderdoc/renderdoc/os/win32/win32_stringio.cpp
T
baldurk 90a6e2b425 Specify file handles as explicitly non-inheriting on windows
* We never want to inherit these, and e.g. Qt process launches are hardcoded to
  always inherit handles so these need to be excluded so files don't stay open.
2021-09-09 12:24:17 +01:00

926 lines
22 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2021 Baldur Karlsson
* Copyright (c) 2014 Crytek
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include <io.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <stdio.h>
#include <string.h>
#include <tchar.h>
#include <time.h>
#include <set>
#include "api/app/renderdoc_app.h"
#include "api/replay/data_types.h"
#include "common/common.h"
#include "common/formatting.h"
#include "os/os_specific.h"
#include "strings/string_utils.h"
// gives us an address to identify this dll with
static int dllLocator = 0;
rdcstr GetDynamicEmbeddedResource(int resource)
{
HMODULE mod = NULL;
GetModuleHandleExA(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(const char *)&dllLocator, &mod);
HRSRC res = FindResource(mod, MAKEINTRESOURCE(resource), MAKEINTRESOURCE(TYPE_EMBED));
if(res == NULL)
{
RDCFATAL("Couldn't find embedded win32 resource");
}
HGLOBAL data = LoadResource(mod, res);
if(data != NULL)
{
DWORD resSize = SizeofResource(mod, res);
const char *resData = (const char *)LockResource(data);
if(resData)
return rdcstr(resData, resSize);
}
return "";
}
namespace Keyboard
{
void Init()
{
}
bool PlatformHasKeyInput()
{
return true;
}
std::set<HWND> inputWindows;
void AddInputWindow(WindowingSystem windowSystem, void *wnd)
{
RDCASSERTEQUAL(windowSystem, WindowingSystem::Win32);
inputWindows.insert((HWND)wnd);
}
void RemoveInputWindow(WindowingSystem windowSystem, void *wnd)
{
RDCASSERTEQUAL(windowSystem, WindowingSystem::Win32);
inputWindows.erase((HWND)wnd);
}
bool GetKeyState(int key)
{
int vk = 0;
if(key >= eRENDERDOC_Key_A && key <= eRENDERDOC_Key_Z)
vk = key;
if(key >= eRENDERDOC_Key_0 && key <= eRENDERDOC_Key_9)
vk = key;
switch(key)
{
case eRENDERDOC_Key_Divide: vk = VK_DIVIDE; break;
case eRENDERDOC_Key_Multiply: vk = VK_MULTIPLY; break;
case eRENDERDOC_Key_Subtract: vk = VK_SUBTRACT; break;
case eRENDERDOC_Key_Plus: vk = VK_ADD; break;
case eRENDERDOC_Key_F1: vk = VK_F1; break;
case eRENDERDOC_Key_F2: vk = VK_F2; break;
case eRENDERDOC_Key_F3: vk = VK_F3; break;
case eRENDERDOC_Key_F4: vk = VK_F4; break;
case eRENDERDOC_Key_F5: vk = VK_F5; break;
case eRENDERDOC_Key_F6: vk = VK_F6; break;
case eRENDERDOC_Key_F7: vk = VK_F7; break;
case eRENDERDOC_Key_F8: vk = VK_F8; break;
case eRENDERDOC_Key_F9: vk = VK_F9; break;
case eRENDERDOC_Key_F10: vk = VK_F10; break;
case eRENDERDOC_Key_F11: vk = VK_F11; break;
case eRENDERDOC_Key_F12: vk = VK_F12; break;
case eRENDERDOC_Key_Home: vk = VK_HOME; break;
case eRENDERDOC_Key_End: vk = VK_END; break;
case eRENDERDOC_Key_Insert: vk = VK_INSERT; break;
case eRENDERDOC_Key_Delete: vk = VK_DELETE; break;
case eRENDERDOC_Key_PageUp: vk = VK_PRIOR; break;
case eRENDERDOC_Key_PageDn: vk = VK_NEXT; break;
case eRENDERDOC_Key_Backspace: vk = VK_BACK; break;
case eRENDERDOC_Key_Tab: vk = VK_TAB; break;
case eRENDERDOC_Key_PrtScrn: vk = VK_SNAPSHOT; break;
case eRENDERDOC_Key_Pause: vk = VK_PAUSE; break;
default: break;
}
if(vk == 0)
return false;
bool keydown = GetAsyncKeyState(vk) != 0;
if(inputWindows.empty() || !keydown)
return keydown;
for(auto it = inputWindows.begin(); it != inputWindows.end(); ++it)
{
HWND w = *it;
HWND fore = GetForegroundWindow();
while(w)
{
if(w == fore)
return keydown;
w = GetParent(w);
}
}
return false;
}
}
namespace FileIO
{
void GetExecutableFilename(rdcstr &selfName)
{
wchar_t curFile[512] = {0};
GetModuleFileNameW(NULL, curFile, 511);
selfName = StringFormat::Wide2UTF8(curFile);
}
void GetLibraryFilename(rdcstr &selfName)
{
wchar_t curFile[512] = {0};
GetModuleFileNameW(GetModuleHandleA(STRINGIZE(RDOC_DLL_FILE) ".dll"), curFile, 511);
selfName = StringFormat::Wide2UTF8(curFile);
}
bool IsRelativePath(const rdcstr &path)
{
if(path.empty())
return false;
return PathIsRelativeW(StringFormat::UTF82Wide(path).c_str()) != 0;
}
rdcstr GetFullPathname(const rdcstr &filename)
{
rdcwstr wfn = StringFormat::UTF82Wide(filename);
wchar_t path[512] = {0};
GetFullPathNameW(wfn.c_str(), ARRAY_COUNT(path) - 1, path, NULL);
return StringFormat::Wide2UTF8(path);
}
rdcstr FindFileInPath(const rdcstr &file)
{
rdcstr filePath;
// Search the PATH directory list for the application (like shell where) to get the absolute path
// Return "" if no exectuable found in the PATH list
const DWORD size = 65535;
char envPath[size];
if(GetEnvironmentVariableA("PATH", envPath, size) == 0)
return filePath;
char *next = NULL;
const char *pathSeparator = ";";
const char *path = strtok_s(envPath, pathSeparator, &next);
rdcwstr fileName = StringFormat::UTF82Wide(file);
while(path)
{
rdcwstr testPath = StringFormat::UTF82Wide(path);
// Check for the following extensions. If fileName already has one, they will be ignored.
rdcarray<rdcwstr> extensions;
extensions.push_back(L".exe");
extensions.push_back(L".bat");
for(uint32_t i = 0; i < extensions.size(); i++)
{
wchar_t foundPath[512] = {0};
if(SearchPathW(testPath.c_str(), fileName.c_str(), extensions[i].c_str(),
ARRAY_COUNT(foundPath) - 1, foundPath, NULL) != 0)
{
filePath = StringFormat::Wide2UTF8(foundPath);
break;
}
}
path = strtok_s(NULL, pathSeparator, &next);
}
return filePath;
}
void CreateParentDirectory(const rdcstr &filename)
{
rdcstr dirname = get_dirname(filename);
// This function needs \\s not /s. So stupid!
for(size_t i = 0; i < dirname.size(); i++)
if(dirname[i] == L'/')
dirname[i] = L'\\';
// remove trailing \\s
while(dirname.back() == '\\')
dirname.pop_back();
// Find all directories we need to create
rdcarray<rdcstr> directoriesToCreate;
while(!dirname.empty())
{
DWORD fileAttributes = GetFileAttributesW(StringFormat::UTF82Wide(dirname).c_str());
if(fileAttributes != INVALID_FILE_ATTRIBUTES)
break;
directoriesToCreate.push_back(dirname);
dirname = get_dirname(dirname);
while(dirname.back() == '\\')
dirname.pop_back();
}
// Create the directories in reverse order
for(ptrdiff_t i = directoriesToCreate.size() - 1; i >= 0; i--)
{
CreateDirectoryW(StringFormat::UTF82Wide(directoriesToCreate[i]).c_str(), NULL);
}
}
rdcstr GetReplayAppFilename()
{
HMODULE hModule = NULL;
GetModuleHandleEx(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(LPCTSTR)&dllLocator, &hModule);
wchar_t curFile[512] = {0};
GetModuleFileNameW(hModule, curFile, 511);
rdcstr path = StringFormat::Wide2UTF8(curFile);
path = get_dirname(path);
rdcstr exe = path + "/qrenderdoc.exe";
FILE *f = FileIO::fopen(exe, FileIO::ReadBinary);
if(f)
{
FileIO::fclose(f);
return exe;
}
// if qrenderdoc.exe doesn't live in the same dir, we must be in x86/
// so look one up the tree.
exe = path + "/../qrenderdoc.exe";
f = FileIO::fopen(exe, FileIO::ReadBinary);
if(f)
{
FileIO::fclose(f);
return exe;
}
// if we didn't find the exe at all, we must not be in a standard
// distributed renderdoc package. On windows we can check in the registry
// to try and find the installed path.
DWORD type = 0;
DWORD dataSize = sizeof(curFile);
RDCEraseEl(curFile);
RegGetValueW(HKEY_CLASSES_ROOT, L"RenderDoc.RDCCapture.1\\DefaultIcon", NULL, RRF_RT_ANY, &type,
(void *)curFile, &dataSize);
if(type == REG_EXPAND_SZ || type == REG_SZ)
{
return StringFormat::Wide2UTF8(curFile);
}
return "";
}
void GetDefaultFiles(const rdcstr &logBaseName, rdcstr &capture_filename, rdcstr &logging_filename,
rdcstr &target)
{
wchar_t temp_filename[MAX_PATH];
GetTempPathW(MAX_PATH, temp_filename);
wchar_t curFile[512];
GetModuleFileNameW(NULL, curFile, 512);
wchar_t fn[MAX_PATH];
wcscpy_s(fn, MAX_PATH, curFile);
wchar_t *mod = wcsrchr(fn, L'.');
if(mod)
*mod = 0;
mod = wcsrchr(fn, L'/');
if(!mod)
mod = fn;
mod = wcsrchr(mod, L'\\');
mod++; // now points to base filename without extension
target = StringFormat::Wide2UTF8(mod);
time_t t = time(NULL);
tm now;
localtime_s(&now, &t);
wchar_t *filename_start = temp_filename + wcslen(temp_filename);
wsprintf(filename_start, L"RenderDoc\\%ls_%04d.%02d.%02d_%02d.%02d.rdc", mod, 1900 + now.tm_year,
now.tm_mon + 1, now.tm_mday, now.tm_hour, now.tm_min);
capture_filename = StringFormat::Wide2UTF8(temp_filename);
*filename_start = 0;
rdcwstr wbase = StringFormat::UTF82Wide(logBaseName);
wsprintf(filename_start, L"RenderDoc\\%ls_%04d.%02d.%02d_%02d.%02d.%02d.log", wbase.c_str(),
1900 + now.tm_year, now.tm_mon + 1, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec);
logging_filename = StringFormat::Wide2UTF8(temp_filename);
}
rdcstr GetHomeFolderFilename()
{
PWSTR docsPath;
SHGetKnownFolderPath(FOLDERID_Documents, KF_FLAG_SIMPLE_IDLIST | KF_FLAG_DONT_UNEXPAND, NULL,
&docsPath);
rdcwstr documents = docsPath;
CoTaskMemFree(docsPath);
rdcstr ret = StringFormat::Wide2UTF8(documents);
while(ret.back() == '/' || ret.back() == '\\')
ret.pop_back();
return ret;
}
rdcstr GetAppFolderFilename(const rdcstr &filename)
{
PWSTR appDataPath = NULL;
HRESULT hr = SHGetKnownFolderPath(
FOLDERID_RoamingAppData, KF_FLAG_SIMPLE_IDLIST | KF_FLAG_DONT_UNEXPAND, NULL, &appDataPath);
if(appDataPath == NULL || FAILED(hr))
return "";
rdcstr ret = StringFormat::Wide2UTF8(appDataPath);
CoTaskMemFree(appDataPath);
while(ret.back() == '/' || ret.back() == '\\')
ret.pop_back();
ret += "\\renderdoc\\" + filename;
CreateParentDirectory(ret);
return ret;
}
rdcstr GetTempFolderFilename()
{
wchar_t temp_filename[MAX_PATH];
GetTempPathW(MAX_PATH, temp_filename);
return StringFormat::Wide2UTF8(temp_filename);
}
uint64_t GetModifiedTimestamp(const rdcstr &filename)
{
rdcwstr wfn = StringFormat::UTF82Wide(filename);
struct __stat64 st;
int res = _wstat64(wfn.c_str(), &st);
if(res == 0)
{
return (uint64_t)st.st_mtime;
}
return 0;
}
uint64_t GetFileSize(const rdcstr &filename)
{
rdcwstr wfn = StringFormat::UTF82Wide(filename);
struct __stat64 st;
int res = _wstat64(wfn.c_str(), &st);
if(res == 0)
{
return (uint64_t)st.st_size;
}
return 0;
}
bool Copy(const rdcstr &from, const rdcstr &to, bool allowOverwrite)
{
rdcwstr wfrom = StringFormat::UTF82Wide(from);
rdcwstr wto = StringFormat::UTF82Wide(to);
return ::CopyFileW(wfrom.c_str(), wto.c_str(), allowOverwrite == false) != 0;
}
bool Move(const rdcstr &from, const rdcstr &to, bool allowOverwrite)
{
rdcwstr wfrom = StringFormat::UTF82Wide(from);
rdcwstr wto = StringFormat::UTF82Wide(to);
if(exists(to))
{
if(allowOverwrite)
Delete(to);
else
return false;
}
return ::MoveFileW(wfrom.c_str(), wto.c_str()) != 0;
}
void Delete(const rdcstr &path)
{
rdcwstr wpath = StringFormat::UTF82Wide(path);
::DeleteFileW(wpath.c_str());
}
void GetFilesInDirectory(const rdcstr &path, rdcarray<PathEntry> &ret)
{
ret.clear();
if(path.size() == 1 && path[0] == '/')
{
DWORD driveMask = GetLogicalDrives();
for(int i = 0; i < 26; i++)
{
DWORD mask = (1 << i);
if(driveMask & mask)
{
rdcstr fn = "A:/";
fn[0] = char('A' + i);
ret.push_back(PathEntry(fn, PathProperty::Directory));
}
}
return;
}
rdcstr pathstr = path;
// normalise path to windows style
for(size_t i = 0; i < pathstr.size(); i++)
if(pathstr[i] == '/')
pathstr[i] = '\\';
// remove any trailing slash
if(pathstr[pathstr.size() - 1] == '\\')
pathstr.resize(pathstr.size() - 1);
// append '\*' to do the search we want
pathstr += "\\*";
rdcwstr wpath = StringFormat::UTF82Wide(pathstr);
WIN32_FIND_DATAW findData = {};
HANDLE find = FindFirstFileW(wpath.c_str(), &findData);
if(find == INVALID_HANDLE_VALUE)
{
DWORD err = GetLastError();
PathProperty flags = PathProperty::ErrorUnknown;
if(err == ERROR_FILE_NOT_FOUND)
flags = PathProperty::ErrorInvalidPath;
else if(err == ERROR_ACCESS_DENIED)
flags = PathProperty::ErrorAccessDenied;
ret.push_back(PathEntry(path, flags));
return;
}
do
{
if(findData.cFileName[0] == L'.' && findData.cFileName[1] == 0)
{
// skip "."
}
else if(findData.cFileName[0] == L'.' && findData.cFileName[1] == L'.' &&
findData.cFileName[2] == 0)
{
// skip ".."
}
else
{
PathProperty flags = PathProperty::NoFlags;
if(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
flags |= PathProperty::Directory;
if(findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
flags |= PathProperty::Hidden;
if(wcsstr(findData.cFileName, L".EXE") || wcsstr(findData.cFileName, L".exe") ||
wcsstr(findData.cFileName, L".Exe"))
{
flags |= PathProperty::Executable;
}
PathEntry f(StringFormat::Wide2UTF8(findData.cFileName), flags);
uint64_t nanosecondsSinceWindowsEpoch = uint64_t(findData.ftLastWriteTime.dwHighDateTime) << 8 |
uint64_t(findData.ftLastWriteTime.dwLowDateTime);
uint64_t secondsSinceWindowsEpoch = nanosecondsSinceWindowsEpoch / 10000000;
// this constant is the number of seconds between Jan 1 1601 and Jan 1 1970
uint64_t secondsSinceUnixEpoch = secondsSinceWindowsEpoch - 11644473600;
f.lastmod = uint32_t(secondsSinceUnixEpoch);
f.size = uint64_t(findData.nFileSizeHigh) << 8 | uint64_t(findData.nFileSizeLow);
ret.push_back(f);
}
} while(FindNextFile(find, &findData) != FALSE);
// don't care if we hit an error or enumerated all files, just finish
FindClose(find);
}
const wchar_t *modeString[] = {
L"r", L"rb", L"w", L"wb", L"r+b", L"w+b",
};
FILE *fopen(const rdcstr &filename, FileMode mode)
{
rdcwstr wfn = StringFormat::UTF82Wide(filename);
FILE *ret = NULL;
::_wfopen_s(&ret, wfn.c_str(), modeString[mode]);
// specify the handle as non-inheriting
if(ret)
{
int fd = ::_fileno(ret);
HANDLE h = (HANDLE)::_get_osfhandle(fd);
SetHandleInformation(h, HANDLE_FLAG_INHERIT, 0);
}
return ret;
}
bool exists(const rdcstr &filename)
{
rdcwstr wfn = StringFormat::UTF82Wide(filename);
struct __stat64 st;
int res = _wstat64(wfn.c_str(), &st);
return (res == 0);
}
rdcstr ErrorString()
{
int err = errno;
char buf[256] = {0};
strerror_s(buf, err);
return buf;
}
size_t fread(void *buf, size_t elementSize, size_t count, FILE *f)
{
return ::fread(buf, elementSize, count, f);
}
size_t fwrite(const void *buf, size_t elementSize, size_t count, FILE *f)
{
return ::fwrite(buf, elementSize, count, f);
}
uint64_t ftell64(FILE *f)
{
return ::_ftelli64(f);
}
void fseek64(FILE *f, uint64_t offset, int origin)
{
::_fseeki64(f, offset, origin);
}
bool feof(FILE *f)
{
return ::feof(f) != 0;
}
void ftruncateat(FILE *f, uint64_t length)
{
::fflush(f);
int fd = ::_fileno(f);
::_chsize_s(fd, (int64_t)length);
}
bool fflush(FILE *f)
{
return ::fflush(f) == 0;
}
int fclose(FILE *f)
{
return ::fclose(f);
}
LogFileHandle *logfile_open(const rdcstr &filename)
{
rdcwstr wfn = StringFormat::UTF82Wide(filename);
// specify the handle as non-inheriting
SECURITY_ATTRIBUTES security = {};
security.nLength = sizeof(security);
security.bInheritHandle = FALSE;
return (LogFileHandle *)CreateFileW(wfn.c_str(), FILE_APPEND_DATA,
FILE_SHARE_READ | FILE_SHARE_WRITE, &security, OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
}
static rdcstr logfile_readall_fallback(uint64_t offset, const wchar_t *filename)
{
// if CreateFile/ReadFile failed, fall back and try regular stdio
FILE *f = NULL;
_wfopen_s(&f, filename, L"r");
if(f)
{
::_fseeki64(f, 0, SEEK_END);
uint64_t filesize = ::_ftelli64(f);
if(filesize > 10 && filesize > offset)
{
::_fseeki64(f, offset, SEEK_SET);
rdcstr ret;
ret.resize(size_t(filesize - offset));
size_t numRead = ::fread(&ret[0], 1, ret.size(), f);
ret.resize(numRead);
::fclose(f);
return ret;
}
::fclose(f);
}
return "";
}
rdcstr logfile_readall(uint64_t offset, const rdcstr &filename)
{
rdcwstr wfn = StringFormat::UTF82Wide(filename);
HANDLE h = CreateFileW(wfn.c_str(), FILE_READ_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
rdcstr ret;
if(h == INVALID_HANDLE_VALUE)
{
DWORD err = GetLastError();
if(err == ERROR_FILE_NOT_FOUND)
return StringFormat::Fmt("Logfile '%s' doesn't exist", filename.c_str());
ret = logfile_readall_fallback(offset, wfn.c_str());
ret += StringFormat::Fmt("\n\nCouldn't open logfile, CreateFile() threw %u\n\n", err);
}
else
{
DWORD highlen = 0;
DWORD len = ::GetFileSize(h, &highlen);
if(len == INVALID_FILE_SIZE)
{
DWORD err = GetLastError();
ret = logfile_readall_fallback(offset, wfn.c_str());
ret += StringFormat::Fmt("\n\nFailed to read logfile, GetFileSize() threw %u", err);
}
else
{
uint64_t length = uint64_t(highlen) << 32 | len;
if(offset < length)
{
LARGE_INTEGER offs;
offs.LowPart = (offset & 0xFFFFFFFFU);
offs.HighPart = ((offset >> 32) & 0xFFFFFFFFU);
::SetFilePointerEx(h, offs, NULL, FILE_BEGIN);
ret.resize(size_t(length - offset));
DWORD dummy = ret.count();
ReadFile(h, ret.data(), ret.count(), &dummy, NULL);
}
}
CloseHandle(h);
}
return ret;
}
void logfile_append(LogFileHandle *logHandle, const char *msg, size_t length)
{
if(logHandle)
{
DWORD bytesWritten = 0;
WriteFile((HANDLE)logHandle, msg, (DWORD)length, &bytesWritten, NULL);
}
}
void logfile_close(LogFileHandle *logHandle, const rdcstr &deleteFilename)
{
CloseHandle((HANDLE)logHandle);
if(!deleteFilename.empty())
{
// we can just try to delete the file. If it's open elsewhere in another process, the delete
// will fail.
rdcwstr wpath = StringFormat::UTF82Wide(deleteFilename);
::DeleteFileW(wpath.c_str());
}
}
};
namespace StringFormat
{
rdcstr sntimef(time_t utcTime, const char *format)
{
tm tmv;
localtime_s(&tmv, &utcTime);
rdcstr result;
rdcwstr wfmt = StringFormat::UTF82Wide(format);
// conservatively assume that most formatters will replace like-for-like (e.g. %H with 12) and
// a few will increase (%Y to 2019) but generally the string will stay the same size.
size_t len = strlen(format) + 16;
size_t ret = 0;
wchar_t *buf = NULL;
// loop until we have successfully formatted
while(ret == 0)
{
// delete any previous buffer
delete[] buf;
// alloate new one of the new size
buf = new wchar_t[len + 1];
buf[len] = 0;
// try formatting
ret = wcsftime(buf, len, wfmt.c_str(), &tmv);
// double the length for next time, if this failed
len *= 2;
}
rdcstr str = StringFormat::Wide2UTF8(buf);
// delete successful buffer
delete[] buf;
return str;
}
void Shutdown()
{
}
rdcstr Wide2UTF8(const rdcwstr &s)
{
int bytes_required = WideCharToMultiByte(CP_UTF8, 0, s.c_str(), -1, NULL, 0, NULL, NULL);
if(bytes_required == 0)
return "";
rdcstr ret;
ret.resize(bytes_required);
int res = WideCharToMultiByte(CP_UTF8, 0, s.c_str(), -1, &ret[0], bytes_required, NULL, NULL);
if(ret.back() == 0)
ret.pop_back();
if(res == 0)
{
#if ENABLED(RDOC_DEVEL)
RDCWARN("Failed to convert wstring"); // can't pass string through as this would infinitely
// recurse
#endif
return "";
}
return ret;
}
rdcwstr UTF82Wide(const rdcstr &s)
{
int chars_required = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0);
// chars_required includes the NULL terminator
chars_required--;
if(chars_required == 0)
return L"";
rdcwstr ret(chars_required);
// the buffer has an extra wchar_t for the null terminator
int res = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, &ret[0], chars_required + 1);
if(res == 0)
{
#if ENABLED(RDOC_DEVEL)
RDCWARN("Failed to convert utf-8 string"); // can't pass string through as this would
// infinitely recurse
#endif
return L"";
}
return ret;
}
};
namespace OSUtility
{
void WriteOutput(int channel, const char *str)
{
rdcwstr wstr = StringFormat::UTF82Wide(str);
if(channel == OSUtility::Output_DebugMon)
OutputDebugStringW(wstr.c_str());
else if(channel == OSUtility::Output_StdOut)
fwprintf_s(stdout, L"%s", wstr.c_str());
else if(channel == OSUtility::Output_StdErr)
fwprintf_s(stderr, L"%s", wstr.c_str());
}
uint64_t GetMachineIdent()
{
uint64_t ret = MachineIdent_Windows;
#if defined(_M_ARM) || defined(__arm__)
ret |= MachineIdent_Arch_ARM;
#else
ret |= MachineIdent_Arch_x86;
#endif
#if ENABLED(RDOC_X64)
ret |= MachineIdent_64bit;
#else
ret |= MachineIdent_32bit;
#endif
return ret;
}
};