aboutsummaryrefslogtreecommitdiff
path: root/src/base/platform
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2024-10-06 13:57:39 +0800
committercrupest <crupest@outlook.com>2024-10-06 13:57:39 +0800
commitdfe62dcf8bcefc523b466e127c3edc4dc2756629 (patch)
tree1c751a14ba0da07ca2ff805633f97568060aa4c9 /src/base/platform
parentf51eb955e188858272230a990565931e7403f23b (diff)
downloadcru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.tar.gz
cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.tar.bz2
cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.zip
Rename common to base.
Diffstat (limited to 'src/base/platform')
-rw-r--r--src/base/platform/Exception.cpp1
-rw-r--r--src/base/platform/osx/Convert.cpp29
-rw-r--r--src/base/platform/osx/Exception.cpp1
-rw-r--r--src/base/platform/unix/PosixSpawnSubProcess.cpp204
-rw-r--r--src/base/platform/unix/UnixFileStream.cpp106
-rw-r--r--src/base/platform/unix/UnixPipe.cpp51
-rw-r--r--src/base/platform/web/WebException.cpp1
-rw-r--r--src/base/platform/win/BridgeComStream.cpp106
-rw-r--r--src/base/platform/win/BrigdeComStream.h43
-rw-r--r--src/base/platform/win/ComAutoInit.cpp15
-rw-r--r--src/base/platform/win/DebugLogTarget.cpp10
-rw-r--r--src/base/platform/win/Exception.cpp38
-rw-r--r--src/base/platform/win/StreamConvert.cpp28
-rw-r--r--src/base/platform/win/Win32FileStream.cpp122
-rw-r--r--src/base/platform/win/Win32FileStreamPrivate.h13
15 files changed, 768 insertions, 0 deletions
diff --git a/src/base/platform/Exception.cpp b/src/base/platform/Exception.cpp
new file mode 100644
index 00000000..b5e8c5b9
--- /dev/null
+++ b/src/base/platform/Exception.cpp
@@ -0,0 +1 @@
+#include "cru/base/platform/Exception.h"
diff --git a/src/base/platform/osx/Convert.cpp b/src/base/platform/osx/Convert.cpp
new file mode 100644
index 00000000..1a6deb8f
--- /dev/null
+++ b/src/base/platform/osx/Convert.cpp
@@ -0,0 +1,29 @@
+#include "cru/base/platform/osx/Convert.h"
+
+namespace cru::platform::osx {
+CFStringRef Convert(const String& string) {
+ return CFStringCreateWithBytes(
+ nullptr, reinterpret_cast<const UInt8*>(string.data()),
+ string.size() * sizeof(std::uint16_t), kCFStringEncodingUTF16, false);
+}
+
+String Convert(CFStringRef string) {
+ auto length = CFStringGetLength(string);
+
+ String result;
+
+ for (int i = 0; i < length; i++) {
+ result.AppendCodePoint(CFStringGetCharacterAtIndex(string, i));
+ }
+
+ return result;
+}
+
+CFRange Convert(const Range& range) {
+ return CFRangeMake(range.position, range.count);
+}
+
+Range Convert(const CFRange& range) {
+ return Range(range.location, range.length);
+}
+} // namespace cru::platform::osx
diff --git a/src/base/platform/osx/Exception.cpp b/src/base/platform/osx/Exception.cpp
new file mode 100644
index 00000000..a60448dd
--- /dev/null
+++ b/src/base/platform/osx/Exception.cpp
@@ -0,0 +1 @@
+#include "cru/base/platform//osx/Exception.h"
diff --git a/src/base/platform/unix/PosixSpawnSubProcess.cpp b/src/base/platform/unix/PosixSpawnSubProcess.cpp
new file mode 100644
index 00000000..75d48cc2
--- /dev/null
+++ b/src/base/platform/unix/PosixSpawnSubProcess.cpp
@@ -0,0 +1,204 @@
+#include "cru/base/platform/unix/PosixSpawnSubProcess.h"
+#include "cru/base/Exception.h"
+#include "cru/base/Format.h"
+#include "cru/base/Guard.h"
+#include "cru/base/String.h"
+#include "cru/base/SubProcess.h"
+#include "cru/base/log/Logger.h"
+
+#include <signal.h>
+#include <spawn.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <memory>
+#include <unordered_map>
+
+namespace cru::platform::unix {
+PosixSpawnSubProcessImpl::PosixSpawnSubProcessImpl()
+ : pid_(0),
+ exit_code_(0),
+ stdin_pipe_(UnixPipe::Usage::Send, false),
+ stdout_pipe_(UnixPipe::Usage::Receive, false),
+ stderr_pipe_(UnixPipe::Usage::Receive, false) {
+ stdin_stream_ = std::make_unique<UnixFileStream>(
+ stdin_pipe_.GetSelfFileDescriptor(), false, false, true, true);
+ stdout_stream_ = std::make_unique<UnixFileStream>(
+ stdout_pipe_.GetSelfFileDescriptor(), false, true, false, true);
+ stderr_stream_ = std::make_unique<UnixFileStream>(
+ stderr_pipe_.GetSelfFileDescriptor(), false, true, false, true);
+
+ stdout_buffer_stream_ =
+ std::make_unique<io::AutoReadStream>(stdout_stream_.get(), false);
+ stderr_buffer_stream_ =
+ std::make_unique<io::AutoReadStream>(stderr_stream_.get(), false);
+}
+
+PosixSpawnSubProcessImpl::~PosixSpawnSubProcessImpl() {}
+
+namespace {
+char** CreateCstrArray(const std::vector<String>& argv) {
+ std::vector<Buffer> utf8_argv;
+ for (const auto& arg : argv) {
+ utf8_argv.push_back(arg.ToUtf8Buffer());
+ }
+ char** result = new char*[utf8_argv.size() + 1];
+ for (int i = 0; i < utf8_argv.size(); i++) {
+ result[i] = reinterpret_cast<char*>(utf8_argv[i].Detach());
+ }
+ result[utf8_argv.size()] = nullptr;
+ return result;
+}
+
+char** CreateCstrArray(const std::unordered_map<String, String>& envp) {
+ std::vector<String> str_array;
+ for (auto& [key, value] : envp) {
+ str_array.push_back(cru::Format(u"{}={}", key, value));
+ }
+ return CreateCstrArray(str_array);
+}
+
+void DestroyCstrArray(char** argv) {
+ int index = 0;
+ char* arg = argv[index];
+ while (arg) {
+ delete[] arg;
+ index++;
+ arg = argv[index];
+ }
+ delete[] argv;
+}
+} // namespace
+
+void PosixSpawnSubProcessImpl::PlatformCreateProcess(
+ const SubProcessStartInfo& start_info) {
+ int error;
+ auto check_error = [&error](String message) {
+ if (error == 0) return;
+ std::unique_ptr<ErrnoException> inner(new ErrnoException({}, error));
+ throw SubProcessFailedToStartException(std::move(message),
+ std::move(inner));
+ };
+
+ posix_spawn_file_actions_t file_actions;
+ error = posix_spawn_file_actions_init(&file_actions);
+ check_error(u"Failed to call posix_spawn_file_actions_init.");
+ Guard file_actions_guard(
+ [&file_actions] { posix_spawn_file_actions_destroy(&file_actions); });
+
+ // dup2 stdin/stdout/stderr
+ error = posix_spawn_file_actions_adddup2(
+ &file_actions, stdin_pipe_.GetOtherFileDescriptor(), STDIN_FILENO);
+ check_error(u"Failed to call posix_spawn_file_actions_adddup2 on stdin.");
+ error = posix_spawn_file_actions_adddup2(
+ &file_actions, stdout_pipe_.GetOtherFileDescriptor(), STDOUT_FILENO);
+ check_error(u"Failed to call posix_spawn_file_actions_adddup2 on stdout.");
+ error = posix_spawn_file_actions_adddup2(
+ &file_actions, stderr_pipe_.GetOtherFileDescriptor(), STDERR_FILENO);
+ check_error(u"Failed to call posix_spawn_file_actions_adddup2 on stderr.");
+
+ error = posix_spawn_file_actions_addclose(
+ &file_actions, stdin_pipe_.GetOtherFileDescriptor());
+ check_error(
+ u"Failed to call posix_spawn_file_actions_addclose on self fd of stdin.");
+ error = posix_spawn_file_actions_addclose(
+ &file_actions, stdout_pipe_.GetOtherFileDescriptor());
+ check_error(
+ u"Failed to call posix_spawn_file_actions_addclose on self fd stdout.");
+ error = posix_spawn_file_actions_addclose(
+ &file_actions, stderr_pipe_.GetOtherFileDescriptor());
+ check_error(
+ u"Failed to call posix_spawn_file_actions_addclose on self fd stderr.");
+
+ error = posix_spawn_file_actions_addclose(
+ &file_actions, stdin_pipe_.GetSelfFileDescriptor());
+ check_error(
+ u"Failed to call posix_spawn_file_actions_addclose on parent fd of "
+ u"stdin.");
+ error = posix_spawn_file_actions_addclose(
+ &file_actions, stdout_pipe_.GetSelfFileDescriptor());
+ check_error(
+ u"Failed to call posix_spawn_file_actions_addclose on parent fd of "
+ u"stdout.");
+ error = posix_spawn_file_actions_addclose(
+ &file_actions, stderr_pipe_.GetSelfFileDescriptor());
+ check_error(
+ u"Failed to call posix_spawn_file_actions_addclose on parent fd of "
+ u"stderr.");
+
+ posix_spawnattr_t attr;
+ error = posix_spawnattr_init(&attr);
+ check_error(u"Failed to call posix_spawnattr_init.");
+ Guard attr_guard([&attr] { posix_spawnattr_destroy(&attr); });
+
+#ifdef CRU_PLATFORM_OSX
+ error = posix_spawnattr_setflags(&attr, POSIX_SPAWN_CLOEXEC_DEFAULT);
+ check_error(u"Failed to set flag POSIX_SPAWN_CLOEXEC_DEFAULT (osx).");
+#endif
+
+ auto exe = start_info.program.ToUtf8();
+ std::vector<String> arguments{start_info.program};
+ arguments.insert(arguments.cend(), start_info.arguments.cbegin(),
+ start_info.arguments.cend());
+
+ auto argv = CreateCstrArray(arguments);
+ Guard argv_guard([argv] { DestroyCstrArray(argv); });
+
+ auto envp = CreateCstrArray(start_info.environments);
+ Guard envp_guard([envp] { DestroyCstrArray(envp); });
+
+ error = posix_spawnp(&pid_, exe.c_str(), &file_actions, &attr, argv, envp);
+ check_error(u"Failed to call posix_spawnp.");
+
+ error = ::close(stdin_pipe_.GetOtherFileDescriptor());
+ check_error(u"Failed to close child stdin.");
+ error = ::close(stdout_pipe_.GetOtherFileDescriptor());
+ check_error(u"Failed to close child stdout.");
+ error = ::close(stderr_pipe_.GetOtherFileDescriptor());
+ check_error(u"Failed to close child stderr.");
+}
+
+SubProcessExitResult PosixSpawnSubProcessImpl::PlatformWaitForProcess() {
+ int wstatus;
+
+ while (waitpid(pid_, &wstatus, 0) == -1) {
+ if (errno == EINTR) {
+ CRU_LOG_INFO(u"Waitpid is interrupted by a signal. Call it again.");
+ continue;
+ }
+
+ std::unique_ptr<ErrnoException> inner(new ErrnoException({}, errno));
+
+ throw SubProcessInternalException(
+ u"Failed to call waitpid on a subprocess.", std::move(inner));
+ }
+
+ if (WIFEXITED(wstatus)) {
+ return SubProcessExitResult::Normal(WEXITSTATUS(wstatus));
+ } else if (WIFEXITED(wstatus)) {
+ return SubProcessExitResult::Signal(WTERMSIG(wstatus), WCOREDUMP(wstatus));
+ } else {
+ return SubProcessExitResult::Unknown();
+ }
+}
+
+void PosixSpawnSubProcessImpl::PlatformKillProcess() {
+ int error = kill(pid_, SIGKILL);
+ if (error != 0) {
+ std::unique_ptr<ErrnoException> inner(new ErrnoException({}, errno));
+ throw SubProcessInternalException(u"Failed to call kill on a subprocess.",
+ std::move(inner));
+ }
+}
+
+io::Stream* PosixSpawnSubProcessImpl::GetStdinStream() {
+ return stdin_stream_.get();
+}
+
+io::Stream* PosixSpawnSubProcessImpl::GetStdoutStream() {
+ return stdout_buffer_stream_.get();
+}
+
+io::Stream* PosixSpawnSubProcessImpl::GetStderrStream() {
+ return stderr_buffer_stream_.get();
+}
+} // namespace cru::platform::unix
diff --git a/src/base/platform/unix/UnixFileStream.cpp b/src/base/platform/unix/UnixFileStream.cpp
new file mode 100644
index 00000000..c53bbbaa
--- /dev/null
+++ b/src/base/platform/unix/UnixFileStream.cpp
@@ -0,0 +1,106 @@
+#include "cru/base/platform/unix/UnixFileStream.h"
+#include "cru/base/Exception.h"
+#include "cru/base/Format.h"
+#include "cru/base/io/Stream.h"
+#include "cru/base/log/Logger.h"
+
+#include <fcntl.h>
+#include <sys/fcntl.h>
+#include <unistd.h>
+#include <cerrno>
+
+namespace cru::platform::unix {
+using namespace cru::io;
+
+namespace {
+bool OflagCanSeek([[maybe_unused]] int oflag) {
+ // Treat every file seekable.
+ return true;
+}
+
+bool OflagCanRead(int oflag) {
+ // There is a problem: O_RDONLY is 0. So we must test it specially.
+ // If it is not write-only. Then it can read.
+ return !(oflag & O_WRONLY);
+}
+
+bool OflagCanWrite(int oflag) { return oflag & O_WRONLY || oflag & O_RDWR; }
+
+int MapSeekOrigin(Stream::SeekOrigin origin) {
+ switch (origin) {
+ case Stream::SeekOrigin::Begin:
+ return SEEK_SET;
+ case Stream::SeekOrigin::Current:
+ return SEEK_CUR;
+ case Stream::SeekOrigin::End:
+ return SEEK_END;
+ default:
+ throw Exception(u"Invalid seek origin.");
+ }
+}
+} // namespace
+
+UnixFileStream::UnixFileStream(const char *path, int oflag, mode_t mode) {
+ file_descriptor_ = ::open(path, oflag, mode);
+ if (file_descriptor_ == -1) {
+ throw ErrnoException(
+ Format(u"Failed to open file {} with oflag {}, mode {}.",
+ String::FromUtf8(path), oflag, mode));
+ }
+
+ SetSupportedOperations(
+ {OflagCanSeek(oflag), OflagCanRead(oflag), OflagCanWrite(oflag)});
+
+ auto_close_ = true;
+}
+
+UnixFileStream::UnixFileStream(int fd, bool can_seek, bool can_read,
+ bool can_write, bool auto_close)
+ : Stream(can_seek, can_read, can_write) {
+ file_descriptor_ = fd;
+ auto_close_ = auto_close;
+}
+
+UnixFileStream::~UnixFileStream() {
+ if (auto_close_ && file_descriptor_ >= 0) {
+ if (::close(file_descriptor_) == -1) {
+ // We are in destructor, so we can not throw.
+ CRU_LOG_WARN(u"Failed to close file descriptor {}, errno {}.",
+ file_descriptor_, errno);
+ }
+ }
+}
+
+Index UnixFileStream::DoSeek(Index offset, SeekOrigin origin) {
+ off_t result = ::lseek(file_descriptor_, offset, MapSeekOrigin(origin));
+ if (result == -1) {
+ throw ErrnoException(u"Failed to seek file.");
+ }
+ return result;
+}
+
+Index UnixFileStream::DoRead(std::byte *buffer, Index offset, Index size) {
+ auto result = ::read(file_descriptor_, buffer + offset, size);
+ if (result == -1) {
+ throw ErrnoException(u"Failed to read file.");
+ }
+ return result;
+}
+
+Index UnixFileStream::DoWrite(const std::byte *buffer, Index offset,
+ Index size) {
+ auto result = ::write(file_descriptor_, buffer + offset, size);
+ if (result == -1) {
+ throw ErrnoException(u"Failed to write file.");
+ }
+ return result;
+}
+
+void UnixFileStream::DoClose() {
+ CRU_STREAM_BEGIN_CLOSE
+ if (auto_close_ && ::close(file_descriptor_) == -1) {
+ throw ErrnoException(u"Failed to close file.");
+ }
+ file_descriptor_ = -1;
+}
+} // namespace cru::platform::unix
diff --git a/src/base/platform/unix/UnixPipe.cpp b/src/base/platform/unix/UnixPipe.cpp
new file mode 100644
index 00000000..747efe94
--- /dev/null
+++ b/src/base/platform/unix/UnixPipe.cpp
@@ -0,0 +1,51 @@
+#include "cru/base/platform/unix/UnixPipe.h"
+#include "cru/base/Exception.h"
+#include "cru/base/log/Logger.h"
+
+#include <fcntl.h>
+#include <sys/fcntl.h>
+#include <unistd.h>
+
+namespace cru::platform::unix {
+UnixPipe::UnixPipe(Usage usage, bool auto_close, UnixPipeFlag flags)
+ : usage_(usage), auto_close_(auto_close), flags_(flags) {
+ int fds[2];
+ if (pipe(fds) != 0) {
+ throw ErrnoException(u"Failed to create unix pipe.");
+ }
+
+ if (flags & UnixPipeFlags::NonBlock) {
+ fcntl(fds[0], F_SETFL, O_NONBLOCK);
+ fcntl(fds[1], F_SETFL, O_NONBLOCK);
+ }
+
+ read_fd_ = fds[0];
+ write_fd_ = fds[1];
+}
+
+int UnixPipe::GetSelfFileDescriptor() {
+ if (usage_ == Usage::Send) {
+ return write_fd_;
+ } else {
+ return read_fd_;
+ }
+}
+
+int UnixPipe::GetOtherFileDescriptor() {
+ if (usage_ == Usage::Send) {
+ return read_fd_;
+ } else {
+ return write_fd_;
+ }
+}
+
+UnixPipe::~UnixPipe() {
+ if (auto_close_) {
+ auto self_fd = GetSelfFileDescriptor();
+ if (::close(self_fd) != 0) {
+ CRU_LOG_ERROR(u"Failed to close unix pipe file descriptor {}, errno {}.",
+ self_fd, errno);
+ }
+ }
+}
+} // namespace cru::platform::unix
diff --git a/src/base/platform/web/WebException.cpp b/src/base/platform/web/WebException.cpp
new file mode 100644
index 00000000..f473bb5b
--- /dev/null
+++ b/src/base/platform/web/WebException.cpp
@@ -0,0 +1 @@
+#include "cru/base/platform/web/WebException.h"
diff --git a/src/base/platform/win/BridgeComStream.cpp b/src/base/platform/win/BridgeComStream.cpp
new file mode 100644
index 00000000..c6987ab2
--- /dev/null
+++ b/src/base/platform/win/BridgeComStream.cpp
@@ -0,0 +1,106 @@
+#include "BrigdeComStream.h"
+#include "cru/base/io/Stream.h"
+
+namespace cru::platform::win {
+BridgeComStream::BridgeComStream(io::Stream *stream)
+ : stream_(stream), ref_count_(1) {}
+
+BridgeComStream::~BridgeComStream() {}
+
+ULONG BridgeComStream::AddRef() { return ++ref_count_; }
+
+ULONG BridgeComStream::Release() {
+ --ref_count_;
+
+ if (ref_count_ == 0) {
+ delete this;
+ return 0;
+ }
+
+ return ref_count_;
+}
+
+HRESULT BridgeComStream::QueryInterface(const IID &riid, void **ppvObject) {
+ if (riid == IID_IStream) {
+ *ppvObject = static_cast<IStream *>(this);
+ AddRef();
+ return S_OK;
+ } else if (riid == IID_ISequentialStream) {
+ *ppvObject = static_cast<ISequentialStream *>(this);
+ AddRef();
+ return S_OK;
+ } else if (riid == IID_IUnknown) {
+ *ppvObject = static_cast<IUnknown *>(this);
+ AddRef();
+ return S_OK;
+ } else {
+ return E_NOINTERFACE;
+ }
+}
+
+HRESULT BridgeComStream::Read(void *pv, ULONG cb, ULONG *pcbRead) {
+ *pcbRead = stream_->Read(static_cast<std::byte *>(pv), cb);
+ return S_OK;
+}
+
+HRESULT BridgeComStream::Write(const void *pv, ULONG cb, ULONG *pcbWritten) {
+ *pcbWritten = stream_->Write(static_cast<const std::byte *>(pv), cb);
+ return S_OK;
+}
+
+HRESULT BridgeComStream::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin,
+ ULARGE_INTEGER *plibNewPosition) {
+ io::Stream::SeekOrigin so;
+
+ switch (dwOrigin) {
+ case STREAM_SEEK_SET:
+ so = io::Stream::SeekOrigin::Begin;
+ break;
+ case STREAM_SEEK_CUR:
+ so = io::Stream::SeekOrigin::Current;
+ break;
+ case STREAM_SEEK_END:
+ so = io::Stream::SeekOrigin::End;
+ break;
+ default:
+ return STG_E_INVALIDFUNCTION;
+ };
+
+ plibNewPosition->QuadPart = stream_->Seek(dlibMove.QuadPart, so);
+ return S_OK;
+}
+
+HRESULT BridgeComStream::SetSize(ULARGE_INTEGER libNewSize) {
+ return E_NOTIMPL;
+}
+
+HRESULT BridgeComStream::CopyTo(IStream *pstm, ULARGE_INTEGER cb,
+ ULARGE_INTEGER *pcbRead,
+ ULARGE_INTEGER *pcbWritten) {
+ return E_NOTIMPL;
+}
+
+HRESULT BridgeComStream::Commit(DWORD grfCommitFlags) { return S_OK; }
+
+HRESULT BridgeComStream::Revert() { return S_OK; }
+
+HRESULT BridgeComStream::LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb,
+ DWORD dwLockType) {
+ return S_OK;
+}
+
+HRESULT BridgeComStream::UnlockRegion(ULARGE_INTEGER libOffset,
+ ULARGE_INTEGER cb, DWORD dwLockType) {
+ return S_OK;
+}
+
+HRESULT BridgeComStream::Stat(STATSTG *pstatstg, DWORD grfStatFlag) {
+ return E_NOTIMPL;
+}
+
+HRESULT BridgeComStream::Clone(IStream **ppstm) {
+ *ppstm = new BridgeComStream(stream_);
+ return S_OK;
+}
+
+} // namespace cru::platform::win
diff --git a/src/base/platform/win/BrigdeComStream.h b/src/base/platform/win/BrigdeComStream.h
new file mode 100644
index 00000000..1621b567
--- /dev/null
+++ b/src/base/platform/win/BrigdeComStream.h
@@ -0,0 +1,43 @@
+#pragma once
+#include "cru/base/platform/win/WinPreConfig.h"
+
+#include "cru/base/io/Stream.h"
+
+#include <objidlbase.h>
+
+namespace cru::platform::win {
+class BridgeComStream : public IStream {
+ public:
+ explicit BridgeComStream(io::Stream* stream);
+
+ CRU_DELETE_COPY(BridgeComStream)
+ CRU_DELETE_MOVE(BridgeComStream)
+
+ ~BridgeComStream();
+
+ public:
+ ULONG AddRef() override;
+ ULONG Release() override;
+ HRESULT QueryInterface(REFIID riid, void** ppvObject) override;
+
+ HRESULT Read(void* pv, ULONG cb, ULONG* pcbRead) override;
+ HRESULT Write(const void* pv, ULONG cb, ULONG* pcbWritten) override;
+ HRESULT Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin,
+ ULARGE_INTEGER* plibNewPosition) override;
+ HRESULT SetSize(ULARGE_INTEGER libNewSize) override;
+ HRESULT CopyTo(IStream* pstm, ULARGE_INTEGER cb, ULARGE_INTEGER* pcbRead,
+ ULARGE_INTEGER* pcbWritten) override;
+ HRESULT Commit(DWORD grfCommitFlags) override;
+ HRESULT Revert() override;
+ HRESULT LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb,
+ DWORD dwLockType) override;
+ HRESULT UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb,
+ DWORD dwLockType) override;
+ HRESULT Stat(STATSTG* pstatstg, DWORD grfStatFlag) override;
+ HRESULT Clone(IStream** ppstm) override;
+
+ private:
+ io::Stream* stream_;
+ ULONG ref_count_;
+};
+} // namespace cru::platform::win
diff --git a/src/base/platform/win/ComAutoInit.cpp b/src/base/platform/win/ComAutoInit.cpp
new file mode 100644
index 00000000..548a7bea
--- /dev/null
+++ b/src/base/platform/win/ComAutoInit.cpp
@@ -0,0 +1,15 @@
+#include "cru/base/platform/win/ComAutoInit.h"
+#include "cru/base/platform/win/Exception.h"
+
+#include <combaseapi.h>
+
+namespace cru::platform::win {
+ComAutoInit::ComAutoInit() {
+ const auto hresult = ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+ if (FAILED(hresult)) {
+ throw HResultError(hresult, "Failed to call CoInitializeEx.");
+ }
+}
+
+ComAutoInit::~ComAutoInit() { ::CoUninitialize(); }
+} // namespace cru::platform::win
diff --git a/src/base/platform/win/DebugLogTarget.cpp b/src/base/platform/win/DebugLogTarget.cpp
new file mode 100644
index 00000000..89bd3d19
--- /dev/null
+++ b/src/base/platform/win/DebugLogTarget.cpp
@@ -0,0 +1,10 @@
+#include "cru/base/platform/win/DebugLogTarget.h"
+
+namespace cru::platform::win {
+void WinDebugLogTarget::Write(::cru::log::LogLevel level, StringView s) {
+ CRU_UNUSED(level)
+
+ String m = s.ToString();
+ ::OutputDebugStringW(reinterpret_cast<const wchar_t*>(m.c_str()));
+}
+} // namespace cru::platform::win
diff --git a/src/base/platform/win/Exception.cpp b/src/base/platform/win/Exception.cpp
new file mode 100644
index 00000000..5ff6146b
--- /dev/null
+++ b/src/base/platform/win/Exception.cpp
@@ -0,0 +1,38 @@
+#include "cru/base/platform/win/Exception.h"
+#include "cru/base/Format.h"
+
+#include <optional>
+
+namespace cru::platform::win {
+
+inline String HResultMakeMessage(HRESULT h_result,
+ std::optional<String> message) {
+ if (message.has_value())
+ return Format(u"HRESULT: {}. Message: {}", h_result, message->WinCStr());
+ else
+ return Format(u"HRESULT: {}.", h_result);
+}
+
+HResultError::HResultError(HRESULT h_result)
+ : PlatformException(HResultMakeMessage(h_result, std::nullopt)),
+ h_result_(h_result) {}
+
+HResultError::HResultError(HRESULT h_result,
+ std::string_view additional_message)
+ : PlatformException(HResultMakeMessage(
+ h_result, String::FromUtf8(additional_message.data(),
+ additional_message.size()))),
+ h_result_(h_result) {}
+
+inline String Win32MakeMessage(DWORD error_code, String message) {
+ return Format(u"Last error code: {}.\nMessage: {}\n", error_code,
+ message.WinCStr());
+}
+
+Win32Error::Win32Error(String message)
+ : Win32Error(::GetLastError(), message) {}
+
+Win32Error::Win32Error(DWORD error_code, String message)
+ : PlatformException(Win32MakeMessage(error_code, message)),
+ error_code_(error_code) {}
+} // namespace cru::platform::win
diff --git a/src/base/platform/win/StreamConvert.cpp b/src/base/platform/win/StreamConvert.cpp
new file mode 100644
index 00000000..f7a0537c
--- /dev/null
+++ b/src/base/platform/win/StreamConvert.cpp
@@ -0,0 +1,28 @@
+#include "cru/base/platform/win/StreamConvert.h"
+#include "BrigdeComStream.h"
+#include "Win32FileStreamPrivate.h"
+#include "cru/base/Exception.h"
+#include "cru/base/io/MemoryStream.h"
+#include "cru/base/io/OpenFileFlag.h"
+#include "cru/base/platform/win/ComAutoInit.h"
+#include "cru/base/platform/win/Exception.h"
+#include "cru/base/platform/win/Win32FileStream.h"
+
+#include <shlwapi.h>
+#include <winnt.h>
+
+namespace cru::platform::win {
+IStream* ConvertStreamToComStream(io::Stream* stream) {
+ static ComAutoInit com_auto_init;
+
+ if (auto memory_stream = dynamic_cast<io::MemoryStream*>(stream)) {
+ return SHCreateMemStream(
+ reinterpret_cast<const BYTE*>(memory_stream->GetBuffer()),
+ memory_stream->GetSize());
+ } else if (auto file_stream = dynamic_cast<Win32FileStream*>(stream)) {
+ return file_stream->GetPrivate_()->stream_;
+ } else {
+ return new BridgeComStream(stream);
+ }
+}
+} // namespace cru::platform::win
diff --git a/src/base/platform/win/Win32FileStream.cpp b/src/base/platform/win/Win32FileStream.cpp
new file mode 100644
index 00000000..54e6ae45
--- /dev/null
+++ b/src/base/platform/win/Win32FileStream.cpp
@@ -0,0 +1,122 @@
+#include "cru/base/platform/win/Win32FileStream.h"
+
+#include "Win32FileStreamPrivate.h"
+#include "cru/base/io/OpenFileFlag.h"
+#include "cru/base/platform/win/Exception.h"
+
+#include <Windows.h>
+#include <coml2api.h>
+#include <shlwapi.h>
+#include <winnt.h>
+#include <filesystem>
+
+namespace cru::platform::win {
+using namespace cru::io;
+
+Win32FileStream::Win32FileStream(String path, OpenFileFlag flags)
+ : path_(std::move(path)),
+ flags_(flags),
+ p_(new details::Win32FileStreamPrivate()) {
+ DWORD grfMode = STGM_SHARE_DENY_NONE;
+ if (flags & io::OpenFileFlags::Read) {
+ if (flags & io::OpenFileFlags::Write) {
+ grfMode |= STGM_READWRITE;
+ } else {
+ grfMode |= STGM_READ;
+ }
+ } else {
+ if (flags & io::OpenFileFlags::Write) {
+ grfMode |= STGM_WRITE;
+ } else {
+ throw Exception(u"Stream must be readable or writable.");
+ }
+ }
+
+ if (flags & io::OpenFileFlags::Truncate) {
+ grfMode |= STGM_CREATE;
+ }
+
+ IStream* stream;
+
+ ThrowIfFailed(SHCreateStreamOnFileEx(
+ path_.WinCStr(), grfMode, FILE_ATTRIBUTE_NORMAL,
+ flags & io::OpenFileFlags::Create ? TRUE : FALSE, NULL, &stream));
+
+ p_->stream_ = stream;
+}
+
+Win32FileStream::~Win32FileStream() {
+ Close();
+ delete p_;
+}
+
+bool Win32FileStream::CanSeek() { return true; }
+
+Index Win32FileStream::Seek(Index offset, SeekOrigin origin) {
+ CheckClosed();
+
+ DWORD dwOrigin = 0;
+
+ if (origin == SeekOrigin::Current) {
+ dwOrigin = STREAM_SEEK_CUR;
+ } else if (origin == SeekOrigin::End) {
+ dwOrigin = STREAM_SEEK_END;
+ } else {
+ dwOrigin = STREAM_SEEK_SET;
+ }
+
+ LARGE_INTEGER n_offset;
+ n_offset.QuadPart = offset;
+ ULARGE_INTEGER n_new_offset;
+
+ ThrowIfFailed(p_->stream_->Seek(n_offset, dwOrigin, &n_new_offset));
+
+ return n_new_offset.QuadPart;
+}
+
+bool Win32FileStream::CanRead() { return true; }
+
+Index Win32FileStream::Read(std::byte* buffer, Index offset, Index size) {
+ if (size < 0) {
+ throw Exception(u"Size must be greater than 0.");
+ }
+
+ CheckClosed();
+
+ ULONG n_read;
+ ThrowIfFailed(p_->stream_->Read(buffer + offset, size, &n_read));
+ return n_read;
+}
+
+bool Win32FileStream::CanWrite() { return true; }
+
+Index Win32FileStream::Write(const std::byte* buffer, Index offset,
+ Index size) {
+ if (size < 0) {
+ throw Exception(u"Size must be greater than 0.");
+ }
+
+ CheckClosed();
+
+ ULONG n_written;
+ ThrowIfFailed(p_->stream_->Write(buffer + offset, size, &n_written));
+
+ return n_written;
+}
+
+void Win32FileStream::Close() {
+ if (closed_) return;
+
+ if (p_->stream_) {
+ p_->stream_->Release();
+ p_->stream_ = nullptr;
+ }
+
+ closed_ = true;
+}
+
+void Win32FileStream::CheckClosed() {
+ if (closed_) throw Exception(u"Stream is closed.");
+}
+
+} // namespace cru::platform::win
diff --git a/src/base/platform/win/Win32FileStreamPrivate.h b/src/base/platform/win/Win32FileStreamPrivate.h
new file mode 100644
index 00000000..718f8d9a
--- /dev/null
+++ b/src/base/platform/win/Win32FileStreamPrivate.h
@@ -0,0 +1,13 @@
+#include "cru/base/platform/win/WinPreConfig.h"
+
+#include <objidl.h>
+
+namespace cru::platform::win {
+
+namespace details {
+struct Win32FileStreamPrivate {
+ IStream* stream_ = nullptr;
+};
+} // namespace details
+
+} // namespace cru::platform::win