Files
GacUI/Import/VlppOS.Windows.cpp
T
2026-05-20 06:56:03 -07:00

4323 lines
116 KiB
C++

/***********************************************************************
THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY
DEVELOPER: Zihan Chen(vczh)
***********************************************************************/
#include "VlppOS.Windows.h"
/***********************************************************************
.\FILESYSTEM.WINDOWS.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#include <Windows.h>
#include <Shlwapi.h>
#ifndef VCZH_MSVC
static_assert(false, "Do not build this file for non-Windows applications.");
#endif
#pragma comment(lib, "Shlwapi.lib")
namespace vl
{
namespace stream
{
extern Ptr<IFileStreamImpl> CreateOSFileStreamImpl(const WString& fileName, FileStream::AccessRight accessRight);
}
namespace filesystem
{
using namespace collections;
using namespace stream;
/***********************************************************************
WindowsFileSystemImpl
***********************************************************************/
class WindowsFileSystemImpl : public feature_injection::FeatureImpl<IFileSystemImpl>
{
public:
wchar_t GetPathDelimiter() const override
{
return L'\\';
}
const wchar_t* GetCompatibleDelimiters() const override
{
return L"/";
}
WString ConcatPath(const WString& fullPath, const WString& relativePath) const override
{
if (IsRoot(fullPath))
{
return relativePath;
}
return fullPath + WString::FromChar(GetPathDelimiter()) + relativePath;
}
void Initialize(WString& fullPath) const override
{
{
Array<wchar_t> buffer(fullPath.Length() + 1);
wcscpy_s(&buffer[0], fullPath.Length() + 1, fullPath.Buffer());
FilePath::NormalizeDelimiters(buffer);
fullPath = &buffer[0];
}
if (fullPath != L"")
{
if (fullPath.Length() < 2 || fullPath[1] != L':')
{
wchar_t buffer[MAX_PATH + 1] = { 0 };
auto result = GetCurrentDirectory(sizeof(buffer) / sizeof(*buffer), buffer);
if (result > MAX_PATH + 1 || result == 0)
{
throw ArgumentException(L"Failed to call GetCurrentDirectory.", L"vl::filesystem::FilePath::Initialize", L"");
}
fullPath = WString(buffer) + L"\\" + fullPath;
}
{
wchar_t buffer[MAX_PATH + 1] = { 0 };
if (fullPath.Length() == 2 && fullPath[1] == L':')
{
fullPath += L"\\";
}
auto result = GetFullPathName(fullPath.Buffer(), sizeof(buffer) / sizeof(*buffer), buffer, NULL);
if (result > MAX_PATH + 1 || result == 0)
{
throw ArgumentException(L"The path is illegal.", L"vl::filesystem::FilePath::FilePath", L"_filePath");
}
{
wchar_t shortPath[MAX_PATH + 1];
wchar_t longPath[MAX_PATH + 1];
if (GetShortPathName(buffer, shortPath, MAX_PATH) > 0)
{
if (GetLongPathName(shortPath, longPath, MAX_PATH) > 0)
{
memcpy(buffer, longPath, sizeof(buffer));
}
}
}
fullPath = buffer;
}
}
FilePath::TrimLastDelimiter(fullPath);
}
bool IsFile(const WString& fullPath) const override
{
WIN32_FILE_ATTRIBUTE_DATA info;
BOOL result = GetFileAttributesEx(fullPath.Buffer(), GetFileExInfoStandard, &info);
if (!result) return false;
return (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
}
bool IsFolder(const WString& fullPath) const override
{
WIN32_FILE_ATTRIBUTE_DATA info;
BOOL result = GetFileAttributesEx(fullPath.Buffer(), GetFileExInfoStandard, &info);
if (!result) return false;
return (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
bool IsRoot(const WString& fullPath) const override
{
return fullPath == L"";
}
WString GetRelativePathFor(const WString& fromPath, const WString& toPath) const override
{
if (fromPath.Length() == 0 || toPath.Length() == 0 || fromPath[0] != toPath[0])
{
return toPath;
}
wchar_t buffer[MAX_PATH + 1] = { 0 };
PathRelativePathTo(
buffer,
fromPath.Buffer(),
(IsFolder(fromPath) ? FILE_ATTRIBUTE_DIRECTORY : 0),
toPath.Buffer(),
(IsFolder(toPath) ? FILE_ATTRIBUTE_DIRECTORY : 0)
);
return buffer;
}
bool FileDelete(const FilePath& filePath) const override
{
return DeleteFile(filePath.GetFullPath().Buffer()) != 0;
}
bool FileRename(const FilePath& filePath, const WString& newName) const override
{
WString oldFileName = filePath.GetFullPath();
WString newFileName = (filePath.GetFolder() / newName).GetFullPath();
return MoveFile(oldFileName.Buffer(), newFileName.Buffer()) != 0;
}
bool GetFolders(const FilePath& folderPath, collections::List<Folder>& folders) const override
{
if (folderPath.IsRoot())
{
auto bufferSize = GetLogicalDriveStrings(0, nullptr);
if (bufferSize > 0)
{
Array<wchar_t> buffer(bufferSize);
if (GetLogicalDriveStrings((DWORD)buffer.Count(), &buffer[0]) > 0)
{
auto begin = &buffer[0];
auto end = begin + buffer.Count();
while (begin < end && *begin)
{
WString driveString = begin;
begin += driveString.Length() + 1;
folders.Add(Folder(FilePath(driveString)));
}
return true;
}
}
return false;
}
else
{
if (!IsFolder(folderPath.GetFullPath())) return false;
WIN32_FIND_DATA findData;
HANDLE findHandle = INVALID_HANDLE_VALUE;
while (true)
{
if (findHandle == INVALID_HANDLE_VALUE)
{
WString searchPath = (folderPath / L"*").GetFullPath();
findHandle = FindFirstFile(searchPath.Buffer(), &findData);
if (findHandle == INVALID_HANDLE_VALUE)
{
break;
}
}
else
{
BOOL result = FindNextFile(findHandle, &findData);
if (result == 0)
{
FindClose(findHandle);
break;
}
}
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if (wcscmp(findData.cFileName, L".") != 0 && wcscmp(findData.cFileName, L"..") != 0)
{
folders.Add(Folder(folderPath / findData.cFileName));
}
}
}
return true;
}
}
bool GetFiles(const FilePath& folderPath, collections::List<File>& files) const override
{
if (IsRoot(folderPath.GetFullPath()))
{
return true;
}
if (!IsFolder(folderPath.GetFullPath())) return false;
WIN32_FIND_DATA findData;
HANDLE findHandle = INVALID_HANDLE_VALUE;
while (true)
{
if (findHandle == INVALID_HANDLE_VALUE)
{
WString searchPath = (folderPath / L"*").GetFullPath();
findHandle = FindFirstFile(searchPath.Buffer(), &findData);
if (findHandle == INVALID_HANDLE_VALUE)
{
break;
}
}
else
{
BOOL result = FindNextFile(findHandle, &findData);
if (result == 0)
{
FindClose(findHandle);
break;
}
}
if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
files.Add(File(folderPath / findData.cFileName));
}
}
return true;
}
bool CreateFolder(const FilePath& folderPath) const override
{
return CreateDirectory(folderPath.GetFullPath().Buffer(), NULL) != 0;
}
bool DeleteFolder(const FilePath& folderPath) const override
{
return RemoveDirectory(folderPath.GetFullPath().Buffer()) != 0;
}
bool FolderRename(const FilePath& folderPath, const WString& newName) const override
{
WString oldFileName = folderPath.GetFullPath();
WString newFileName = (folderPath.GetFolder() / newName).GetFullPath();
return MoveFile(oldFileName.Buffer(), newFileName.Buffer()) != 0;
}
Ptr<stream::IFileStreamImpl> GetFileStreamImpl(const WString& fileName, stream::FileStream::AccessRight accessRight) const override
{
return stream::CreateOSFileStreamImpl(fileName, accessRight);
}
};
IFileSystemImpl* GetOSFileSystemImpl()
{
static WindowsFileSystemImpl osFileSystemImpl;
return &osFileSystemImpl;
}
}
}
/***********************************************************************
.\HTTPUTILITY.WINDOWS.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_MSVC
static_assert(false, "Do not build this file for non-Windows applications.");
#endif
#pragma comment(lib, "WinHttp.lib")
namespace vl
{
using namespace collections;
/***********************************************************************
HttpRequest
***********************************************************************/
bool HttpRequest::SetHost(const WString& inputQuery)
{
if (method == L"")
{
method = L"GET";
}
server = L"";
query = L"";
port = 0;
secure = false;
{
if (server == L"")
{
if (inputQuery.Length() > 7)
{
WString protocol = inputQuery.Sub(0, 8);
if (_wcsicmp(protocol.Buffer(), L"https://") == 0)
{
const wchar_t* reading = inputQuery.Buffer() + 8;
const wchar_t* index1 = wcschr(reading, L':');
const wchar_t* index2 = wcschr(reading, L'/');
if (index2)
{
query = index2;
server = WString::CopyFrom(reading, (index1 ? index1 : index2) - reading);
port = INTERNET_DEFAULT_HTTPS_PORT;
secure = true;
if (index1)
{
auto portString = WString::CopyFrom(index1 + 1, index2 - index1 - 1);
port = _wtoi(portString.Buffer());
}
return true;
}
}
}
}
if (server == L"")
{
if (inputQuery.Length() > 6)
{
WString protocol = inputQuery.Sub(0, 7);
if (_wcsicmp(protocol.Buffer(), L"http://") == 0)
{
const wchar_t* reading = inputQuery.Buffer() + 7;
const wchar_t* index1 = wcschr(reading, L':');
const wchar_t* index2 = wcschr(reading, L'/');
if (index2)
{
query = index2;
server = WString::CopyFrom(reading, (index1 ? index1 : index2) - reading);
port = INTERNET_DEFAULT_HTTP_PORT;
if (index1)
{
auto portString = WString::CopyFrom(index1 + 1, index2 - index1 - 1);
port = _wtoi(portString.Buffer());
}
return true;
}
}
}
}
}
return false;
}
void HttpRequest::SetBodyUtf8(const WString& bodyString)
{
vint utf8Size = WideCharToMultiByte(CP_UTF8, 0, bodyString.Buffer(), (int)bodyString.Length(), NULL, 0, NULL, NULL);
char* utf8 = new char[utf8Size + 1];
ZeroMemory(utf8, utf8Size + 1);
WideCharToMultiByte(CP_UTF8, 0, bodyString.Buffer(), (int)bodyString.Length(), utf8, (int)utf8Size, NULL, NULL);
body.Resize(utf8Size);
memcpy(&body[0], utf8, utf8Size);
delete[] utf8;
}
/***********************************************************************
HttpResponse
***********************************************************************/
WString HttpResponse::GetBodyUtf8()
{
WString response;
char* utf8 = &body[0];
vint totalSize = body.Count();
vint utf16Size = MultiByteToWideChar(CP_UTF8, 0, utf8, (int)totalSize, NULL, 0);
wchar_t* utf16 = new wchar_t[utf16Size + 1];
ZeroMemory(utf16, (utf16Size + 1) * sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, utf8, (int)totalSize, utf16, (int)utf16Size);
response = utf16;
delete[] utf16;
return response;
}
/***********************************************************************
Utilities
***********************************************************************/
struct BufferPair
{
char* buffer;
vint length;
BufferPair()
:buffer(0)
, length(0)
{
}
BufferPair(char* _buffer, vint _length)
:buffer(_buffer)
, length(_length)
{
}
};
bool HttpQuery(const HttpRequest& request, HttpResponse& response)
{
// initialize
response.statusCode = -1;
HINTERNET internet = NULL;
HINTERNET connectedInternet = NULL;
HINTERNET requestInternet = NULL;
BOOL httpResult = FALSE;
DWORD error = 0;
List<LPCWSTR> acceptTypes;
List<BufferPair> availableBuffers;
// access http
internet = WinHttpOpen(L"vczh", WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0);
error = GetLastError();
if (!internet) goto CLEANUP;
// connect
connectedInternet = WinHttpConnect(internet, request.server.Buffer(), (int)request.port, 0);
error = GetLastError();
if (!connectedInternet) goto CLEANUP;
// open request
// TODO: (enumerable) Linq:Select
for (vint i = 0; i < request.acceptTypes.Count(); i++)
{
acceptTypes.Add(request.acceptTypes.Get(i).Buffer());
}
acceptTypes.Add(nullptr);
requestInternet = WinHttpOpenRequest(connectedInternet, request.method.Buffer(), request.query.Buffer(), NULL, WINHTTP_NO_REFERER, &acceptTypes[0], (request.secure ? WINHTTP_FLAG_SECURE : 0));
error = GetLastError();
if (!requestInternet) goto CLEANUP;
// authentication, cookie and request
if (request.username != L"" && request.password != L"")
{
WinHttpSetCredentials(requestInternet, WINHTTP_AUTH_TARGET_SERVER, WINHTTP_AUTH_SCHEME_BASIC, request.username.Buffer(), request.password.Buffer(), NULL);
}
if (request.contentType != L"")
{
httpResult = WinHttpAddRequestHeaders(requestInternet, (L"Content-type:" + request.contentType).Buffer(), -1, WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD);
}
if (request.cookie != L"")
{
WinHttpAddRequestHeaders(requestInternet, (L"Cookie:" + request.cookie).Buffer(), -1, WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD);
}
// extra headers
for (int i = 0; i < request.extraHeaders.Count(); i++)
{
WString key = request.extraHeaders.Keys()[i];
WString value = request.extraHeaders.Values().Get(i);
WinHttpAddRequestHeaders(requestInternet, (key + L":" + value).Buffer(), -1, WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD);
}
if (request.body.Count() > 0)
{
httpResult = WinHttpSendRequest(requestInternet, WINHTTP_NO_ADDITIONAL_HEADERS, 0, (LPVOID)&request.body.Get(0), (int)request.body.Count(), (int)request.body.Count(), NULL);
}
else
{
httpResult = WinHttpSendRequest(requestInternet, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, NULL);
}
error = GetLastError();
if (httpResult == FALSE) goto CLEANUP;
// receive response
httpResult = WinHttpReceiveResponse(requestInternet, NULL);
error = GetLastError();
if (httpResult != TRUE) goto CLEANUP;
// read response status code
{
DWORD headerLength = sizeof(DWORD);
DWORD statusCode = 0;
httpResult = WinHttpQueryHeaders(requestInternet, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &headerLength, WINHTTP_NO_HEADER_INDEX);
error = GetLastError();
if (httpResult == FALSE) goto CLEANUP;
response.statusCode = statusCode;
}
// read respons cookie
{
DWORD headerLength = sizeof(DWORD);
httpResult = WinHttpQueryHeaders(requestInternet, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, NULL, &headerLength, WINHTTP_NO_HEADER_INDEX);
error = GetLastError();
if (error == ERROR_INSUFFICIENT_BUFFER)
{
wchar_t* rawHeader = new wchar_t[headerLength / sizeof(wchar_t)];
ZeroMemory(rawHeader, headerLength);
httpResult = WinHttpQueryHeaders(requestInternet, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, rawHeader, &headerLength, WINHTTP_NO_HEADER_INDEX);
const wchar_t* cookieStart = wcsstr(rawHeader, L"Cookie:");
if (cookieStart)
{
const wchar_t* cookieEnd = wcsstr(cookieStart, L";");
if (cookieEnd)
{
response.cookie = WString::CopyFrom(cookieStart + 7, cookieEnd - cookieStart - 7);
}
}
delete[] rawHeader;
}
}
// read response body
while (true)
{
DWORD bytesAvailable = 0;
BOOL queryDataAvailableResult = WinHttpQueryDataAvailable(requestInternet, &bytesAvailable);
error = GetLastError();
if (queryDataAvailableResult == TRUE && bytesAvailable != 0)
{
char* utf8 = new char[bytesAvailable];
DWORD bytesRead = 0;
BOOL readDataResult = WinHttpReadData(requestInternet, utf8, bytesAvailable, &bytesRead);
error = GetLastError();
if (readDataResult == TRUE)
{
availableBuffers.Add(BufferPair(utf8, bytesRead));
}
else
{
delete[] utf8;
}
}
else
{
break;
}
}
{
// concatincate response body
vint totalSize = 0;
for (auto p : availableBuffers)
{
totalSize += p.length;
}
response.body.Resize(totalSize);
if (totalSize > 0)
{
char* utf8 = new char[totalSize];
{
char* temp = utf8;
for (auto p : availableBuffers)
{
memcpy(temp, p.buffer, p.length);
temp += p.length;
}
}
memcpy(&response.body[0], utf8, totalSize);
delete[] utf8;
}
for (auto p : availableBuffers)
{
delete[] p.buffer;
}
}
CLEANUP:
if (requestInternet) WinHttpCloseHandle(requestInternet);
if (connectedInternet) WinHttpCloseHandle(connectedInternet);
if (internet) WinHttpCloseHandle(internet);
return response.statusCode != -1;
}
WString UrlEncodeQuery(const WString& query)
{
vint utf8Size = WideCharToMultiByte(CP_UTF8, 0, query.Buffer(), (int)query.Length(), NULL, 0, NULL, NULL);
char* utf8 = new char[utf8Size + 1];
ZeroMemory(utf8, utf8Size + 1);
WideCharToMultiByte(CP_UTF8, 0, query.Buffer(), (int)query.Length(), utf8, (int)utf8Size, NULL, NULL);
wchar_t* encoded = new wchar_t[utf8Size * 3 + 1];
ZeroMemory(encoded, (utf8Size * 3 + 1) * sizeof(wchar_t));
wchar_t* writing = encoded;
for (vint i = 0; i < utf8Size; i++)
{
unsigned char x = (unsigned char)utf8[i];
if (L'a' <= x && x <= 'z' || L'A' <= x && x <= L'Z' || L'0' <= x && x <= L'9')
{
writing[0] = x;
writing += 1;
}
else
{
writing[0] = L'%';
writing[1] = L"0123456789ABCDEF"[x / 16];
writing[2] = L"0123456789ABCDEF"[x % 16];
writing += 3;
}
}
WString result = encoded;
delete[] encoded;
delete[] utf8;
return result;
}
}
/***********************************************************************
.\LOCALE.WINDOWS.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_MSVC
static_assert(false, "Do not build this file for non-Windows applications.");
#endif
namespace vl
{
using namespace collections;
/***********************************************************************
Locale Helper Functions
***********************************************************************/
SYSTEMTIME DateTimeToSystemTime(const DateTime& dateTime)
{
SYSTEMTIME systemTime;
systemTime.wYear = (WORD)dateTime.year;
systemTime.wMonth = (WORD)dateTime.month;
systemTime.wDayOfWeek = (WORD)dateTime.dayOfWeek;
systemTime.wDay = (WORD)dateTime.day;
systemTime.wHour = (WORD)dateTime.hour;
systemTime.wMinute = (WORD)dateTime.minute;
systemTime.wSecond = (WORD)dateTime.second;
systemTime.wMilliseconds = (WORD)dateTime.milliseconds;
return systemTime;
}
BOOL CALLBACK Locale_EnumLocalesProcEx(
_In_ LPWSTR lpLocaleString,
_In_ DWORD dwFlags,
_In_ LPARAM lParam
)
{
((List<Locale>*)lParam)->Add(Locale(lpLocaleString));
return TRUE;
}
BOOL CALLBACK Locale_EnumDateFormatsProcExEx(
_In_ LPWSTR lpDateFormatString,
_In_ CALID CalendarID,
_In_ LPARAM lParam
)
{
((List<WString>*)lParam)->Add(lpDateFormatString);
return TRUE;
}
BOOL CALLBACK EnumTimeFormatsProcEx(
_In_ LPWSTR lpTimeFormatString,
_In_ LPARAM lParam
)
{
((List<WString>*)lParam)->Add(lpTimeFormatString);
return TRUE;
}
WString Transform(const WString& localeName, const WString& input, DWORD flag)
{
int length = LCMapStringEx(localeName.Buffer(), flag, input.Buffer(), (int)input.Length() + 1, NULL, 0, NULL, NULL, NULL);
Array<wchar_t> buffer(length);
LCMapStringEx(localeName.Buffer(), flag, input.Buffer(), (int)input.Length() + 1, &buffer[0], (int)buffer.Count(), NULL, NULL, NULL);
return &buffer[0];
}
DWORD TranslateNormalization(Locale::Normalization normalization)
{
DWORD result = 0;
if (normalization & Locale::IgnoreCase) result |= NORM_IGNORECASE;
if (normalization & Locale::IgnoreCaseLinguistic) result |= NORM_IGNORECASE | NORM_LINGUISTIC_CASING;
if (normalization & Locale::IgnoreKanaType) result |= NORM_IGNOREKANATYPE;
if (normalization & Locale::IgnoreNonSpace) result |= NORM_IGNORENONSPACE;
if (normalization & Locale::IgnoreSymbol) result |= NORM_IGNORESYMBOLS;
if (normalization & Locale::IgnoreWidth) result |= NORM_IGNOREWIDTH;
if (normalization & Locale::DigitsAsNumbers) result |= SORT_DIGITSASNUMBERS;
if (normalization & Locale::StringSoft) result |= SORT_STRINGSORT;
return result;
}
/***********************************************************************
WindowsLocaleImpl
***********************************************************************/
class WindowsLocaleImpl : public feature_injection::FeatureImpl<ILocaleImpl>
{
public:
Locale Invariant() const override
{
return Locale(LOCALE_NAME_INVARIANT);
}
Locale SystemDefault() const override
{
wchar_t buffer[LOCALE_NAME_MAX_LENGTH + 1] = { 0 };
GetSystemDefaultLocaleName(buffer, LOCALE_NAME_MAX_LENGTH);
return Locale(buffer);
}
Locale UserDefault() const override
{
wchar_t buffer[LOCALE_NAME_MAX_LENGTH + 1] = { 0 };
GetUserDefaultLocaleName(buffer, LOCALE_NAME_MAX_LENGTH);
return Locale(buffer);
}
void Enumerate(collections::List<Locale>& locales) const override
{
EnumSystemLocalesEx(&Locale_EnumLocalesProcEx, LOCALE_ALL, (LPARAM)&locales, NULL);
}
void GetShortDateFormats(const WString& localeName, collections::List<WString>& formats) const override
{
EnumDateFormatsExEx(&Locale_EnumDateFormatsProcExEx, localeName.Buffer(), DATE_SHORTDATE, (LPARAM)&formats);
}
void GetLongDateFormats(const WString& localeName, collections::List<WString>& formats) const override
{
EnumDateFormatsExEx(&Locale_EnumDateFormatsProcExEx, localeName.Buffer(), DATE_LONGDATE, (LPARAM)&formats);
}
void GetYearMonthDateFormats(const WString& localeName, collections::List<WString>& formats) const override
{
EnumDateFormatsExEx(&Locale_EnumDateFormatsProcExEx, localeName.Buffer(), DATE_YEARMONTH, (LPARAM)&formats);
}
void GetLongTimeFormats(const WString& localeName, collections::List<WString>& formats) const override
{
EnumTimeFormatsEx(&EnumTimeFormatsProcEx, localeName.Buffer(), 0, (LPARAM)&formats);
}
void GetShortTimeFormats(const WString& localeName, collections::List<WString>& formats) const override
{
EnumTimeFormatsEx(&EnumTimeFormatsProcEx, localeName.Buffer(), TIME_NOSECONDS, (LPARAM)&formats);
}
WString FormatDate(const WString& localeName, const WString& format, DateTime date) const override
{
SYSTEMTIME st = DateTimeToSystemTime(date);
int length = GetDateFormatEx(localeName.Buffer(), 0, &st, format.Buffer(), NULL, 0, NULL);
if (length == 0) return L"";
Array<wchar_t> buffer(length);
GetDateFormatEx(localeName.Buffer(), 0, &st, format.Buffer(), &buffer[0], (int)buffer.Count(), NULL);
return &buffer[0];
}
WString FormatTime(const WString& localeName, const WString& format, DateTime time) const override
{
SYSTEMTIME st = DateTimeToSystemTime(time);
int length = GetTimeFormatEx(localeName.Buffer(), 0, &st, format.Buffer(), NULL, 0);
if (length == 0) return L"";
Array<wchar_t> buffer(length);
GetTimeFormatEx(localeName.Buffer(), 0, &st, format.Buffer(), &buffer[0], (int)buffer.Count());
return &buffer[0];
}
WString FormatNumber(const WString& localeName, const WString& number) const override
{
int length = GetNumberFormatEx(localeName.Buffer(), 0, number.Buffer(), NULL, NULL, 0);
if (length == 0) return L"";
Array<wchar_t> buffer(length);
GetNumberFormatEx(localeName.Buffer(), 0, number.Buffer(), NULL, &buffer[0], (int)buffer.Count());
return &buffer[0];
}
WString FormatCurrency(const WString& localeName, const WString& currency) const override
{
int length = GetCurrencyFormatEx(localeName.Buffer(), 0, currency.Buffer(), NULL, NULL, 0);
if (length == 0) return L"";
Array<wchar_t> buffer(length);
GetCurrencyFormatEx(localeName.Buffer(), 0, currency.Buffer(), NULL, &buffer[0], (int)buffer.Count());
return &buffer[0];
}
WString GetShortDayOfWeekName(const WString& localeName, vint dayOfWeek) const override
{
return FormatDate(localeName, L"ddd", DateTime::FromDateTime(2000, 1, 2 + dayOfWeek));
}
WString GetLongDayOfWeekName(const WString& localeName, vint dayOfWeek) const override
{
return FormatDate(localeName, L"dddd", DateTime::FromDateTime(2000, 1, 2 + dayOfWeek));
}
WString GetShortMonthName(const WString& localeName, vint month) const override
{
return FormatDate(localeName, L"MMM", DateTime::FromDateTime(2000, month, 1));
}
WString GetLongMonthName(const WString& localeName, vint month) const override
{
return FormatDate(localeName, L"MMMM", DateTime::FromDateTime(2000, month, 1));
}
WString ToLower(const WString& localeName, const WString& str) const override
{
return Transform(localeName, str, LCMAP_LOWERCASE);
}
WString ToUpper(const WString& localeName, const WString& str) const override
{
return Transform(localeName, str, LCMAP_UPPERCASE);
}
WString ToLinguisticLower(const WString& localeName, const WString& str) const override
{
return Transform(localeName, str, LCMAP_LOWERCASE | LCMAP_LINGUISTIC_CASING);
}
WString ToLinguisticUpper(const WString& localeName, const WString& str) const override
{
return Transform(localeName, str, LCMAP_UPPERCASE | LCMAP_LINGUISTIC_CASING);
}
vint Compare(const WString& localeName, const WString& s1, const WString& s2, Locale::Normalization normalization) const override
{
switch (CompareStringEx(localeName.Buffer(), TranslateNormalization(normalization), s1.Buffer(), (int)s1.Length(), s2.Buffer(), (int)s2.Length(), NULL, NULL, NULL))
{
case CSTR_LESS_THAN: return -1;
case CSTR_GREATER_THAN: return 1;
default: return 0;
}
}
vint CompareOrdinal(const WString& s1, const WString& s2) const override
{
switch (CompareStringOrdinal(s1.Buffer(), (int)s1.Length(), s2.Buffer(), (int)s2.Length(), FALSE))
{
case CSTR_LESS_THAN: return -1;
case CSTR_GREATER_THAN: return 1;
default: return 0;
}
}
vint CompareOrdinalIgnoreCase(const WString& s1, const WString& s2) const override
{
switch (CompareStringOrdinal(s1.Buffer(), (int)s1.Length(), s2.Buffer(), (int)s2.Length(), TRUE))
{
case CSTR_LESS_THAN: return -1;
case CSTR_GREATER_THAN: return 1;
default: return 0;
}
}
collections::Pair<vint, vint> FindFirst(const WString& localeName, const WString& text, const WString& find, Locale::Normalization normalization) const override
{
int length = 0;
int result = FindNLSStringEx(localeName.Buffer(), FIND_FROMSTART | TranslateNormalization(normalization), text.Buffer(), (int)text.Length(), find.Buffer(), (int)find.Length(), &length, NULL, NULL, NULL);
return result == -1 ? collections::Pair<vint, vint>(-1, 0) : collections::Pair<vint, vint>(result, length);
}
collections::Pair<vint, vint> FindLast(const WString& localeName, const WString& text, const WString& find, Locale::Normalization normalization) const override
{
int length = 0;
int result = FindNLSStringEx(localeName.Buffer(), FIND_FROMEND | TranslateNormalization(normalization), text.Buffer(), (int)text.Length(), find.Buffer(), (int)find.Length(), &length, NULL, NULL, NULL);
return result == -1 ? collections::Pair<vint, vint>(-1, 0) : collections::Pair<vint, vint>(result, length);
}
bool StartsWith(const WString& localeName, const WString& text, const WString& find, Locale::Normalization normalization) const override
{
int result = FindNLSStringEx(localeName.Buffer(), FIND_STARTSWITH | TranslateNormalization(normalization), text.Buffer(), (int)text.Length(), find.Buffer(), (int)find.Length(), NULL, NULL, NULL, NULL);
return result != -1;
}
bool EndsWith(const WString& localeName, const WString& text, const WString& find, Locale::Normalization normalization) const override
{
int result = FindNLSStringEx(localeName.Buffer(), FIND_ENDSWITH | TranslateNormalization(normalization), text.Buffer(), (int)text.Length(), find.Buffer(), (int)find.Length(), NULL, NULL, NULL, NULL);
return result != -1;
}
};
ILocaleImpl* GetOSLocaleImpl()
{
static WindowsLocaleImpl windowsLocaleImpl;
return &windowsLocaleImpl;
}
/***********************************************************************
Locale (Windows Specific)
***********************************************************************/
WString Locale::ToFullWidth(const WString& str)const
{
return Transform(localeName, str, LCMAP_FULLWIDTH);
}
WString Locale::ToHalfWidth(const WString& str)const
{
return Transform(localeName, str, LCMAP_HALFWIDTH);
}
WString Locale::ToHiragana(const WString& str)const
{
return Transform(localeName, str, LCMAP_HIRAGANA);
}
WString Locale::ToKatagana(const WString& str)const
{
return Transform(localeName, str, LCMAP_KATAKANA);
}
WString Locale::ToSimplifiedChinese(const WString& str)const
{
return Transform(localeName, str, LCMAP_SIMPLIFIED_CHINESE);
}
WString Locale::ToTraditionalChinese(const WString& str)const
{
return Transform(localeName, str, LCMAP_TRADITIONAL_CHINESE);
}
WString Locale::ToTileCase(const WString& str)const
{
return Transform(localeName, str, LCMAP_TITLECASE);
}
}
/***********************************************************************
.\THREADING.WINDOWS.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_MSVC
static_assert(false, "Do not build this file for non-Windows applications.");
#endif
namespace vl
{
using namespace threading_internal;
using namespace collections;
/***********************************************************************
WaitableObject
***********************************************************************/
namespace threading_internal
{
struct WaitableData
{
HANDLE handle;
WaitableData(HANDLE _handle)
:handle(_handle)
{
}
};
}
WaitableObject::WaitableObject()
:waitableData(0)
{
}
void WaitableObject::SetData(threading_internal::WaitableData* data)
{
waitableData=data;
}
bool WaitableObject::IsCreated()
{
return waitableData!=0;
}
bool WaitableObject::Wait()
{
return WaitForTime(INFINITE);
}
bool WaitableObject::WaitForTime(vint ms)
{
if(IsCreated())
{
if(WaitForSingleObject(waitableData->handle, (DWORD)ms)==WAIT_OBJECT_0)
{
return true;
}
}
return false;
}
bool WaitableObject::WaitAll(WaitableObject** objects, vint count)
{
Array<HANDLE> handles(count);
for(vint i=0;i<count;i++)
{
handles[i]=objects[i]->waitableData->handle;
}
DWORD result=WaitForMultipleObjects((DWORD)count, &handles[0], TRUE, INFINITE);
return result==WAIT_OBJECT_0 || result==WAIT_ABANDONED_0;
}
bool WaitableObject::WaitAllForTime(WaitableObject** objects, vint count, vint ms)
{
Array<HANDLE> handles(count);
for(vint i=0;i<count;i++)
{
handles[i]=objects[i]->waitableData->handle;
}
DWORD result=WaitForMultipleObjects((DWORD)count, &handles[0], TRUE, (DWORD)ms);
return result==WAIT_OBJECT_0 || result==WAIT_ABANDONED_0;
}
vint WaitableObject::WaitAny(WaitableObject** objects, vint count, bool* abandoned)
{
Array<HANDLE> handles(count);
for(vint i=0;i<count;i++)
{
handles[i]=objects[i]->waitableData->handle;
}
DWORD result=WaitForMultipleObjects((DWORD)count, &handles[0], FALSE, INFINITE);
if(WAIT_OBJECT_0 <= result && result<WAIT_OBJECT_0+count)
{
*abandoned=false;
return result-WAIT_OBJECT_0;
}
else if(WAIT_ABANDONED_0 <= result && result<WAIT_ABANDONED_0+count)
{
*abandoned=true;
return result-WAIT_ABANDONED_0;
}
else
{
return -1;
}
}
vint WaitableObject::WaitAnyForTime(WaitableObject** objects, vint count, vint ms, bool* abandoned)
{
Array<HANDLE> handles(count);
for(vint i=0;i<count;i++)
{
handles[i]=objects[i]->waitableData->handle;
}
DWORD result=WaitForMultipleObjects((DWORD)count, &handles[0], FALSE, (DWORD)ms);
if(WAIT_OBJECT_0 <= result && result<WAIT_OBJECT_0+count)
{
*abandoned=false;
return result-WAIT_OBJECT_0;
}
else if(WAIT_ABANDONED_0 <= result && result<WAIT_ABANDONED_0+count)
{
*abandoned=true;
return result-WAIT_ABANDONED_0;
}
else
{
return -1;
}
}
/***********************************************************************
Thread
***********************************************************************/
namespace threading_internal
{
struct ThreadData : public WaitableData
{
DWORD id;
ThreadData()
:WaitableData(NULL)
{
id=-1;
}
};
class ProceduredThread : public Thread
{
private:
Thread::ThreadProcedure procedure;
void* argument;
bool deleteAfterStopped;
protected:
void Run()
{
bool deleteAfterStopped = this->deleteAfterStopped;
ThreadLocalStorage::FixStorages();
try
{
procedure(this, argument);
threadState=Thread::Stopped;
ThreadLocalStorage::ClearStorages();
}
catch (...)
{
ThreadLocalStorage::ClearStorages();
throw;
}
if(deleteAfterStopped)
{
delete this;
}
}
public:
ProceduredThread(Thread::ThreadProcedure _procedure, void* _argument, bool _deleteAfterStopped)
:procedure(_procedure)
,argument(_argument)
,deleteAfterStopped(_deleteAfterStopped)
{
}
};
class LambdaThread : public Thread
{
private:
Func<void()> procedure;
bool deleteAfterStopped;
protected:
void Run()
{
bool deleteAfterStopped = this->deleteAfterStopped;
ThreadLocalStorage::FixStorages();
try
{
procedure();
threadState=Thread::Stopped;
ThreadLocalStorage::ClearStorages();
}
catch (...)
{
ThreadLocalStorage::ClearStorages();
throw;
}
if(deleteAfterStopped)
{
delete this;
}
}
public:
LambdaThread(const Func<void()>& _procedure, bool _deleteAfterStopped)
:procedure(_procedure)
,deleteAfterStopped(_deleteAfterStopped)
{
}
};
}
void InternalThreadProc(Thread* thread)
{
thread->Run();
}
DWORD WINAPI InternalThreadProcWrapper(LPVOID lpParameter)
{
InternalThreadProc((Thread*)lpParameter);
return 0;
}
Thread::Thread()
{
internalData=new ThreadData;
internalData->handle=CreateThread(NULL, 0, InternalThreadProcWrapper, this, CREATE_SUSPENDED, &internalData->id);
threadState=Thread::NotStarted;
SetData(internalData);
}
Thread::~Thread()
{
if (internalData)
{
Stop();
CloseHandle(internalData->handle);
delete internalData;
}
}
Thread* Thread::CreateAndStart(ThreadProcedure procedure, void* argument, bool deleteAfterStopped)
{
if(procedure)
{
Thread* thread=new ProceduredThread(procedure, argument, deleteAfterStopped);
if(thread->Start())
{
return thread;
}
else
{
delete thread;
}
}
return 0;
}
Thread* Thread::CreateAndStart(const Func<void()>& procedure, bool deleteAfterStopped)
{
Thread* thread=new LambdaThread(procedure, deleteAfterStopped);
if(thread->Start())
{
return thread;
}
else
{
delete thread;
}
return 0;
}
void Thread::Sleep(vint ms)
{
::Sleep((DWORD)ms);
}
vint Thread::GetCPUCount()
{
SYSTEM_INFO info;
GetSystemInfo(&info);
return info.dwNumberOfProcessors;
}
vint Thread::GetCurrentThreadId()
{
return (vint)::GetCurrentThreadId();
}
bool Thread::Start()
{
if(threadState==Thread::NotStarted && internalData->handle!=NULL)
{
if(ResumeThread(internalData->handle)!=-1)
{
threadState=Thread::Running;
return true;
}
}
return false;
}
bool Thread::Stop()
{
if(internalData->handle!=NULL)
{
if (SuspendThread(internalData->handle) != -1)
{
threadState=Thread::Stopped;
return true;
}
}
return false;
}
Thread::ThreadState Thread::GetState()
{
return threadState;
}
void Thread::SetCPU(vint index)
{
SetThreadAffinityMask(internalData->handle, ((vint)1 << index));
}
/***********************************************************************
Mutex
***********************************************************************/
namespace threading_internal
{
struct MutexData : public WaitableData
{
MutexData(HANDLE _handle)
:WaitableData(_handle)
{
}
};
}
Mutex::Mutex()
:internalData(0)
{
}
Mutex::~Mutex()
{
if(internalData)
{
CloseHandle(internalData->handle);
delete internalData;
}
}
bool Mutex::Create(bool owned, const WString& name)
{
if(IsCreated())return false;
BOOL aOwned=owned?TRUE:FALSE;
LPCTSTR aName=name==L""?NULL:name.Buffer();
HANDLE handle=CreateMutex(NULL, aOwned, aName);
if(handle)
{
internalData=new MutexData(handle);
SetData(internalData);
}
return IsCreated();
}
bool Mutex::Open(bool inheritable, const WString& name)
{
if(IsCreated())return false;
BOOL aInteritable=inheritable?TRUE:FALSE;
HANDLE handle=OpenMutex(SYNCHRONIZE, aInteritable, name.Buffer());
if(handle)
{
internalData=new MutexData(handle);
SetData(internalData);
}
return IsCreated();
}
bool Mutex::Release()
{
if(IsCreated())
{
return ReleaseMutex(internalData->handle)!=0;
}
return false;
}
/***********************************************************************
Semaphore
***********************************************************************/
namespace threading_internal
{
struct SemaphoreData : public WaitableData
{
SemaphoreData(HANDLE _handle)
:WaitableData(_handle)
{
}
};
}
Semaphore::Semaphore()
:internalData(0)
{
}
Semaphore::~Semaphore()
{
if(internalData)
{
CloseHandle(internalData->handle);
delete internalData;
}
}
bool Semaphore::Create(vint initialCount, vint maxCount, const WString& name)
{
if(IsCreated())return false;
LONG aInitial=(LONG)initialCount;
LONG aMax=(LONG)maxCount;
LPCTSTR aName=name==L""?NULL:name.Buffer();
HANDLE handle=CreateSemaphore(NULL, aInitial, aMax, aName);
if(handle)
{
internalData=new SemaphoreData(handle);
SetData(internalData);
}
return IsCreated();
}
bool Semaphore::Open(bool inheritable, const WString& name)
{
if(IsCreated())return false;
BOOL aInteritable=inheritable?TRUE:FALSE;
HANDLE handle=OpenSemaphore(SYNCHRONIZE, aInteritable, name.Buffer());
if(handle)
{
internalData=new SemaphoreData(handle);
SetData(internalData);
}
return IsCreated();
}
bool Semaphore::Release()
{
if(IsCreated())
{
return Release(1)!=-1;
}
return false;
}
vint Semaphore::Release(vint count)
{
if(IsCreated())
{
LONG previous=-1;
if(ReleaseSemaphore(internalData->handle, (LONG)count, &previous)!=0)
{
return (vint)previous;
}
}
return -1;
}
/***********************************************************************
EventObject
***********************************************************************/
namespace threading_internal
{
struct EventData : public WaitableData
{
EventData(HANDLE _handle)
:WaitableData(_handle)
{
}
};
}
EventObject::EventObject()
:internalData(0)
{
}
EventObject::~EventObject()
{
if(internalData)
{
CloseHandle(internalData->handle);
delete internalData;
}
}
bool EventObject::CreateAutoUnsignal(bool signaled, const WString& name)
{
if(IsCreated())return false;
BOOL aSignaled=signaled?TRUE:FALSE;
LPCTSTR aName=name==L""?NULL:name.Buffer();
HANDLE handle=CreateEvent(NULL, FALSE, aSignaled, aName);
if(handle)
{
internalData=new EventData(handle);
SetData(internalData);
}
return IsCreated();
}
bool EventObject::CreateManualUnsignal(bool signaled, const WString& name)
{
if(IsCreated())return false;
BOOL aSignaled=signaled?TRUE:FALSE;
LPCTSTR aName=name==L""?NULL:name.Buffer();
HANDLE handle=CreateEvent(NULL, TRUE, aSignaled, aName);
if(handle)
{
internalData=new EventData(handle);
SetData(internalData);
}
return IsCreated();
}
bool EventObject::Open(bool inheritable, const WString& name)
{
if(IsCreated())return false;
BOOL aInteritable=inheritable?TRUE:FALSE;
HANDLE handle=OpenEvent(SYNCHRONIZE, aInteritable, name.Buffer());
if(handle)
{
internalData=new EventData(handle);
SetData(internalData);
}
return IsCreated();
}
bool EventObject::Signal()
{
if(IsCreated())
{
return SetEvent(internalData->handle)!=0;
}
return false;
}
bool EventObject::Unsignal()
{
if(IsCreated())
{
return ResetEvent(internalData->handle)!=0;
}
return false;
}
/***********************************************************************
ThreadPoolLite
***********************************************************************/
DWORD WINAPI ThreadPoolQueueFunc(void* argument)
{
auto proc=Ptr((Func<void()>*)argument);
ThreadLocalStorage::FixStorages();
try
{
(*proc.Obj())();
ThreadLocalStorage::ClearStorages();
}
catch (...)
{
ThreadLocalStorage::ClearStorages();
}
return 0;
}
ThreadPoolLite::ThreadPoolLite()
{
}
ThreadPoolLite::~ThreadPoolLite()
{
}
bool ThreadPoolLite::Queue(void(*proc)(void*), void* argument)
{
return Queue([=]() {proc(argument); });
}
bool ThreadPoolLite::Queue(const Func<void()>& proc)
{
Func<void()>* p=new Func<void()>(proc);
if(QueueUserWorkItem(&ThreadPoolQueueFunc, p, WT_EXECUTEDEFAULT))
{
return true;
}
else
{
delete p;
return false;
}
}
/***********************************************************************
CriticalSection
***********************************************************************/
namespace threading_internal
{
struct CriticalSectionData
{
CRITICAL_SECTION criticalSection;
};
}
CriticalSection::Scope::Scope(CriticalSection& _criticalSection)
:criticalSection(&_criticalSection)
{
criticalSection->Enter();
}
CriticalSection::Scope::~Scope()
{
criticalSection->Leave();
}
CriticalSection::CriticalSection()
{
internalData=new CriticalSectionData;
InitializeCriticalSection(&internalData->criticalSection);
}
CriticalSection::~CriticalSection()
{
DeleteCriticalSection(&internalData->criticalSection);
delete internalData;
}
bool CriticalSection::TryEnter()
{
return TryEnterCriticalSection(&internalData->criticalSection)!=0;
}
void CriticalSection::Enter()
{
EnterCriticalSection(&internalData->criticalSection);
}
void CriticalSection::Leave()
{
LeaveCriticalSection(&internalData->criticalSection);
}
/***********************************************************************
ReaderWriterLock
***********************************************************************/
namespace threading_internal
{
struct ReaderWriterLockData
{
SRWLOCK lock;
};
}
ReaderWriterLock::ReaderScope::ReaderScope(ReaderWriterLock& _lock)
:lock(&_lock)
{
lock->EnterReader();
}
ReaderWriterLock::ReaderScope::~ReaderScope()
{
lock->LeaveReader();
}
ReaderWriterLock::WriterScope::WriterScope(ReaderWriterLock& _lock)
:lock(&_lock)
{
lock->EnterWriter();
}
ReaderWriterLock::WriterScope::~WriterScope()
{
lock->LeaveWriter();
}
ReaderWriterLock::ReaderWriterLock()
:internalData(new threading_internal::ReaderWriterLockData)
{
InitializeSRWLock(&internalData->lock);
}
ReaderWriterLock::~ReaderWriterLock()
{
delete internalData;
}
bool ReaderWriterLock::TryEnterReader()
{
return TryAcquireSRWLockShared(&internalData->lock)!=0;
}
void ReaderWriterLock::EnterReader()
{
AcquireSRWLockShared(&internalData->lock);
}
void ReaderWriterLock::LeaveReader()
{
ReleaseSRWLockShared(&internalData->lock);
}
bool ReaderWriterLock::TryEnterWriter()
{
return TryAcquireSRWLockExclusive(&internalData->lock)!=0;
}
void ReaderWriterLock::EnterWriter()
{
AcquireSRWLockExclusive(&internalData->lock);
}
void ReaderWriterLock::LeaveWriter()
{
ReleaseSRWLockExclusive(&internalData->lock);
}
/***********************************************************************
ConditionVariable
***********************************************************************/
namespace threading_internal
{
struct ConditionVariableData
{
CONDITION_VARIABLE variable;
};
}
ConditionVariable::ConditionVariable()
:internalData(new threading_internal::ConditionVariableData)
{
InitializeConditionVariable(&internalData->variable);
}
ConditionVariable::~ConditionVariable()
{
delete internalData;
}
bool ConditionVariable::SleepWith(CriticalSection& cs)
{
return SleepConditionVariableCS(&internalData->variable, &cs.internalData->criticalSection, INFINITE)!=0;
}
bool ConditionVariable::SleepWithForTime(CriticalSection& cs, vint ms)
{
return SleepConditionVariableCS(&internalData->variable, &cs.internalData->criticalSection, (DWORD)ms)!=0;
}
bool ConditionVariable::SleepWithReader(ReaderWriterLock& lock)
{
return SleepConditionVariableSRW(&internalData->variable, &lock.internalData->lock, INFINITE, CONDITION_VARIABLE_LOCKMODE_SHARED)!=0;
}
bool ConditionVariable::SleepWithReaderForTime(ReaderWriterLock& lock, vint ms)
{
return SleepConditionVariableSRW(&internalData->variable, &lock.internalData->lock, (DWORD)ms, CONDITION_VARIABLE_LOCKMODE_SHARED)!=0;
}
bool ConditionVariable::SleepWithWriter(ReaderWriterLock& lock)
{
return SleepConditionVariableSRW(&internalData->variable, &lock.internalData->lock, INFINITE, 0)!=0;
}
bool ConditionVariable::SleepWithWriterForTime(ReaderWriterLock& lock, vint ms)
{
return SleepConditionVariableSRW(&internalData->variable, &lock.internalData->lock, (DWORD)ms, 0)!=0;
}
void ConditionVariable::WakeOnePending()
{
WakeConditionVariable(&internalData->variable);
}
void ConditionVariable::WakeAllPendings()
{
WakeAllConditionVariable(&internalData->variable);
}
/***********************************************************************
ThreadLocalStorage
***********************************************************************/
#define KEY ((DWORD&)key)
ThreadLocalStorage::ThreadLocalStorage(Destructor _destructor)
:destructor(_destructor)
{
static_assert(sizeof(key) >= sizeof(DWORD), "ThreadLocalStorage's key storage is not large enouth.");
PushStorage(this);
KEY = TlsAlloc();
CHECK_ERROR(KEY != TLS_OUT_OF_INDEXES, L"vl::ThreadLocalStorage::ThreadLocalStorage()#Failed to alloc new thread local storage index.");
}
ThreadLocalStorage::~ThreadLocalStorage()
{
TlsFree(KEY);
}
void* ThreadLocalStorage::Get()
{
CHECK_ERROR(!disposed, L"vl::ThreadLocalStorage::Get()#Cannot access a disposed ThreadLocalStorage.");
return TlsGetValue(KEY);
}
void ThreadLocalStorage::Set(void* data)
{
CHECK_ERROR(!disposed, L"vl::ThreadLocalStorage::Set()#Cannot access a disposed ThreadLocalStorage.");
TlsSetValue(KEY, data);
}
#undef KEY
}
/***********************************************************************
.\ENCODING\CHARFORMAT\CHARFORMAT.WINDOWS.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_MSVC
static_assert(false, "Do not build this file for non-Windows applications.");
#endif
namespace vl
{
namespace stream
{
bool IsMbcsLeadByte(char c)
{
return IsDBCSLeadByte(c);
}
void MbcsToWChar(wchar_t* wideBuffer, vint wideChars, vint wideReaded, char* mbcsBuffer, vint mbcsChars)
{
MultiByteToWideChar(CP_THREAD_ACP, 0, mbcsBuffer, (int)mbcsChars, wideBuffer, (int)wideChars);
}
/***********************************************************************
MbcsEncoder
***********************************************************************/
vint MbcsEncoder::WriteString(wchar_t* _buffer, vint chars)
{
vint length = WideCharToMultiByte(CP_THREAD_ACP, 0, _buffer, (int)chars, NULL, NULL, NULL, NULL);
char* mbcs = new char[length];
WideCharToMultiByte(CP_THREAD_ACP, 0, _buffer, (int)chars, mbcs, (int)length, NULL, NULL);
vint result = stream->Write(mbcs, length);
delete[] mbcs;
if (result != length)
{
Close();
return 0;
}
return chars;
}
/***********************************************************************
Helper Functions
***********************************************************************/
extern bool CanBeMbcs(unsigned char* buffer, vint size);
extern bool CanBeUtf8(unsigned char* buffer, vint size);
extern bool CanBeUtf16(unsigned char* buffer, vint size, bool& hitSurrogatePairs);
extern bool CanBeUtf16BE(unsigned char* buffer, vint size, bool& hitSurrogatePairs);
template<vint Count>
bool GetEncodingResult(int(&tests)[Count], bool(&results)[Count], int test)
{
for (vint i = 0; i < Count; i++)
{
if (tests[i] & test)
{
if (results[i]) return true;
}
}
return false;
}
/***********************************************************************
TestEncoding
***********************************************************************/
extern void TestEncodingInternal(
unsigned char* buffer,
vint size,
BomEncoder::Encoding& encoding,
bool containsBom,
bool utf16HitSurrogatePairs,
bool utf16BEHitSurrogatePairs,
bool roughMbcs,
bool roughUtf8,
bool roughUtf16,
bool roughUtf16BE
)
{
int tests[] =
{
IS_TEXT_UNICODE_REVERSE_ASCII16,
IS_TEXT_UNICODE_REVERSE_STATISTICS,
IS_TEXT_UNICODE_REVERSE_CONTROLS,
IS_TEXT_UNICODE_ASCII16,
IS_TEXT_UNICODE_STATISTICS,
IS_TEXT_UNICODE_CONTROLS,
IS_TEXT_UNICODE_ILLEGAL_CHARS,
IS_TEXT_UNICODE_ODD_LENGTH,
IS_TEXT_UNICODE_NULL_BYTES,
};
const vint TestCount = sizeof(tests) / sizeof(*tests);
bool results[TestCount];
for (vint i = 0; i < TestCount; i++)
{
int test = tests[i];
results[i] = IsTextUnicode(buffer, (int)size, &test) != 0;
}
if (size % 2 == 0
&& !GetEncodingResult(tests, results, IS_TEXT_UNICODE_REVERSE_ASCII16)
&& !GetEncodingResult(tests, results, IS_TEXT_UNICODE_REVERSE_STATISTICS)
&& !GetEncodingResult(tests, results, IS_TEXT_UNICODE_REVERSE_CONTROLS)
)
{
for (vint i = 0; i < size; i += 2)
{
unsigned char c = buffer[i];
buffer[i] = buffer[i + 1];
buffer[i + 1] = c;
}
// 3 = (count of reverse group) = (count of unicode group)
for (vint i = 0; i < 3; i++)
{
int test = tests[i + 3];
results[i] = IsTextUnicode(buffer, (int)size, &test) != 0;
}
for (vint i = 0; i < size; i += 2)
{
unsigned char c = buffer[i];
buffer[i] = buffer[i + 1];
buffer[i + 1] = c;
}
}
if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_NOT_UNICODE_MASK))
{
if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_NOT_ASCII_MASK))
{
encoding = BomEncoder::Utf8;
}
else if (roughUtf8 || !roughMbcs)
{
encoding = BomEncoder::Utf8;
}
}
else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_ASCII16))
{
encoding = BomEncoder::Utf16;
}
else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_REVERSE_ASCII16))
{
encoding = BomEncoder::Utf16BE;
}
else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_CONTROLS))
{
encoding = BomEncoder::Utf16;
}
else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_REVERSE_CONTROLS))
{
encoding = BomEncoder::Utf16BE;
}
else
{
if (!roughUtf8)
{
if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_STATISTICS))
{
encoding = BomEncoder::Utf16;
}
else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_STATISTICS))
{
encoding = BomEncoder::Utf16BE;
}
}
else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_NOT_UNICODE_MASK))
{
encoding = BomEncoder::Utf8;
}
else if (roughUtf8 || !roughMbcs)
{
encoding = BomEncoder::Utf8;
}
}
}
}
}
/***********************************************************************
.\INTERPROCESS\WINDOWS\HTTPCLIENT.WINDOWS.CPP
***********************************************************************/
namespace vl::inter_process
{
using namespace vl::collections;
/***********************************************************************
HttpClient (Reading)
***********************************************************************/
void HttpClient::RaiseErrorUnsafe(WString errorMessage)
{
if (callback)
{
callback->OnReadError(errorMessage);
}
}
void HttpClient::BeginReadingLoopUnsafe()
{
if (state == State::Stopping) return;
CHECK_ERROR(state == State::Running, L"BeginReadingLoopUnsafe can only be called when client is running.");
DWORD lastError = 0;
BOOL httpResult = FALSE;
LPCWSTR acceptTypes[] = { L"application/json; charset=utf8", NULL };
HINTERNET httpRequest = WinHttpOpenRequest(
httpConnection,
L"POST",
urlRequest.Buffer(),
NULL,
WINHTTP_NO_REFERER,
acceptTypes,
WINHTTP_FLAG_REFRESH);
lastError = GetLastError();
if (lastError == ERROR_INVALID_HANDLE)
{
CHECK_ERROR(state == State::Stopping, L"WinHttpOpenRequest failed with ERROR_INVALID_HANDLE but client is not stopping.");
return;
}
CHECK_ERROR(httpRequest != NULL, L"WinHttpOpenRequest failed.");
{
auto self = this;
WINHTTP_STATUS_CALLBACK previousCallback = WinHttpSetStatusCallback(
httpRequest,
(WINHTTP_STATUS_CALLBACK)[](HINTERNET httpRequest, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) -> void
{
if (!dwContext) return;
auto self = reinterpret_cast<HttpClient*>(dwContext);
switch (dwInternetStatus)
{
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
{
self->QueueCallback([=]()
{
if (self->state == State::Stopping) return;
DWORD lastError = 0;
BOOL httpResult = WinHttpReceiveResponse(httpRequest, NULL);
lastError = GetLastError();
if (lastError == ERROR_INVALID_HANDLE)
{
CHECK_ERROR(self->state == State::Stopping, L"WinHttpReceiveResponse failed with ERROR_INVALID_HANDLE but client is not stopping.");
WinHttpCloseHandle(httpRequest);
return;
}
CHECK_ERROR(httpResult == TRUE, L"WinHttpReceiveResponse failed.");
});
}
break;
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
{
self->QueueCallback([=]()
{
if (self->state == State::Stopping) return;
DWORD lastError = 0;
DWORD statusCode = 0;
DWORD dwordLength = sizeof(DWORD);
BOOL httpResult = FALSE;
{
httpResult = WinHttpQueryHeaders(
httpRequest,
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX,
&statusCode,
&dwordLength,
WINHTTP_NO_HEADER_INDEX);
lastError = GetLastError();
if (lastError == ERROR_INVALID_HANDLE)
{
CHECK_ERROR(self->state == State::Stopping, L"WinHttpQueryHeaders failed with ERROR_INVALID_HANDLE but client is not stopping.");
return;
}
CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryHeaders failed to retrieve status code.");
if (statusCode != 200)
{
self->CloseRequest(httpRequest);
self->RaiseErrorUnsafe(WString::Unmanaged(L"/Request returned status code: ") + itow(statusCode) + L", another renderer may have connected to the core.");
return;
}
}
{
DWORD headerLength = 0;
httpResult = WinHttpQueryHeaders(
httpRequest,
WINHTTP_QUERY_CONTENT_TYPE,
WINHTTP_HEADER_NAME_BY_INDEX,
NULL,
&headerLength,
WINHTTP_NO_HEADER_INDEX);
lastError = GetLastError();
if (lastError == ERROR_INVALID_HANDLE)
{
CHECK_ERROR(self->state == State::Stopping, L"WinHttpQueryHeaders failed with ERROR_INVALID_HANDLE but client is not stopping.");
return;
}
CHECK_ERROR(httpResult == FALSE && lastError == ERROR_INSUFFICIENT_BUFFER, L"WinHttpQueryHeaders failed to retrieve content type.");
Array<wchar_t> headerBuffer(headerLength / 2 + 1);
ZeroMemory(&headerBuffer[0], headerBuffer.Count() * sizeof(wchar_t));
httpResult = WinHttpQueryHeaders(
httpRequest,
WINHTTP_QUERY_CONTENT_TYPE,
WINHTTP_HEADER_NAME_BY_INDEX,
&headerBuffer[0],
&headerLength,
WINHTTP_NO_HEADER_INDEX);
lastError = GetLastError();
if (lastError == ERROR_INVALID_HANDLE)
{
CHECK_ERROR(self->state == State::Stopping, L"WinHttpQueryHeaders failed with ERROR_INVALID_HANDLE but client is not stopping.");
return;
}
CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryHeaders failed to retrieve content-type.");
const wchar_t* header = &headerBuffer[0];
CHECK_ERROR(wcscmp(header, L"application/json; charset=utf8") == 0, L"/Request did not return content type: application/json; charset=utf8.");
}
{
self->httpRespondBodyBufferWriting = 0;
httpResult = WinHttpQueryDataAvailable(
httpRequest,
NULL);
lastError = GetLastError();
if (lastError == ERROR_INVALID_HANDLE)
{
CHECK_ERROR(self->state == State::Stopping, L"WinHttpQueryDataAvailable failed with ERROR_INVALID_HANDLE but client is not stopping.");
return;
}
CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryDataAvailable failed.");
}
});
}
break;
case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
{
DWORD dataAvailable = *(PDWORD)lpvStatusInformation;
self->QueueCallback([=]()
{
if (self->state == State::Stopping) return;
if (dataAvailable == 0)
{
if (self->callback)
{
self->httpRespondBodyBuffer[self->httpRespondBodyBufferWriting] = 0;
U8String bodyUtf8 = U8String::Unmanaged(&self->httpRespondBodyBuffer[0]);
self->callback->OnReadString(u8tow(bodyUtf8));
}
self->CloseRequest(httpRequest);
self->BeginReadingLoopUnsafe();
return;
}
self->httpRespondBodyBufferWritingAvailable = dataAvailable;
DWORD bufferSize = self->httpRespondBodyBufferWriting + dataAvailable + 1;
if (self->httpRespondBodyBuffer.Count() < (vint)bufferSize)
{
self->httpRespondBodyBuffer.Resize((bufferSize + HttpRespondBodyStep - 1) / HttpRespondBodyStep * HttpRespondBodyStep);
}
DWORD lastError = 0;
BOOL httpResult = WinHttpReadData(
httpRequest,
&self->httpRespondBodyBuffer[self->httpRespondBodyBufferWriting],
dataAvailable,
NULL);
lastError = GetLastError();
if (lastError == ERROR_INVALID_HANDLE)
{
CHECK_ERROR(self->state == State::Stopping, L"WinHttpReadData failed with ERROR_INVALID_HANDLE but client is not stopping.");
return;
}
CHECK_ERROR(httpResult == TRUE, L"WinHttpReadData failed.");
});
}
break;
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
{
self->QueueCallback([=]()
{
if (self->state == State::Stopping) return;
CHECK_ERROR(
self->httpRespondBodyBufferWritingAvailable == dwStatusInformationLength,
L"WinHttpReadData failed to read all available data."
);
self->httpRespondBodyBufferWriting += self->httpRespondBodyBufferWritingAvailable;
DWORD lastError = 0;
BOOL httpResult = WinHttpQueryDataAvailable(
httpRequest,
NULL);
lastError = GetLastError();
if (lastError == ERROR_INVALID_HANDLE)
{
CHECK_ERROR(self->state == State::Stopping, L"WinHttpQueryDataAvailable failed with ERROR_INVALID_HANDLE but client is not stopping.");
return;
}
CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryDataAvailable failed.");
});
}
break;
case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
{
self->QueueCallback([=]()
{
if (self->state == State::Stopping) return;
self->CloseRequest(httpRequest);
self->RaiseErrorUnsafe(WString::Unmanaged(L"/Request canceled, another renderer may have connected to the core."));
});
}
break;
case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
self->OnRequestHandleClosing(httpRequest);
break;
}
},
WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS,
NULL);
lastError = GetLastError();
if (previousCallback == WINHTTP_INVALID_STATUS_CALLBACK && lastError == ERROR_INVALID_HANDLE)
{
CHECK_ERROR(state == State::Stopping, L"WinHttpSetStatusCallback failed with ERROR_INVALID_HANDLE but client is not stopping.");
WinHttpCloseHandle(httpRequest);
return;
}
CHECK_ERROR(previousCallback != WINHTTP_INVALID_STATUS_CALLBACK, L"WinHttpSetStatusCallback failed.");
}
{
AttachRequest(httpRequest);
httpResult = WinHttpSendRequest(
httpRequest,
WINHTTP_NO_ADDITIONAL_HEADERS,
0,
WINHTTP_NO_REQUEST_DATA,
0,
0,
reinterpret_cast<DWORD_PTR>(this));
lastError = GetLastError();
if (httpResult == FALSE && lastError == ERROR_INVALID_HANDLE)
{
OnRequestHandleClosing(httpRequest);
CHECK_ERROR(state == State::Stopping, L"WinHttpSendRequest failed with ERROR_INVALID_HANDLE but client is not stopping.");
WinHttpCloseHandle(httpRequest);
return;
}
if (httpResult == FALSE)
{
OnRequestHandleClosing(httpRequest);
WinHttpCloseHandle(httpRequest);
CHECK_FAIL(L"WinHttpSendRequest failed.");
}
}
}
/***********************************************************************
HttpClient (WaitForServer)
***********************************************************************/
INetworkProtocolConnection* HttpClient::GetConnection()
{
return this;
}
void HttpClient::WaitForServer()
{
if (state == State::Stopping) return;
CHECK_ERROR(state == State::Ready, L"WaitForServer can only be called once.");
DWORD lastError = 0;
state = State::WaitForServerConnection;
LPCWSTR acceptTypes[] = { L"application/json; charset=utf8", NULL };
BOOL httpResult = FALSE;
HINTERNET httpRequest = WinHttpOpenRequest(
httpConnection,
L"GET",
urlConnect.Buffer(),
NULL,
WINHTTP_NO_REFERER,
acceptTypes,
WINHTTP_FLAG_REFRESH);
lastError = GetLastError();
CHECK_ERROR(httpRequest != NULL, L"WinHttpOpenRequest failed.");
{
WINHTTP_STATUS_CALLBACK previousCallback = WinHttpSetStatusCallback(
httpRequest,
(WINHTTP_STATUS_CALLBACK)[](HINTERNET httpRequest, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) -> void
{
if (!dwContext) return;
auto self = reinterpret_cast<HttpClient*>(dwContext);
switch (dwInternetStatus)
{
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
{
self->dwInternetStatus_WaitForServer = dwInternetStatus;
self->dwStatusInformationLength_WaitForServer = dwStatusInformationLength;
SetEvent(self->hEventWaitForServer);
}
break;
}
},
WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS,
NULL);
lastError = GetLastError();
CHECK_ERROR(previousCallback != WINHTTP_INVALID_STATUS_CALLBACK, L"WinHttpSetStatusCallback failed.");
}
{
ResetEvent(hEventWaitForServer);
httpResult = WinHttpSendRequest(
httpRequest,
WINHTTP_NO_ADDITIONAL_HEADERS,
0,
WINHTTP_NO_REQUEST_DATA,
0,
0,
reinterpret_cast<DWORD_PTR>(this));
lastError = GetLastError();
CHECK_ERROR(httpResult == TRUE, L"WinHttpSendRequest failed.");
WaitForSingleObject(hEventWaitForServer, INFINITE);
CHECK_ERROR(dwInternetStatus_WaitForServer == WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE, L"WinHttpSendRequest failed to complete.");
}
{
ResetEvent(hEventWaitForServer);
httpResult = WinHttpReceiveResponse(httpRequest, NULL);
lastError = GetLastError();
CHECK_ERROR(httpResult == TRUE, L"WinHttpReceiveResponse failed.");
WaitForSingleObject(hEventWaitForServer, INFINITE);
CHECK_ERROR(dwInternetStatus_WaitForServer == WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE, L"WinHttpSendRequest failed to complete.");
}
DWORD statusCode = 0;
DWORD dataLength = 0;
DWORD dwordLength = sizeof(DWORD);
{
httpResult = WinHttpQueryHeaders(
httpRequest,
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX,
&statusCode,
&dwordLength,
WINHTTP_NO_HEADER_INDEX);
lastError = GetLastError();
CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryHeaders failed to retrieve status code.");
CHECK_ERROR(statusCode == 200, L"/Connect did not return status code: 200.");
}
{
DWORD headerLength = 0;
httpResult = WinHttpQueryHeaders(
httpRequest,
WINHTTP_QUERY_CONTENT_TYPE,
WINHTTP_HEADER_NAME_BY_INDEX,
NULL,
&headerLength,
WINHTTP_NO_HEADER_INDEX);
lastError = GetLastError();
CHECK_ERROR(httpResult == FALSE && lastError == ERROR_INSUFFICIENT_BUFFER, L"WinHttpQueryHeaders failed to retrieve content type.");
Array<wchar_t> headerBuffer(headerLength + 1);
ZeroMemory(&headerBuffer[0], headerBuffer.Count() * sizeof(wchar_t));
httpResult = WinHttpQueryHeaders(
httpRequest,
WINHTTP_QUERY_CONTENT_TYPE,
WINHTTP_HEADER_NAME_BY_INDEX,
&headerBuffer[0],
&headerLength,
WINHTTP_NO_HEADER_INDEX);
lastError = GetLastError();
CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryHeaders failed to retrieve content-type.");
const wchar_t* header = &headerBuffer[0];
CHECK_ERROR(wcscmp(header, L"application/json; charset=utf8") == 0, L"/Content did not return content type: application/json; charset=utf8.");
}
{
httpResult = WinHttpQueryDataAvailable(
httpRequest,
&dataLength);
lastError = GetLastError();
CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryDataAvailable failed.");
}
{
Array<char8_t> bodyBuffer(dataLength + 1);
ZeroMemory(&bodyBuffer[0], bodyBuffer.Count() * sizeof(char8_t));
ResetEvent(hEventWaitForServer);
httpResult = WinHttpReadData(
httpRequest,
&bodyBuffer[0],
dataLength,
NULL);
lastError = GetLastError();
CHECK_ERROR(httpResult == TRUE, L"WinHttpReadData failed.");
WaitForSingleObject(hEventWaitForServer, INFINITE);
CHECK_ERROR(dwInternetStatus_WaitForServer == WINHTTP_CALLBACK_STATUS_READ_COMPLETE, L"WinHttpReadData failed to complete.");
CHECK_ERROR(dwStatusInformationLength_WaitForServer == dataLength, L"WinHttpReadData failed to read full data.");
U8String bodyUtf8 = U8String::Unmanaged(&bodyBuffer[0]);
vint separatorIndex = bodyUtf8.IndexOf(L';');
CHECK_ERROR(separatorIndex != -1, L"/Connect response body is not in the correct format: requestUrl;responseUrl.");
urlRequest = baseUrl + u8tow(bodyUtf8.Left(separatorIndex));
urlResponse = baseUrl + u8tow(bodyUtf8.Right(bodyUtf8.Length() - separatorIndex - 1));
}
WinHttpCloseHandle(httpRequest);
state = State::Running;
if (callback)
{
callback->OnConnected();
}
}
ClientStatus HttpClient::GetStatus()
{
switch (state)
{
case State::Ready:
return ClientStatus::Ready;
case State::WaitForServerConnection:
return ClientStatus::WaitingForServer;
case State::Running:
return ClientStatus::Connected;
default:
return ClientStatus::Disconnected;
}
}
/***********************************************************************
HttpClient (Writing)
***********************************************************************/
void HttpClient::SendString(const WString& str)
{
if (state == State::Stopping) return;
CHECK_ERROR(state == State::Running, L"SendString can only be called when client is running.");
DWORD lastError = 0;
BOOL httpResult = FALSE;
HINTERNET httpRequest = WinHttpOpenRequest(
httpConnection,
L"POST",
urlResponse.Buffer(),
NULL,
WINHTTP_NO_REFERER,
NULL,
WINHTTP_FLAG_REFRESH);
lastError = GetLastError();
if (lastError == ERROR_INVALID_HANDLE)
{
CHECK_ERROR(state == State::Stopping, L"WinHttpOpenRequest failed with ERROR_INVALID_HANDLE.");
return;
}
CHECK_ERROR(httpRequest != NULL, L"WinHttpOpenRequest failed.");
WINHTTP_STATUS_CALLBACK previousCallback = WinHttpSetStatusCallback(
httpRequest,
(WINHTTP_STATUS_CALLBACK)[](HINTERNET httpRequest, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) -> void
{
if (!dwContext) return;
auto contextPtr = reinterpret_cast<Ptr<HttpClient::HttpRequestContext>*>(dwContext);
auto context = *contextPtr;
auto self = context->client;
auto requestId = context->requestId;
switch (dwInternetStatus)
{
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
{
self->QueueCallback([=, context = std::move(context)]()
{
if (self->state == State::Stopping) return;
DWORD lastError = 0;
BOOL httpResult = WinHttpReceiveResponse(httpRequest, NULL);
lastError = GetLastError();
if (lastError == ERROR_INVALID_HANDLE)
{
CHECK_ERROR(self->state == State::Stopping, L"WinHttpReceiveResponse failed with ERROR_INVALID_HANDLE but client is not stopping.");
return;
}
CHECK_ERROR(httpResult == TRUE, L"WinHttpReceiveResponse failed.");
});
}
break;
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
{
self->QueueCallback([=, context = std::move(context)]()
{
if (self->state == State::Stopping) return;
DWORD lastError = 0;
DWORD statusCode = 0;
DWORD dwordLength = sizeof(DWORD);
BOOL httpResult = WinHttpQueryHeaders(
httpRequest,
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX,
&statusCode,
&dwordLength,
WINHTTP_NO_HEADER_INDEX);
lastError = GetLastError();
if (lastError == ERROR_INVALID_HANDLE)
{
CHECK_ERROR(self->state == State::Stopping, L"WinHttpQueryHeaders failed with ERROR_INVALID_HANDLE but client is not stopping.");
return;
}
CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryHeaders failed to retrieve status code.");
if (statusCode != 200)
{
self->CloseRequest(httpRequest, requestId);
self->RaiseErrorUnsafe(WString::Unmanaged(L"/Response returned status code: ") + itow(statusCode) + L", another renderer may have connected to the core.");
return;
}
auto reading = context->responseReading;
reading->bodyBufferWriting = 0;
httpResult = WinHttpQueryDataAvailable(
httpRequest,
NULL);
lastError = GetLastError();
if (lastError == ERROR_INVALID_HANDLE)
{
CHECK_ERROR(self->state == State::Stopping, L"WinHttpQueryDataAvailable failed with ERROR_INVALID_HANDLE but client is not stopping.");
return;
}
CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryDataAvailable failed.");
});
}
break;
case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
{
DWORD dataAvailable = *(PDWORD)lpvStatusInformation;
self->QueueCallback([=, context = std::move(context)]()
{
if (self->state == State::Stopping) return;
auto reading = context->responseReading;
if (dataAvailable == 0)
{
if (reading->bodyBufferWriting > 0 && self->callback)
{
reading->bodyBuffer[reading->bodyBufferWriting] = 0;
U8String bodyUtf8 = U8String::Unmanaged(&reading->bodyBuffer[0]);
self->callback->OnReadString(u8tow(bodyUtf8));
}
self->CloseRequest(httpRequest, requestId);
return;
}
reading->bodyBufferWritingAvailable = dataAvailable;
DWORD bufferSize = reading->bodyBufferWriting + dataAvailable + 1;
if (reading->bodyBuffer.Count() < (vint)bufferSize)
{
reading->bodyBuffer.Resize((bufferSize + HttpRespondBodyStep - 1) / HttpRespondBodyStep * HttpRespondBodyStep);
}
DWORD lastError = 0;
BOOL httpResult = WinHttpReadData(
httpRequest,
&reading->bodyBuffer[reading->bodyBufferWriting],
dataAvailable,
NULL);
lastError = GetLastError();
if (lastError == ERROR_INVALID_HANDLE)
{
CHECK_ERROR(self->state == State::Stopping, L"WinHttpReadData failed with ERROR_INVALID_HANDLE but client is not stopping.");
return;
}
CHECK_ERROR(httpResult == TRUE, L"WinHttpReadData failed.");
});
}
break;
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
{
self->QueueCallback([=, context = std::move(context)]()
{
if (self->state == State::Stopping) return;
auto reading = context->responseReading;
CHECK_ERROR(
reading->bodyBufferWritingAvailable == dwStatusInformationLength,
L"WinHttpReadData failed to read all available data."
);
reading->bodyBufferWriting += reading->bodyBufferWritingAvailable;
DWORD lastError = 0;
BOOL httpResult = WinHttpQueryDataAvailable(
httpRequest,
NULL);
lastError = GetLastError();
if (lastError == ERROR_INVALID_HANDLE)
{
CHECK_ERROR(self->state == State::Stopping, L"WinHttpQueryDataAvailable failed with ERROR_INVALID_HANDLE but client is not stopping.");
return;
}
CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryDataAvailable failed.");
});
}
break;
case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
{
self->QueueCallback([=, context = std::move(context)]()
{
if (self->state == State::Stopping) return;
self->CloseRequest(httpRequest, requestId);
self->RaiseErrorUnsafe(WString::Unmanaged(L"/Response canceled, another renderer may have connected to the core."));
});
}
break;
case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
self->OnRequestHandleClosing(httpRequest, requestId);
delete contextPtr;
break;
}
},
WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS,
NULL);
lastError = GetLastError();
CHECK_ERROR(previousCallback != WINHTTP_INVALID_STATUS_CALLBACK, L"WinHttpSetStatusCallback failed.");
httpResult = WinHttpAddRequestHeaders(
httpRequest,
L"Content-Type: application/json; charset=utf8",
-1,
WINHTTP_ADDREQ_FLAG_ADD);
lastError = GetLastError();
if (lastError == ERROR_INVALID_HANDLE)
{
CHECK_ERROR(state == State::Stopping, L"WinHttpAddRequestHeaders failed with ERROR_INVALID_HANDLE.");
WinHttpCloseHandle(httpRequest);
return;
}
CHECK_ERROR(httpResult == TRUE, L"WinHttpAddRequestHeaders failed.");
auto contextPtr = new Ptr<HttpRequestContext>(new HttpRequestContext);
auto context = *contextPtr;
context->client = this;
context->httpRequest = httpRequest;
context->requestId = ++createdRequestIds;
context->requestBody = wtou8(str);
context->responseReading = Ptr(new HttpResponseReading);
{
AttachRequest(httpRequest, context->requestId);
httpResult = WinHttpSendRequest(
httpRequest,
WINHTTP_NO_ADDITIONAL_HEADERS,
0,
(LPVOID)context->requestBody.Buffer(),
(DWORD)context->requestBody.Length(),
(DWORD)context->requestBody.Length(),
reinterpret_cast<DWORD_PTR>(contextPtr));
lastError = GetLastError();
if (lastError == ERROR_INVALID_HANDLE)
{
OnRequestHandleClosing(httpRequest, context->requestId);
delete contextPtr;
CHECK_ERROR(state == State::Stopping, L"WinHttpSendRequest failed with ERROR_INVALID_HANDLE.");
WinHttpCloseHandle(httpRequest);
return;
}
if (httpResult == FALSE)
{
OnRequestHandleClosing(httpRequest, context->requestId);
delete contextPtr;
WinHttpCloseHandle(httpRequest);
CHECK_FAIL(L"WinHttpSendRequest failed.");
}
}
}
/***********************************************************************
HttpClient
***********************************************************************/
void HttpClient::BeginPendingCallback()
{
if (pendingCallbacks++ == 0)
{
eventPendingCallbacks.Unsignal();
}
}
void HttpClient::EndPendingCallback()
{
if (--pendingCallbacks == 0)
{
eventPendingCallbacks.Signal();
}
}
void HttpClient::QueueCallback(const Func<void()>& proc)
{
BeginPendingCallback();
auto queued = ThreadPoolLite::Queue([=]()
{
try
{
proc();
}
catch (...)
{
EndPendingCallback();
throw;
}
EndPendingCallback();
});
if (!queued)
{
EndPendingCallback();
CHECK_FAIL(L"HttpClient failed to queue asynchronous callback.");
}
}
vint HttpClient::FindActiveRequestUnsafe(HINTERNET httpRequest, vint requestId)
{
for (vint index = 0; index < httpActiveRequests.Count(); index++)
{
auto&& activeRequest = httpActiveRequests[index];
if (activeRequest.requestId != -1 && activeRequest.httpRequest == httpRequest && activeRequest.requestId == requestId)
{
return index;
}
}
return -1;
}
void HttpClient::AttachRequest(HINTERNET httpRequest, vint requestId)
{
BeginPendingCallback();
SPIN_LOCK(httpActiveRequestsLock)
{
for (vint index = 0; index < httpActiveRequests.Count(); index++)
{
auto&& activeRequest = httpActiveRequests[index];
if (activeRequest.requestId == -1)
{
activeRequest.httpRequest = httpRequest;
activeRequest.requestId = requestId;
return;
}
}
HttpActiveRequest activeRequest;
activeRequest.httpRequest = httpRequest;
activeRequest.requestId = requestId;
httpActiveRequests.Add(activeRequest);
}
}
void HttpClient::CloseRequest(HINTERNET httpRequest, vint requestId)
{
bool closeRequest = false;
SPIN_LOCK(httpActiveRequestsLock)
{
vint index = FindActiveRequestUnsafe(httpRequest, requestId);
if (index != -1)
{
auto&& activeRequest = httpActiveRequests[index];
activeRequest.httpRequest = NULL;
activeRequest.requestId = -1;
closeRequest = true;
}
}
if (closeRequest)
{
WinHttpCloseHandle(httpRequest);
}
}
void HttpClient::OnRequestHandleClosing(HINTERNET httpRequest, vint requestId)
{
SPIN_LOCK(httpActiveRequestsLock)
{
vint index = FindActiveRequestUnsafe(httpRequest, requestId);
if (index != -1)
{
auto&& activeRequest = httpActiveRequests[index];
activeRequest.httpRequest = NULL;
activeRequest.requestId = -1;
}
}
EndPendingCallback();
}
HttpClient::HttpClient(const WString _baseUrl, vint port)
: baseUrl(_baseUrl)
{
DWORD lastError = 0;
hEventWaitForServer = CreateEvent(NULL, FALSE, TRUE, NULL);
CHECK_ERROR(hEventWaitForServer != NULL, L"HttpClient initialization failed on CreateEvent(hEventWaitForServer).");
CHECK_ERROR(eventPendingCallbacks.CreateManualUnsignal(true), L"HttpClient initialization failed on eventPendingCallbacks.CreateManualUnsignal.");
httpSession = WinHttpOpen(
L"vl::inter_process::HttpClient",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS,
WINHTTP_FLAG_ASYNC);
lastError = GetLastError();
CHECK_ERROR(httpSession != NULL, L"WinHttpOpen failed.");
httpConnection = WinHttpConnect(
httpSession,
L"localhost",
(INTERNET_PORT)port,
0);
lastError = GetLastError();
CHECK_ERROR(httpConnection != NULL, L"WinHttpConnect failed.");
urlConnect = baseUrl + HttpServerUrl_Connect;
}
HttpClient::~HttpClient()
{
Stop();
CloseHandle(hEventWaitForServer);
}
void HttpClient::InstallCallback(INetworkProtocolCallback* _callback)
{
callback = _callback;
CHECK_ERROR(callback, L"HttpClient::InstallCallback needs a valid INetworkProtocolCallback.");
callback->OnInstalled(this);
}
void HttpClient::Stop()
{
if (httpSession != NULL)
{
state = State::Stopping;
List<HINTERNET> stoppingRequests;
SPIN_LOCK(httpActiveRequestsLock)
{
for (auto activeRequest : httpActiveRequests)
{
if (activeRequest.requestId != -1)
{
stoppingRequests.Add(activeRequest.httpRequest);
}
}
httpActiveRequests.Clear();
}
for (auto httpRequest : stoppingRequests)
{
WinHttpCloseHandle(httpRequest);
}
WinHttpCloseHandle(httpConnection);
WinHttpSetStatusCallback(
httpSession,
NULL,
WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS,
NULL);
WinHttpCloseHandle(httpSession);
eventPendingCallbacks.Wait();
httpConnection = NULL;
httpSession = NULL;
if (callback)
{
callback->OnDisconnected();
}
}
}
}
/***********************************************************************
.\INTERPROCESS\WINDOWS\HTTPSERVER.WINDOWS.CPP
***********************************************************************/
namespace vl::inter_process
{
using namespace vl::collections;
/***********************************************************************
HttpServerConnection
***********************************************************************/
void HttpServerConnection::OnCancelCurrentHttpRequestForPendingRequest()
{
if (httpPendingRequestId != HTTP_NULL_ID)
{
if (!server)
{
httpPendingRequestId = HTTP_NULL_ID;
return;
}
ULONG result = HttpCancelHttpRequest(
server->httpRequestQueue,
httpPendingRequestId,
NULL);
CHECK_ERROR(
result == NO_ERROR || result == ERROR_CONNECTION_INVALID || result == ERROR_OPERATION_ABORTED,
L"HttpCancelHttpRequest failed for canceling outdated /Request.");
httpPendingRequestId = HTTP_NULL_ID;
}
}
void HttpServerConnection::OnNewHttpRequestForPendingRequest(HTTP_REQUEST_ID httpRequestId)
{
OnCancelCurrentHttpRequestForPendingRequest();
httpPendingRequestId = httpRequestId;
if (pendingRequestsToSend.Count() > 0)
{
auto pendingRequest = pendingRequestsToSend[0];
pendingRequestsToSend.RemoveAt(0);
ULONG result = HttpServer::SendResponse(server->httpRequestQueue, httpPendingRequestId, pendingRequest);
CHECK_ERROR(result == NO_ERROR, L"HttpSendHttpResponse failed for responding /Request.");
httpPendingRequestId = HTTP_NULL_ID;
}
}
WString HttpServerConnection::SubmitResponse(PHTTP_REQUEST pRequest)
{
ULONG bodyLength = 0;
ULONG bodyReceived = 0;
{
auto& headerContentType = pRequest->Headers.KnownHeaders[HttpHeaderContentType];
CHECK_ERROR(headerContentType.pRawValue != NULL, L"/Response missing Content-Type header.");
CHECK_ERROR(
strncmp((const char*)headerContentType.pRawValue, "application/json; charset=utf8", headerContentType.RawValueLength) == 0,
L"/Response Content-Type header must be \"application/json; charset=utf8\".");
}
{
auto& headerContentLength = pRequest->Headers.KnownHeaders[HttpHeaderContentLength];
CHECK_ERROR(headerContentLength.pRawValue != NULL, L"/Response missing Content-Type header.");
bodyLength = (ULONG)atoi(headerContentLength.pRawValue);
}
CHECK_ERROR(pRequest->Flags & HTTP_REQUEST_FLAG_MORE_ENTITY_BODY_EXISTS, L"/Response must contain body data.");
Array<char8_t> bodyBuffer(bodyLength + 1);
ZeroMemory(&bodyBuffer[0], bodyBuffer.Count() * sizeof(char8_t));
{
ULONG result = NO_ERROR;
result = HttpReceiveRequestEntityBody(
server->httpRequestQueue,
pRequest->RequestId,
HTTP_RECEIVE_REQUEST_ENTITY_BODY_FLAG_FILL_BUFFER,
&bodyBuffer[0],
bodyLength,
&bodyReceived,
NULL);
CHECK_ERROR(result == NO_ERROR, L"HttpReceiveRequestEntityBody.");
}
SPIN_LOCK(pendingRequestLock)
{
submittingResponse = true;
}
try
{
SPIN_LOCK(lockQueuedStrings)
{
U8String bodyUtf8 = U8String::Unmanaged(&bodyBuffer[0]);
if (callback)
{
callback->OnReadString(u8tow(bodyUtf8));
}
else
{
queuedStrings.Add(u8tow(bodyUtf8));
}
}
}
catch (...)
{
SPIN_LOCK(pendingRequestLock)
{
submittingResponse = false;
responsesToSubmit.Clear();
}
throw;
}
WString responseToClient;
SPIN_LOCK(pendingRequestLock)
{
submittingResponse = false;
if (responsesToSubmit.Count() > 0)
{
responseToClient = responsesToSubmit[0];
responsesToSubmit.RemoveAt(0);
while (responsesToSubmit.Count() > 0)
{
pendingRequestsToSend.Add(responsesToSubmit[0]);
responsesToSubmit.RemoveAt(0);
}
}
else if (pendingRequestsToSend.Count() > 0)
{
responseToClient = pendingRequestsToSend[0];
pendingRequestsToSend.RemoveAt(0);
}
}
return responseToClient;
}
void HttpServerConnection::InstallCallback(INetworkProtocolCallback* _callback)
{
SPIN_LOCK(lockQueuedStrings)
{
callback = _callback;
callback->OnInstalled(this);
for (const auto& str : queuedStrings)
{
callback->OnReadString(str);
}
queuedStrings.Clear();
}
}
void HttpServerConnection::BeginReadingLoopUnsafe()
{
// Do nothing, HttpServer automatically handles this.
}
void HttpServerConnection::SendString(const WString& str)
{
SPIN_LOCK(pendingRequestLock)
{
if (submittingResponse)
{
responsesToSubmit.Add(str);
}
else if (httpPendingRequestId != HTTP_NULL_ID)
{
ULONG result = HttpServer::SendResponse(server->httpRequestQueue, httpPendingRequestId, str);
if (result == NO_ERROR)
{
httpPendingRequestId = HTTP_NULL_ID;
}
else if (result == ERROR_CONNECTION_INVALID || result == ERROR_OPERATION_ABORTED)
{
httpPendingRequestId = HTTP_NULL_ID;
pendingRequestsToSend.Add(str);
}
else
{
CHECK_FAIL(L"HttpSendHttpResponse failed for responding /Request.");
}
}
else
{
pendingRequestsToSend.Add(str);
}
}
}
void HttpServerConnection::Stop()
{
auto holding = Ptr(this);
if (server)
{
SPIN_LOCK(server->lockConnections)
{
server->connections.Remove(guid);
}
SPIN_LOCK(pendingRequestLock)
{
OnCancelCurrentHttpRequestForPendingRequest();
}
if (callback)
{
callback->OnDisconnected();
}
}
}
WString HttpServerConnection::GenerateNewGuid()
{
RPC_STATUS status = -1;
UUID guid;
status = UuidCreate(&guid);
CHECK_ERROR(status == RPC_S_OK, L"UuidCreate failed.");
RPC_WSTR guidString = nullptr;
status = UuidToString(&guid, &guidString);
CHECK_ERROR(status == RPC_S_OK, L"UuidToString failed.");
WString result = guidString;
status = RpcStringFree(&guidString);
CHECK_ERROR(status == RPC_S_OK, L"RpcStringFree failed.");
return result;
}
/***********************************************************************
HttpServer (ListenToHttpRequest)
***********************************************************************/
void HttpServer::OnHttpConnectionBrokenUnsafe()
{
if (state == State::Running)
{
SPIN_LOCK(lockConnections)
{
state = State::Stopping;
for (auto connection : connections.Values())
{
connection->server = nullptr;
}
for (auto connection : connections.Values())
{
connection->Stop();
}
connections.Clear();
}
}
}
void HttpServer::OnHttpRequestReceivedUnsafe(PHTTP_REQUEST pRequest)
{
if (state == State::Stopping)
{
Send404Response(httpRequestQueue, pRequest->RequestId, "Server is stopping");
return;
}
bool isValidRequest = wcsncmp(pRequest->CookedUrl.pAbsPath, urlRequestPrefix.Buffer(), urlRequestPrefix.Length()) == 0;
bool isValidResponse = wcsncmp(pRequest->CookedUrl.pAbsPath, urlResponsePrefix.Buffer(), urlResponsePrefix.Length()) == 0;
auto FindExistingConnection = [=, this](const WString& guid)->Ptr<HttpServerConnection>
{
SPIN_LOCK(lockConnections)
{
vint index = connections.Keys().IndexOf(guid);
if (index == -1)
{
Send404Response(httpRequestQueue, pRequest->RequestId, "Unknown connection guid");
}
else
{
return connections.Values()[index];
}
}
return {};
};
if (pRequest->Verb == HttpVerbGET && pRequest->CookedUrl.pAbsPath == urlConnect)
{
auto newGuid = HttpServerConnection::GenerateNewGuid();
auto connection = Ptr(new HttpServerConnection);
connection->server = this;
connection->guid = newGuid;
SPIN_LOCK(lockConnections)
{
connections.Add(newGuid, connection);
}
auto result = OnClientConnected(connection.Obj());
if (result == WaitForClientResult::Reject)
{
SPIN_LOCK(lockConnections)
{
connections.Remove(newGuid);
}
connection->server = nullptr;
Send404Response(httpRequestQueue, pRequest->RequestId, "Connection rejected");
}
else
{
auto completeUrlRequest = WString::Unmanaged(HttpServerUrl_Request) + L"/" + newGuid;
auto completeUrlResponse = WString::Unmanaged(HttpServerUrl_Response) + L"/" + newGuid;
SendResponse(httpRequestQueue, pRequest->RequestId, completeUrlRequest + L";" + completeUrlResponse);
}
}
else if (pRequest->Verb == HttpVerbPOST && isValidRequest)
{
auto guid = WString::Unmanaged(pRequest->CookedUrl.pAbsPath + urlRequestPrefix.Length());
if (auto connection = FindExistingConnection(guid))
{
SPIN_LOCK(connection->pendingRequestLock)
{
connection->OnNewHttpRequestForPendingRequest(pRequest->RequestId);
}
}
}
else if (pRequest->Verb == HttpVerbPOST && isValidResponse)
{
auto guid = WString::Unmanaged(pRequest->CookedUrl.pAbsPath + urlResponsePrefix.Length());
if (auto connection = FindExistingConnection(guid))
{
auto responseToClient = connection->SubmitResponse(pRequest);
auto result = SendResponse(httpRequestQueue, pRequest->RequestId, responseToClient);
CHECK_ERROR(result == NO_ERROR, L"HttpSendHttpResponse failed for responding /Response.");
}
}
else if (pRequest->Verb == HttpVerbOPTIONS && (isValidRequest || isValidResponse))
{
SendOptionsResponse(httpRequestQueue, pRequest->RequestId);
}
else
{
Send404Response(httpRequestQueue, pRequest->RequestId, "Unknown URL");
}
ListenToHttpRequest();
}
ULONG HttpServer::ListenToHttpRequest_Init(OVERLAPPED* overlapped)
{
ZeroMemory(&bufferRequest[0], bufferRequest.Count());
ULONG result = HttpReceiveHttpRequest(
httpRequestQueue,
HTTP_NULL_ID,
0,
(PHTTP_REQUEST)&bufferRequest[0],
(ULONG)bufferRequest.Count(),
NULL,
overlapped);
return result;
}
ULONG HttpServer::ListenToHttpRequest_InitMoreData(ULONG* bytesReturned)
{
HTTP_REQUEST_ID httpRequestIdReading = ((PHTTP_REQUEST)&bufferRequest[0])->RequestId;
ZeroMemory(&bufferRequest[0], bufferRequest.Count());
ULONG result = HttpReceiveHttpRequest(
httpRequestQueue,
httpRequestIdReading,
0,
(PHTTP_REQUEST)&bufferRequest[0],
(ULONG)bufferRequest.Count(),
bytesReturned,
NULL);
return result;
}
ULONG HttpServer::ListenToHttpRequest_OverlappedMoreData(vint expectedBufferSize)
{
HTTP_REQUEST_ID httpRequestIdReading = ((PHTTP_REQUEST)&bufferRequest[0])->RequestId;
bufferRequest.Resize(expectedBufferSize);
ZeroMemory(&bufferRequest[0], bufferRequest.Count());
ULONG bytesReturned = 0;
ULONG result = HttpReceiveHttpRequest(
httpRequestQueue,
httpRequestIdReading,
0,
(PHTTP_REQUEST)&bufferRequest[0],
(ULONG)bufferRequest.Count(),
&bytesReturned,
NULL);
return result;
}
void HttpServer::ListenToHttpRequest()
{
if (state == State::Stopping) return;
ResetEvent(hEventRequest);
ZeroMemory(&overlappedRequest, sizeof(overlappedRequest));
overlappedRequest.hEvent = hEventRequest;
ZeroMemory(&bufferRequest[0], sizeof(HTTP_REQUEST));
PHTTP_REQUEST pRequest = (PHTTP_REQUEST)&bufferRequest[0];
ULONG result = ListenToHttpRequest_Init(&overlappedRequest);
if (result == ERROR_CONNECTION_INVALID || result == ERROR_OPERATION_ABORTED)
{
OnHttpConnectionBrokenUnsafe();
return;
}
if (result == NO_ERROR)
{
OnHttpRequestReceivedUnsafe(pRequest);
return;
}
if (result == ERROR_MORE_DATA)
{
ULONG bytesReturned = 0;
result = ListenToHttpRequest_InitMoreData(&bytesReturned);
if (result == ERROR_CONNECTION_INVALID || result == ERROR_OPERATION_ABORTED)
{
OnHttpConnectionBrokenUnsafe();
return;
}
CHECK_ERROR(result == ERROR_MORE_DATA, L"HttpReceiveHttpRequest(#1) failed on unexpected result.");
result = ListenToHttpRequest_OverlappedMoreData((vint)bytesReturned);
if (result == ERROR_CONNECTION_INVALID || result == ERROR_OPERATION_ABORTED)
{
OnHttpConnectionBrokenUnsafe();
return;
}
CHECK_ERROR(result == NO_ERROR, L"HttpReceiveHttpRequest(#2) failed on unexpected result.");
PHTTP_REQUEST pRequest = (PHTTP_REQUEST)&bufferRequest[0];
OnHttpRequestReceivedUnsafe(pRequest);
return;
}
CHECK_ERROR(result == ERROR_IO_PENDING, L"HttpReceiveHttpRequest(#3) failed on unexpected result.");
RegisterWaitForSingleObject(
&hWaitHandleRequest,
hEventRequest,
[](PVOID lpParameter, BOOLEAN TimerOrWaitFired)
{
auto self = (HttpServer*)lpParameter;
auto waitHandle = (HANDLE)InterlockedExchangePointer((PVOID volatile*)&self->hWaitHandleRequest, INVALID_HANDLE_VALUE);
if (waitHandle != INVALID_HANDLE_VALUE)
{
UnregisterWait(waitHandle);
}
DWORD read = 0;
BOOL result = GetOverlappedResult(self->httpRequestQueue, &self->overlappedRequest, &read, FALSE);
if (result == TRUE)
{
PHTTP_REQUEST pRequest = (PHTTP_REQUEST)&self->bufferRequest[0];
self->OnHttpRequestReceivedUnsafe(pRequest);
}
else
{
DWORD error = GetLastError();
if (error == ERROR_CONNECTION_INVALID || error == ERROR_OPERATION_ABORTED)
{
self->OnHttpConnectionBrokenUnsafe();
return;
}
CHECK_ERROR(error == ERROR_MORE_DATA, L"GetOverlappedResult(#4) failed on unexpected GetLastError.");
CHECK_ERROR(self->bufferRequest.Count() < (vint)read, L"GetOverlappedResult(#5) failed on unexpected read size.");
ULONG result = self->ListenToHttpRequest_OverlappedMoreData((vint)read);
if (result == ERROR_CONNECTION_INVALID || result == ERROR_OPERATION_ABORTED)
{
self->OnHttpConnectionBrokenUnsafe();
return;
}
CHECK_ERROR(result == NO_ERROR, L"HttpReceiveHttpRequest(#6) failed on unexpected result.");
PHTTP_REQUEST pRequest = (PHTTP_REQUEST)&self->bufferRequest[0];
self->OnHttpRequestReceivedUnsafe(pRequest);
}
},
this,
INFINITE,
WT_EXECUTEONLYONCE);
}
/***********************************************************************
HttpServer (Writing)
***********************************************************************/
void HttpServer::Send404Response(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId, PCSTR reason)
{
ULONG bytesSent = 0;
HTTP_RESPONSE httpResponse;
ZeroMemory(&httpResponse, sizeof(httpResponse));
httpResponse.StatusCode = 404;
httpResponse.pReason = reason;
static const char headerACAOName[] = "Access-Control-Allow-Origin";
static HTTP_UNKNOWN_HEADER unknownHeaders[] = { {
sizeof(headerACAOName) - 1,
1,
headerACAOName,
"*"
} };
httpResponse.Headers.UnknownHeaderCount = sizeof(unknownHeaders) / sizeof(HTTP_UNKNOWN_HEADER);
httpResponse.Headers.pUnknownHeaders = unknownHeaders;
ULONG result = NO_ERROR;
result = HttpSendHttpResponse(
httpRequestQueue,
requestId,
0,
&httpResponse,
NULL,
&bytesSent,
NULL,
0,
NULL,
NULL);
CHECK_ERROR(result == NO_ERROR, L"HttpSendHttpResponse failed (404).");
}
void HttpServer::SendOptionsResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId)
{
ULONG bytesSent = 0;
HTTP_RESPONSE httpResponse;
ZeroMemory(&httpResponse, sizeof(httpResponse));
httpResponse.StatusCode = 200;
static const char headerACAOName[] = "Access-Control-Allow-Origin";
static const char headerACAMName[] = "Access-Control-Allow-Methods";
static const char headerACAMValue[] = "POST, OPTIONS";
static const char headerACAHName[] = "Access-Control-Allow-Headers";
static const char headerACAHValue[] = "Content-Type";
static HTTP_UNKNOWN_HEADER unknownHeaders[] = { {
sizeof(headerACAOName) - 1,
1,
headerACAOName,
"*"
},{
sizeof(headerACAMName) - 1,
sizeof(headerACAMValue) - 1,
headerACAMName,
headerACAMValue
},{
sizeof(headerACAHName) - 1,
sizeof(headerACAHValue) - 1,
headerACAHName,
headerACAHValue
} };
httpResponse.Headers.UnknownHeaderCount = sizeof(unknownHeaders) / sizeof(HTTP_UNKNOWN_HEADER);
httpResponse.Headers.pUnknownHeaders = unknownHeaders;
ULONG result = NO_ERROR;
result = HttpSendHttpResponse(
httpRequestQueue,
requestId,
0,
&httpResponse,
NULL,
&bytesSent,
NULL,
0,
NULL,
NULL);
CHECK_ERROR(result == NO_ERROR, L"HttpSendHttpResponse failed (OPTIONS).");
}
ULONG HttpServer::SendResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId, const WString& str)
{
ULONG bytesSent = 0;
HTTP_RESPONSE httpResponse;
HTTP_DATA_CHUNK httpResponseBody;
ZeroMemory(&httpResponse, sizeof(httpResponse));
ZeroMemory(&httpResponseBody, sizeof(httpResponseBody));
httpResponse.StatusCode = 200;
httpResponse.pReason = "OK";
U8String body = wtou8(str);
if (body.Length() > 0)
{
httpResponse.EntityChunkCount = 1;
httpResponse.pEntityChunks = &httpResponseBody;
httpResponseBody.DataChunkType = HttpDataChunkFromMemory;
httpResponseBody.FromMemory.pBuffer = (PVOID)body.Buffer();
httpResponseBody.FromMemory.BufferLength = (ULONG)body.Length();
}
static const char headerContentType[] = "application/json; charset=utf8";
httpResponse.Headers.KnownHeaders[HttpHeaderContentType].pRawValue = headerContentType;
httpResponse.Headers.KnownHeaders[HttpHeaderContentType].RawValueLength = sizeof(headerContentType) - 1;
static const char headerACAOName[] = "Access-Control-Allow-Origin";
static HTTP_UNKNOWN_HEADER unknownHeaders[] = { {
sizeof(headerACAOName) - 1,
1,
headerACAOName,
"*"
}};
httpResponse.Headers.UnknownHeaderCount = sizeof(unknownHeaders) / sizeof(HTTP_UNKNOWN_HEADER);
httpResponse.Headers.pUnknownHeaders = unknownHeaders;
ULONG result = NO_ERROR;
result = HttpSendHttpResponse(
httpRequestQueue,
requestId,
0,
&httpResponse,
NULL,
&bytesSent,
NULL,
0,
NULL,
NULL);
return result;
}
/***********************************************************************
HttpServer
***********************************************************************/
HttpServer::HttpServer(const WString _baseUrl, vint port)
: bufferRequest(HttpBodyInitSize)
, baseUrl(_baseUrl)
{
urlConnect = baseUrl + HttpServerUrl_Connect;
urlRequestPrefix = baseUrl + HttpServerUrl_Request + L"/";
urlResponsePrefix = baseUrl + HttpServerUrl_Response + L"/";
hEventRequest = CreateEvent(NULL, TRUE, TRUE, NULL);
CHECK_ERROR(hEventRequest != NULL, L"HttpServer initialization failed on CreateEvent(hEventRequest).");
{
ULONG result = NO_ERROR;
result = HttpInitialize(
HTTPAPI_VERSION_2,
HTTP_INITIALIZE_SERVER,
NULL);
CHECK_ERROR(result == NO_ERROR, L"HttpInitialize failed.");
result = HttpCreateRequestQueue(
HTTPAPI_VERSION_2,
NULL,
NULL,
0,
&httpRequestQueue);
CHECK_ERROR(result == NO_ERROR, L"HttpCreateRequestQueue failed.");
result = HttpCreateServerSession(
HTTPAPI_VERSION_2,
&httpSessionId,
0);
CHECK_ERROR(result == NO_ERROR, L"HttpCreateServerSession failed.");
result = HttpCreateUrlGroup(
httpSessionId,
&httpUrlGroupId,
0);
CHECK_ERROR(result == NO_ERROR, L"HttpCreateUrlGroup failed.");
}
{
ULONG result = NO_ERROR;
result = HttpAddUrlToUrlGroup(
httpUrlGroupId,
(WString::Unmanaged(L"http://localhost:") + itow(port) + baseUrl + WString::Unmanaged(HttpServerUrl_Connect)).Buffer(),
0,
0);
CHECK_ERROR(result == NO_ERROR, L"HttpAddUrlToUrlGroup failed (urlConnect).");
result = HttpAddUrlToUrlGroup(
httpUrlGroupId,
(WString::Unmanaged(L"http://localhost:") + itow(port) + baseUrl + WString::Unmanaged(HttpServerUrl_Request)).Buffer(),
0,
0);
CHECK_ERROR(result == NO_ERROR, L"HttpAddUrlToUrlGroup failed (urlRequest).");
result = HttpAddUrlToUrlGroup(
httpUrlGroupId,
(WString::Unmanaged(L"http://localhost:") + itow(port) + baseUrl + WString::Unmanaged(HttpServerUrl_Response)).Buffer(),
0,
0);
CHECK_ERROR(result == NO_ERROR, L"HttpAddUrlToUrlGroup failed (urlResponse).");
}
{
ULONG result = NO_ERROR;
HTTP_BINDING_INFO bindingInfo;
ZeroMemory(&bindingInfo, sizeof(bindingInfo));
bindingInfo.Flags.Present = 1;
bindingInfo.RequestQueueHandle = httpRequestQueue;
result = HttpSetUrlGroupProperty(
httpUrlGroupId,
HttpServerBindingProperty,
&bindingInfo,
sizeof(bindingInfo));
CHECK_ERROR(result == NO_ERROR, L"HttpSetUrlGroupProperty failed (HttpServerBindingProperty).");
}
}
HttpServer::~HttpServer()
{
Stop();
CloseHandle(hEventRequest);
}
WaitForClientResult HttpServer::OnClientConnected(INetworkProtocolConnection* connection)
{
return WaitForClientResult::Accept;
}
void HttpServer::Start()
{
CHECK_ERROR(state == State::Ready, L"HttpServer can only be started once.");
state = State::Running;
ListenToHttpRequest();
}
void HttpServer::Stop()
{
state = State::Stopping;
auto waitHandle = (HANDLE)InterlockedExchangePointer((PVOID volatile*)&hWaitHandleRequest, INVALID_HANDLE_VALUE);
if (waitHandle != INVALID_HANDLE_VALUE)
{
UnregisterWaitEx(waitHandle, INVALID_HANDLE_VALUE);
}
List<Ptr<HttpServerConnection>> stoppingConnections;
SPIN_LOCK(lockConnections)
{
for (auto connection : connections.Values())
{
stoppingConnections.Add(connection);
}
connections.Clear();
}
for (auto connection : stoppingConnections)
{
SPIN_LOCK(connection->pendingRequestLock)
{
connection->OnCancelCurrentHttpRequestForPendingRequest();
}
connection->server = nullptr;
}
for (auto connection : stoppingConnections)
{
if (connection->callback)
{
connection->callback->OnDisconnected();
}
}
if (httpRequestQueue != INVALID_HANDLE_VALUE)
{
HttpCloseUrlGroup(httpUrlGroupId);
HttpCloseServerSession(httpSessionId);
HttpCloseRequestQueue(httpRequestQueue);
httpRequestQueue = INVALID_HANDLE_VALUE;
HttpTerminate(
HTTP_INITIALIZE_SERVER,
NULL);
}
}
bool HttpServer::IsStopped()
{
return state == State::Stopping;
}
}
/***********************************************************************
.\INTERPROCESS\WINDOWS\NAMEDPIPE.WINDOWS.CPP
***********************************************************************/
namespace vl::inter_process
{
using namespace vl::console;
using namespace vl::collections;
/***********************************************************************
NamedPipeConnection (Reading)
***********************************************************************/
void NamedPipeConnection::BeginReadingUnsafe()
{
if (firstRead)
{
streamReadFile.SeekFromBegin(0);
}
}
void NamedPipeConnection::SubmitReadBufferUnsafe(vint bytes)
{
streamReadFile.Write(&bufferReadFile[0], bytes);
}
void NamedPipeConnection::EndReadingUnsafe()
{
vint32_t position = (vint32_t)streamReadFile.Position();
streamReadFile.SeekFromBegin(0);
CHECK_ERROR(position >= 2 * sizeof(vint32_t), L"ReadFile failed on incomplete message.");
vint32_t bytes = 0;
vint consumed = 0;
consumed = streamReadFile.Read(&bytes, sizeof(bytes));
CHECK_ERROR(consumed == sizeof(bytes), L"ReadFile failed on incomplete message.");
if (bytes > position - (vint32_t)sizeof(bytes))
{
firstRead = false;
streamReadFile.SeekFromBegin(position);
return;
}
CHECK_ERROR(bytes == position - sizeof(bytes), L"ReadFile failed on corrupted message.");
firstRead = true;
Array<wchar_t> strBuffer;
auto ReadSingleString = [&]()
{
vint32_t length = 0;
consumed = streamReadFile.Read(&length, sizeof(length));
CHECK_ERROR(consumed == sizeof(length) && streamReadFile.Position() <= position, L"ReadFile failed on incomplete message.");
if (length == 0)
{
return WString::Empty;
}
else
{
strBuffer.Resize(length);
consumed = streamReadFile.Read(&strBuffer[0], length * sizeof(wchar_t));
CHECK_ERROR(consumed == length * sizeof(wchar_t) && streamReadFile.Position() <= position, L"ReadFile failed on incomplete message.");
return WString::CopyFrom(&strBuffer[0], length);
}
};
callback->OnReadString(ReadSingleString());
CHECK_ERROR(streamReadFile.Position() == position, L"ReadFile failed on incomplete message.");
}
void NamedPipeConnection::BeginReadingLoopUnsafe()
{
if (stopped) return;
RESTART_LOOP:
{
BeginReadingUnsafe();
ResetEvent(hEventReadFile);
ZeroMemory(&overlappedReadFile, sizeof(overlappedReadFile));
overlappedReadFile.hEvent = hEventReadFile;
DWORD read = 0;
BOOL result = ReadFile(hPipe, &bufferReadFile[0], sizeof(BYTE) * MaxMessageSize, &read, &overlappedReadFile);
if (result == TRUE)
{
SubmitReadBufferUnsafe((vint)read);
EndReadingUnsafe();
goto RESTART_LOOP;
}
DWORD error = GetLastError();
if (error == ERROR_BROKEN_PIPE || error == ERROR_INVALID_HANDLE)
{
OnDisconnected();
return;
}
CHECK_ERROR(error == ERROR_MORE_DATA || error == ERROR_IO_PENDING, L"ReadFile failed on unexpected GetLastError.");
RegisterWaitForSingleObject(
&hWaitHandleReadFile,
hEventReadFile,
[](PVOID lpParameter, BOOLEAN TimerOrWaitFired)
{
auto self = (NamedPipeConnection*)lpParameter;
auto waitHandle = (HANDLE)InterlockedExchangePointer((PVOID volatile*)&self->hWaitHandleReadFile, INVALID_HANDLE_VALUE);
if (waitHandle != INVALID_HANDLE_VALUE)
{
UnregisterWait(waitHandle);
}
DWORD read = 0;
BOOL result = GetOverlappedResult(self->hPipe, &self->overlappedReadFile, &read, FALSE);
if (result == TRUE)
{
self->SubmitReadBufferUnsafe((vint)read);
self->EndReadingUnsafe();
}
else
{
DWORD error = GetLastError();
if (error == ERROR_BROKEN_PIPE || error == ERROR_INVALID_HANDLE)
{
self->OnDisconnected();
return;
}
CHECK_ERROR(error == ERROR_MORE_DATA, L"GetOverlappedResult(ReadFile) failed on unexpected GetLastError.");
self->SubmitReadBufferUnsafe((vint)read);
}
if (!self->stopped)
{
self->BeginReadingLoopUnsafe();
}
},
this,
INFINITE,
WT_EXECUTEONLYONCE);
}
}
/***********************************************************************
NamedPipeConnection (Writing)
***********************************************************************/
vint32_t NamedPipeConnection::WriteInt32ToStream(vint32_t number)
{
return (vint32_t)streamWriteFile.Write(&number, sizeof(number));
}
vint32_t NamedPipeConnection::WriteStringToStream(const WString& str)
{
vint32_t bytes = 0;
vint32_t count = (vint32_t)str.Length();
bytes += (vint32_t)streamWriteFile.Write(&count, sizeof(count));
if (count > 0)
{
bytes += (vint32_t)streamWriteFile.Write((void*)str.Buffer(), sizeof(wchar_t) * str.Length());
}
return bytes;
}
void NamedPipeConnection::BeginSendStream()
{
vint32_t bytes = 0;
streamWriteFile.SeekFromBegin(0);
streamWriteFile.Write(&bytes, sizeof(bytes));
}
void NamedPipeConnection::EndSendStream(vint32_t bytes)
{
streamWriteFile.SeekFromBegin(0);
WriteInt32ToStream(bytes);
vint32_t length = bytes + sizeof(bytes);
for (vint i = 0; i < (length + MaxMessageSize - 1) / MaxMessageSize; i++)
{
vint offset = i * MaxMessageSize;
vint bytesToSend = length >= (i + 1) * MaxMessageSize ? MaxMessageSize : length % MaxMessageSize;
auto buffer = (LPCVOID)((char*)streamWriteFile.GetInternalBuffer() + offset);
ResetEvent(hEventWriteFile);
ZeroMemory(&overlappedWriteFile, sizeof(overlappedWriteFile));
overlappedWriteFile.hEvent = hEventWriteFile;
DWORD written = 0;
BOOL result = WriteFile(hPipe, buffer, (DWORD)bytesToSend, NULL, &overlappedWriteFile);
if (result == FALSE)
{
auto error = GetLastError();
if (error == ERROR_BROKEN_PIPE || error == ERROR_INVALID_HANDLE)
{
OnDisconnected();
return;
}
CHECK_ERROR(error == ERROR_IO_PENDING, L"WriteFile failed on unexpected GetLastError.");
WaitForSingleObject(hEventWriteFile, INFINITE);
result = GetOverlappedResult(hPipe, &overlappedWriteFile, &written, FALSE);
if (result == FALSE)
{
error = GetLastError();
if (error == ERROR_BROKEN_PIPE || error == ERROR_INVALID_HANDLE || error == ERROR_OPERATION_ABORTED)
{
OnDisconnected();
return;
}
CHECK_FAIL(L"GetOverlappedResult(WriteFile) failed on unexpected GetLastError.");
}
}
else
{
written = (DWORD)bytesToSend;
}
CHECK_ERROR(written == (DWORD)bytesToSend, L"WriteFile failed to write all data.");
}
}
void NamedPipeConnection::SendString(const WString& str)
{
if (stopped) return;
SPIN_LOCK(lockWrite)
{
vint32_t bytes = 0;
BeginSendStream();
bytes += WriteStringToStream(str);
EndSendStream(bytes);
}
}
/***********************************************************************
NamedPipeConnection
***********************************************************************/
void NamedPipeConnection::OnDisconnected()
{
if (callback)
{
callback->OnDisconnected();
}
if (server)
{
SPIN_LOCK(server->lockConnections)
{
server->connections.Remove(this);
}
}
}
NamedPipeConnection::NamedPipeConnection(HANDLE _hPipe)
: bufferReadFile(MaxMessageSize)
{
hPipe = _hPipe;
hEventReadFile = CreateEvent(NULL, TRUE, TRUE, NULL);
CHECK_ERROR(hEventReadFile != NULL, L"NamedPipeConnection initialization failed on CreateEvent(hEventReadFile).");
hEventWriteFile = CreateEvent(NULL, TRUE, TRUE, NULL);
CHECK_ERROR(hEventWriteFile != NULL, L"NamedPipeConnection initialization failed on CreateEvent(hEventWriteFile).");
}
NamedPipeConnection::~NamedPipeConnection()
{
Stop();
CloseHandle(hEventReadFile);
CloseHandle(hEventWriteFile);
}
void NamedPipeConnection::InstallCallback(INetworkProtocolCallback* _callback)
{
callback = _callback;
CHECK_ERROR(callback, L"NamedPipeConnection::InstallCallback needs a valid INetworkProtocolCallback.");
callback->OnInstalled(this);
}
void NamedPipeConnection::Stop()
{
stopped = 1;
auto waitHandle = (HANDLE)InterlockedExchangePointer((PVOID volatile*)&hWaitHandleReadFile, INVALID_HANDLE_VALUE);
if (waitHandle != INVALID_HANDLE_VALUE)
{
UnregisterWaitEx(waitHandle, INVALID_HANDLE_VALUE);
}
SPIN_LOCK(lockWrite)
{
if (hPipe != INVALID_HANDLE_VALUE)
{
CancelIoEx(hPipe, NULL);
CloseHandle(hPipe);
hPipe = INVALID_HANDLE_VALUE;
}
}
}
/***********************************************************************
NamedPipeServer
***********************************************************************/
NamedPipeServer::PendingConnection::PendingConnection(NamedPipeServer* _server, Ptr<NamedPipeConnection> _connection)
: server(_server)
, connection(_connection)
{
ZeroMemory(&overlappedConnect, sizeof(overlappedConnect));
hEventConnect = CreateEvent(NULL, TRUE, FALSE, NULL);
CHECK_ERROR(hEventConnect != NULL, L"ConnectNamedPipe failed on CreateEvent.");
overlappedConnect.hEvent = hEventConnect;
}
NamedPipeServer::PendingConnection::~PendingConnection()
{
Stop();
CloseHandle(hEventConnect);
}
void NamedPipeServer::PendingConnection::Stop()
{
server = nullptr;
auto waitHandle = (HANDLE)InterlockedExchangePointer((PVOID volatile*)&hWaitHandleConnect, INVALID_HANDLE_VALUE);
if (waitHandle != INVALID_HANDLE_VALUE)
{
UnregisterWaitEx(waitHandle, INVALID_HANDLE_VALUE);
}
if (connection)
{
connection->Stop();
connection = nullptr;
}
}
HANDLE NamedPipeServer::ServerCreatePipe(const WString& pipeName)
{
HANDLE hPipe = CreateNamedPipe(
(L"\\\\.\\pipe\\" + pipeName).Buffer(),
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_REJECT_REMOTE_CLIENTS,
PIPE_UNLIMITED_INSTANCES,
65536,
65536,
6000,
NULL);
CHECK_ERROR(hPipe != INVALID_HANDLE_VALUE, L"CreateNamedPipe failed.");
return hPipe;
}
NamedPipeServer::NamedPipeServer(const WString& _pipeName)
: pipeName(_pipeName)
{
}
NamedPipeServer::~NamedPipeServer()
{
Stop();
}
void NamedPipeServer::BeginListening()
{
Ptr<PendingConnection> pendingConnection;
SPIN_LOCK(lockConnections)
{
if (!started || stopped)
{
return;
}
}
auto connection = Ptr(new NamedPipeConnection(ServerCreatePipe(pipeName)));
pendingConnection = Ptr(new PendingConnection(this, connection));
BOOL result = ConnectNamedPipe(connection->hPipe, &pendingConnection->overlappedConnect);
DWORD error = result ? ERROR_SUCCESS : GetLastError();
switch (error)
{
case ERROR_SUCCESS:
case ERROR_PIPE_CONNECTED:
CompletePendingConnection(pendingConnection, true);
break;
case ERROR_IO_PENDING:
SPIN_LOCK(lockConnections)
{
if (stopped)
{
pendingConnection->Stop();
return;
}
pendingConnections.Add(pendingConnection);
BOOL waitResult = RegisterWaitForSingleObject(
&pendingConnection->hWaitHandleConnect,
pendingConnection->hEventConnect,
[](PVOID lpParameter, BOOLEAN TimerOrWaitFired)
{
auto pendingConnection = (PendingConnection*)lpParameter;
auto waitHandle = (HANDLE)InterlockedExchangePointer((PVOID volatile*)&pendingConnection->hWaitHandleConnect, INVALID_HANDLE_VALUE);
if (waitHandle != INVALID_HANDLE_VALUE)
{
UnregisterWait(waitHandle);
}
DWORD transferred = 0;
BOOL result = GetOverlappedResult(pendingConnection->connection->hPipe, &pendingConnection->overlappedConnect, &transferred, FALSE);
if (result == TRUE)
{
if (pendingConnection->server)
{
pendingConnection->server->CompletePendingConnection(pendingConnection, true);
}
}
else
{
auto error = GetLastError();
if (error == ERROR_OPERATION_ABORTED || error == ERROR_INVALID_HANDLE || error == ERROR_BROKEN_PIPE || error == ERROR_NO_DATA)
{
if (pendingConnection->server)
{
pendingConnection->server->CompletePendingConnection(pendingConnection, false);
}
return;
}
CHECK_FAIL(L"GetOverlappedResult(ConnectNamedPipe) failed on unexpected GetLastError.");
}
},
pendingConnection.Obj(),
INFINITE,
WT_EXECUTEONLYONCE);
CHECK_ERROR(waitResult, L"RegisterWaitForSingleObject failed for ConnectNamedPipe.");
}
break;
default:
CHECK_FAIL(L"ConnectNamedPipe failed on unexpected GetLastError.");
}
}
void NamedPipeServer::CompletePendingConnection(Ptr<PendingConnection> pendingConnection, bool connected)
{
auto connection = pendingConnection->connection;
bool shouldListen = false;
bool shouldNotify = false;
{
SPIN_LOCK(lockConnections)
{
pendingConnections.Remove(pendingConnection.Obj());
if (started && !stopped)
{
shouldListen = true;
if (connected)
{
connection->server = this;
connections.Add(connection);
shouldNotify = true;
pendingConnection->connection = nullptr;
pendingConnection->server = nullptr;
}
}
}
}
if (shouldListen)
{
BeginListening();
}
if (shouldNotify)
{
auto result = OnClientConnected(connection.Obj());
if (result == WaitForClientResult::Reject)
{
SPIN_LOCK(lockConnections)
{
connections.Remove(connection.Obj());
}
connection->server = nullptr;
connection->Stop();
}
}
else if (connection)
{
connection->Stop();
}
}
void NamedPipeServer::CompletePendingConnection(PendingConnection* pendingConnection, bool connected)
{
Ptr<PendingConnection> holding;
{
SPIN_LOCK(lockConnections)
{
for (vint i = 0; i < pendingConnections.Count(); i++)
{
if (pendingConnections[i].Obj() == pendingConnection)
{
holding = pendingConnections[i];
break;
}
}
}
}
if (holding)
{
CompletePendingConnection(holding, connected);
}
}
WaitForClientResult NamedPipeServer::OnClientConnected(INetworkProtocolConnection* connection)
{
return WaitForClientResult::Accept;
}
void NamedPipeServer::Start()
{
{
SPIN_LOCK(lockConnections)
{
CHECK_ERROR(!started, L"NamedPipeServer has already started.");
CHECK_ERROR(!stopped, L"NamedPipeServer has stopped.");
started = true;
}
}
BeginListening();
}
void NamedPipeServer::Stop()
{
List<Ptr<NamedPipeConnection>> stoppingConnections;
List<Ptr<PendingConnection>> stoppingPendingConnections;
SPIN_LOCK(lockConnections)
{
if (!stopped)
{
started = false;
stopped = true;
for (auto connection : connections)
{
connection->server = nullptr;
stoppingConnections.Add(connection);
}
for (auto pendingConnection : pendingConnections)
{
stoppingPendingConnections.Add(pendingConnection);
}
connections.Clear();
pendingConnections.Clear();
}
}
for (auto pendingConnection : stoppingPendingConnections)
{
pendingConnection->Stop();
}
for (auto connection : stoppingConnections)
{
connection->Stop();
}
}
bool NamedPipeServer::IsStopped()
{
bool result = false;
SPIN_LOCK(lockConnections)
{
result = stopped;
}
return result;
}
/***********************************************************************
NamedPipeClient
***********************************************************************/
HANDLE NamedPipeClient::ClientCreatePipe(const WString& pipeName)
{
auto fullPipeName = L"\\\\.\\pipe\\" + pipeName;
auto start = GetTickCount64();
while (true)
{
HANDLE hPipe = CreateFile(
fullPipeName.Buffer(),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
if (hPipe != INVALID_HANDLE_VALUE)
{
return hPipe;
}
auto error = GetLastError();
CHECK_ERROR(error == ERROR_FILE_NOT_FOUND || error == ERROR_PIPE_BUSY, L"CreateFile failed.");
CHECK_ERROR(GetTickCount64() - start < 6000, L"CreateFile failed because the pipe server did not become available.");
if (error == ERROR_PIPE_BUSY)
{
WaitNamedPipe(fullPipeName.Buffer(), 100);
}
else
{
Thread::Sleep(1);
}
}
}
NamedPipeClient::NamedPipeClient(const WString& _pipeName)
: NamedPipeConnection(ClientCreatePipe(_pipeName))
{
}
NamedPipeClient::~NamedPipeClient()
{
}
INetworkProtocolConnection* NamedPipeClient::GetConnection()
{
return this;
}
void NamedPipeClient::WaitForServer()
{
status = ClientStatus::WaitingForServer;
DWORD dwPipeMode = PIPE_READMODE_MESSAGE;
BOOL bSucceeded = SetNamedPipeHandleState(
hPipe,
&dwPipeMode,
NULL,
NULL);
CHECK_ERROR(bSucceeded, L"SetNamedPipeHandleState failed.");
status = ClientStatus::Connected;
if (callback)
{
callback->OnConnected();
}
}
ClientStatus NamedPipeClient::GetStatus()
{
return status;
}
void NamedPipeClient::Stop()
{
NamedPipeConnection::Stop();
status = ClientStatus::Disconnected;
}
}
/***********************************************************************
.\INTERPROCESS\WINDOWS\NETWORKPROTOCOL.WINDOWS.CPP
***********************************************************************/
#pragma comment(lib, "httpapi.lib")
#pragma comment(lib, "rpcrt4.lib")