diff --git a/README.md b/README.md index 568514a..e6107da 100644 --- a/README.md +++ b/README.md @@ -109,9 +109,48 @@ Ninety-one observations held, none failed, none went unexamined. ## Toolchains `x86_64-windows-gnu` (GCC producing PE), and the MSVC ABI through `llvm`. The -implementation uses no GNU extension: the object manager's declarations are -written out in `src/win.h` rather than taken from ``, because that -header is complete in one toolchain's sources and partial in another's. +implementation uses no GNU extension. + +## This package takes no vendor SDK, on either side + +The object manager's declarations were written out in `src/win.h` from the +beginning, with the reason recorded there: `` is complete in one +toolchain's sources and partial in another's. That argument was always the same +argument, and it had only been applied to the half where one toolchain disagrees +with another rather than to the half where one MACHINE disagrees with another. + +**Declarations.** `src/win32.h` now declares the whole of what this +implementation calls — forty-seven functions and the types and constants they +need — so nothing here opens ``. The list was not read out of the +sources: the header was removed and the compiler was asked what was missing. +Each name carries `__declspec(dllimport)`, which is not an optimisation — it is +what makes the objects say that these are this environment's interfaces reached +through its import table, and this package's own independence check reads exactly +that. + +**Where they live.** `port/*.def` names the library each of those forty-five +symbols is exported from, and the build program turns them into import libraries. +An import library is a list of names rather than code, which is why generating +one is a complete substitute rather than an approximation. The DLL each name +belongs to was read out of the corresponding mingw import library rather than +assumed — `WaitOnAddress` and its two neighbours are **not** in `kernel32.dll`, +and putting them there produces an import table that links and then fails to +bind. + +⚠️ **Supplied only where the system's own are absent.** On this system they are +present, they are the vendor's, and they list every name rather than the +forty-five this implementation calls; `-L` is searched first, so supplying ours +there would shadow them and a consumer calling a forty-sixth would be told there +is no such name. + +⚠️ Measured 2026-08-22, on a clean continuous-integration runner, after every +object had compiled: + +``` +lld: error: unable to find library -lkernel32 +``` + +The compile had been free of a vendor SDK since `win32.h`; the link had not. ## License diff --git a/build.mcpp b/build.mcpp new file mode 100644 index 0000000..8a8fa9e --- /dev/null +++ b/build.mcpp @@ -0,0 +1,156 @@ +import mcpp; +import std; + +// ⭐⭐ WHERE THIS SYSTEM'S NAMES LIVE, PUT ON THE CONSUMER'S LINK LINE. +// +// `src/win32.h` and `src/win.h` freed the COMPILE from a vendor SDK. The LINK +// was still reaching for `libkernel32.a` and its three neighbours — files that +// exist on a machine with mingw installed and nowhere else. +// +// ⚠️ Measured 2026-08-22 on a clean CI runner, after every object compiled: +// +// lld: error: unable to find library -lkernel32 +// lld: error: unable to find library -lntdll +// lld: error: unable to find library -lshell32 +// lld: error: unable to find library -lsynchronization +// +// and it had passed on a developer's machine, which had mingw installed. This +// repository has recorded that shape before: a green that came from history the +// new machine does not have. +// +// ⭐ An import library is a LIST OF NAMES, not code — which is why generating +// one from `port/*.def` is a complete substitute rather than an approximation, +// the same reason `openkal-macos/port/libSystem.tbd` is one for that system. +// +// ⚠️⚠️ AND ONLY WHERE THE SYSTEM'S OWN ARE ABSENT. +// +// On this system they are present, they are the vendor's, and they list every +// name rather than the forty-five this implementation calls. `-L` is searched +// first, so supplying ours there would not augment them — it would SHADOW them, +// and a consumer that calls a forty-sixth would be told there is no such name. +// That is exactly the defect `openkal-macos` had with its `libSystem` stub, one +// commit before this was written, and it is the same fix: a substitute is +// needed where the thing it substitutes for is absent. + +namespace { + +// ⭐⭐ WHICH COMPILER FAMILY RESOLVED — AND WHAT AN ABSENT ANSWER MEANS. +// +// ⚠️ READ FROM THE ENVIRONMENT, NOT THROUGH A HELPER. A build program is +// compiled against the `mcpp` module of whichever tool RUNS it, so naming a +// helper newer than the released tool makes this package require an unreleased +// one. Measured 2026-08-22, on every row of three repositories at once: +// +// build.mcpp: error: no member named 'compiler' in namespace 'mcpp' +// build.mcpp: error: 'toolchain_dir' is not a member of 'mcpp' +// +// — and the second is the more useful of the two, because `toolchain_dir` was +// the FALLBACK written for the first. The environment variables are the +// contract and `std::getenv` reads them on every version. +// +// ⚠️ AND AN ABSENT VALUE IS NOT "NO COMPILER". Measured against mcpp 2026.8.19.1, +// which is what CI installs: `MCPP_TARGET_OS`, `MCPP_HOST`, `MCPP_OUT_DIR`, +// `MCPP_MANIFEST_DIR`, `MCPP_TARGET` and `MCPP_TARGET_ARCH` are all set, and +// `MCPP_COMPILER` and `MCPP_TOOLCHAIN_DIR` are absent. +// +// ⭐ So absent means "a build tool from before the question could be asked" — +// and such a tool cannot produce the configuration the answer would change. The +// clang-over-openkal cross to PE arrives on the same release as the variable. +// The one configuration a tool that predates it can produce is the GCC one, so +// that is what absent is answered with, and it is a deduction rather than a +// default. +std::string compiler_family() { + if (const char* v = std::getenv("MCPP_COMPILER"); v && *v) return v; + return "gcc"; +} + +std::string env_or_empty(const char* name) { + const char* v = std::getenv(name); + return v ? v : ""; +} + +bool host_is_windows() { + return env_or_empty("MCPP_HOST").contains("windows"); +} + +// The tool that turns a `.def` into an import library. +// +// ⚠️ ITS NAME IS A PROPERTY OF THE COMPILER FAMILY, NOT OF THE PACKAGE. LLVM +// spells it `llvm-dlltool`; binutils spells it `dlltool`, and a cross binutils +// prefixes it with the triple. Measured 2026-08-22 — this file named only the +// first, and the row of CI that builds with a GCC toolchain said: +// +// sh: 1: llvm-dlltool: not found +// +// ⭐ So the family is ASKED for rather than assumed — see `compiler_family()` +// above for how, and why it is not `mcpp::compiler()`. +// +// ⚠️ Asked for rather than DECLARED, too: a package that put `xim:llvm` in its +// dependencies would pin itself to one implementation of the toolchain. +std::string dlltool() { + const std::string dir = env_or_empty("MCPP_TOOLCHAIN_DIR"); + + // Only reached under clang (see `main`), so LLVM's spelling is first. The + // other two are kept because both read the same `.def` and both take + // `-m i386:x86-64`, so either is a correct answer if it is what is there. + const std::vector names = { + "llvm-dlltool", "x86_64-w64-mingw32-dlltool", "dlltool" }; + + if (!dir.empty()) { + for (const auto& n : names) + for (auto candidate : { std::format("{}/{}", dir, n), + std::format("{}/bin/{}", dir, n) }) { + std::error_code ec; + if (std::filesystem::exists(candidate, ec)) return candidate; + } + } + // Nothing beside the driver. The first name is then tried on PATH, and if it + // is not there the run below reports it by name. + return names.front(); +} + +} // namespace + +int main() { + mcpp::rerun_if_changed("port/kernel32.def"); + mcpp::rerun_if_changed("port/ntdll.def"); + mcpp::rerun_if_changed("port/shell32.def"); + mcpp::rerun_if_changed("port/synchronization.def"); + + if (host_is_windows()) return 0; + + // ⭐⭐ AND ONLY UNDER CLANG, WHICH IS THE OTHER HALF OF THE SAME FACT. + // + // A GCC toolchain for this format IS a mingw payload, and a mingw payload + // carries these four libraries — complete ones, with every name rather than + // the forty-five this implementation calls. Generating ours there would + // shadow them for the same reason supplying them on this system would (see + // the note above), and it would need `dlltool` under a different name into + // the bargain. + // + // ⇒ The gap this file exists to close is the clang one: the LLVM payload is + // a compiler, not a system, and it brings no import libraries at all. + if (compiler_family() != "clang") return 0; + + const std::string out = env_or_empty("MCPP_OUT_DIR"); + const std::string root = env_or_empty("MCPP_MANIFEST_DIR"); + if (out.empty() || root.empty()) return 0; + const std::string tool = dlltool(); + + for (auto name : { "kernel32", "ntdll", "shell32", "synchronization" }) { + const auto def = std::format("{}/port/{}.def", root, name); + const auto lib = std::format("{}/lib{}.a", out, name); + // ⚠️ `-m i386:x86-64` is stated. See port/README.md: the 32-bit ABI + // would need `@N` decoration on these names, which is a property of + // that ABI rather than of the list. + const auto cmd = std::format( + "\"{}\" -m i386:x86-64 -d \"{}\" -l \"{}\"", tool, def, lib); + if (std::system(cmd.c_str()) != 0) { + std::cerr << "openkal-windows: could not build the import library " + "for " << name << " (" << cmd << ")\n"; + return 1; + } + } + mcpp::link_search(out.c_str()); + return 0; +} diff --git a/mcpp.toml b/mcpp.toml index a6b17fa..27640b8 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -4,11 +4,21 @@ name = "openkal-windows" version = "0.1.1" description = "An implementation of openkal for Windows, written on the Win32 interfaces and the object manager beneath them, using no C runtime symbol." license = "Apache-2.0" + +# The layer this package supplies, in the vocabulary the engine resolves. +# +# `mcpp:kernel-abi` names the platform interface a C library sits on. On a +# traditional stack that seam is unnamed — a C library issues system calls or +# calls the platform's own entry points directly — and naming it is what lets +# one C library sit above several platforms. `=openkal` is the interface this +# package answers to; several packages answer to it and the engine knows none +# of them by name. +provides = ["mcpp:kernel-abi=openkal"] authors = ["mcpplibs"] repo = "https://github.com/mcpplibs/openkal-windows" [dependencies] -openkal = "0.5.2" +openkal = { git = "https://github.com/mcpplibs/openkal", branch = "feat/openkal-closure" } # The package contributes definitions and no modules. The interface it # implements is declared by the specification package, which this package diff --git a/port/README.md b/port/README.md new file mode 100644 index 0000000..32eb9d5 --- /dev/null +++ b/port/README.md @@ -0,0 +1,42 @@ +# `port/` — what this system's own interfaces are called, and where they live + +`src/win32.h` and `src/win.h` say what this implementation CALLS. These four +files say which library each of those names lives in, so that a link does not +need a vendor SDK either. + +⭐ **The same argument as the headers, applied one step later.** Removing +`` freed the COMPILE from a toolchain-provided SDK. The LINK was +still reaching for `libkernel32.a` and its neighbours — files that exist on a +machine with mingw installed and nowhere else. Measured 2026-08-22, on a clean +CI runner, after every object compiled: + +``` +lld: error: unable to find library -lkernel32 +lld: error: unable to find library -lntdll +lld: error: unable to find library -lshell32 +lld: error: unable to find library -lsynchronization +``` + +⚠️ **And it had passed on a developer's machine**, which had mingw installed — +the shape this repository has recorded before as "a green that came from +history the new machine does not have". + +⭐ **An import library is a list of names, not code.** It carries the DLL each +name lives in and nothing else, which is why generating one from a list is a +complete substitute rather than an approximation — the same reason +`openkal-macos/port/libSystem.tbd` is one for that system. + +## The lists were measured, not read + +Every object of a complete build was given to `nm`, and the undefined symbols +are what these four files contain. The DLL each belongs to was read out of the +corresponding mingw import library rather than assumed: `WaitOnAddress` and its +two neighbours are **not** in `kernel32.dll`, and putting them there would +produce an import table that fails to bind on the system it names. + +## ⚠️ x86-64 only, and the reason is stated rather than assumed + +`llvm-dlltool` is invoked with `-m i386:x86-64`. On the 32-bit ABI these names +would need `@N` stdcall decoration, which is a property of that ABI rather than +of this list. That target is not built today; when it is, this is where it is +answered. diff --git a/port/kernel32.def b/port/kernel32.def new file mode 100644 index 0000000..92175b1 --- /dev/null +++ b/port/kernel32.def @@ -0,0 +1,41 @@ +; The bulk of this system's interface. Read out of the objects of a +; complete build, so the list is what is CALLED rather than what exists. +; +; Generated into an import library by build.mcpp; see port/README.md. +LIBRARY KERNEL32.dll +EXPORTS +CloseHandle +CreateFileW +CreateProcessW +CreateThread +FlushFileBuffers +FreeEnvironmentStringsW +GetCommandLineW +GetConsoleMode +GetCurrentDirectoryW +GetCurrentProcess +GetCurrentThreadId +GetEnvironmentStringsW +GetExitCodeProcess +GetFileType +GetFinalPathNameByHandleW +GetLastError +GetLogicalDriveStringsW +GetProcessHeap +GetStdHandle +GetSystemTimePreciseAsFileTime +HeapAlloc +HeapFree +LocalFree +MultiByteToWideChar +QueryPerformanceCounter +QueryPerformanceFrequency +ReadFile +SetFilePointerEx +SetHandleInformation +Sleep +SwitchToThread +TerminateProcess +WaitForSingleObject +WideCharToMultiByte +WriteFile diff --git a/port/ntdll.def b/port/ntdll.def new file mode 100644 index 0000000..4358c82 --- /dev/null +++ b/port/ntdll.def @@ -0,0 +1,13 @@ +; The object manager. `src/win.h` declares these, with the reason: a name +; relative to a directory is what this layer offers and the documented layer +; above it does not. +; +; Generated into an import library by build.mcpp; see port/README.md. +LIBRARY ntdll.dll +EXPORTS +NtClose +NtCreateFile +NtQueryDirectoryFile +NtQueryInformationFile +NtSetInformationFile +RtlNtStatusToDosError diff --git a/port/shell32.def b/port/shell32.def new file mode 100644 index 0000000..6159ad0 --- /dev/null +++ b/port/shell32.def @@ -0,0 +1,7 @@ +; One name. The system's own splitting of a command line, which this +; implementation uses rather than reimplementing the rule. +; +; Generated into an import library by build.mcpp; see port/README.md. +LIBRARY SHELL32.dll +EXPORTS +CommandLineToArgvW diff --git a/port/synchronization.def b/port/synchronization.def new file mode 100644 index 0000000..6c9e4ba --- /dev/null +++ b/port/synchronization.def @@ -0,0 +1,11 @@ +; The suspension primitive. ⚠️ NOT IN kernel32.dll — these three live in +; the API set below, and an import table naming the wrong library links and +; then fails to bind. Read out of mingw's own libsynchronization.a rather +; than assumed. +; +; Generated into an import library by build.mcpp; see port/README.md. +LIBRARY api-ms-win-core-synch-l1-2-0.dll +EXPORTS +WaitOnAddress +WakeByAddressAll +WakeByAddressSingle diff --git a/src/win.h b/src/win.h index 6ca0768..714fee7 100644 --- a/src/win.h +++ b/src/win.h @@ -26,14 +26,17 @@ // one layer down, there is nothing to resolve. #pragma once -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include -#include +// ⭐ THIS SYSTEM'S INTERFACE, DECLARED BY THIS PACKAGE. +// +// It used to be `` and `` — a vendor SDK that had to be +// found somewhere, and "somewhere" turned out to be whichever copy the machine +// happened to have. win32.h records the measurement and the method. +// +// ⚠️ AND THE ARGUMENT IS THE ONE THIS FILE ALREADY MAKES, ten lines down, about +// ``. That argument was always the same argument; it had only been +// applied to the half where one TOOLCHAIN disagrees with another, and not to +// the half where one MACHINE disagrees with another. +#include "win32.h" // The width of a machine word is taken from the specification rather than from // the compiler, because the three compilers this package is built with do not @@ -139,28 +142,38 @@ enum : unsigned long { }; extern "C" { -long __stdcall NtCreateFile(void** handle, unsigned long access, +// ⚠️ `dllimport` HERE TOO, AND IT IS THE SAME FACT AS IN `win32.h`. +// +// These live in `ntdll.dll`. Omitting the attribute still links — the linker +// synthesises a thunk through the import table — but it changes what the OBJECT +// says about itself, and this package's own independence check reads exactly +// that. `RtlNtStatusToDosError` is declared in both headers, so leaving one of +// them without it also produced: +// +// warning: 'okw::RtlNtStatusToDosError' redeclared without 'dllimport' +// attribute: previous 'dllimport' ignored [-Winconsistent-dllimport] +__declspec(dllimport) long __stdcall NtCreateFile(void** handle, unsigned long access, object_attributes* attributes, io_status_block* status, okw_i64* allocation, unsigned long file_attributes, unsigned long share, unsigned long disposition, unsigned long options, void* ea, unsigned long ea_length); -long __stdcall NtClose(void* handle); -long __stdcall NtReadFile(void* handle, void* event, void* apc, void* apc_context, +__declspec(dllimport) long __stdcall NtClose(void* handle); +__declspec(dllimport) long __stdcall NtReadFile(void* handle, void* event, void* apc, void* apc_context, io_status_block* status, void* buffer, unsigned long length, okw_i64* offset, unsigned long* key); -long __stdcall NtWriteFile(void* handle, void* event, void* apc, void* apc_context, +__declspec(dllimport) long __stdcall NtWriteFile(void* handle, void* event, void* apc, void* apc_context, io_status_block* status, const void* buffer, unsigned long length, okw_i64* offset, unsigned long* key); -long __stdcall NtQueryInformationFile(void* handle, io_status_block* status, void* info, +__declspec(dllimport) long __stdcall NtQueryInformationFile(void* handle, io_status_block* status, void* info, unsigned long length, int cls); -long __stdcall NtSetInformationFile(void* handle, io_status_block* status, void* info, +__declspec(dllimport) long __stdcall NtSetInformationFile(void* handle, io_status_block* status, void* info, unsigned long length, int cls); -long __stdcall NtQueryDirectoryFile(void* handle, void* event, void* apc, void* apc_context, +__declspec(dllimport) long __stdcall NtQueryDirectoryFile(void* handle, void* event, void* apc, void* apc_context, io_status_block* status, void* buffer, unsigned long length, int cls, unsigned char single, unicode_string* pattern, unsigned char restart); -long __stdcall NtFlushBuffersFile(void* handle, io_status_block* status); -unsigned long __stdcall RtlNtStatusToDosError(long status); +__declspec(dllimport) long __stdcall NtFlushBuffersFile(void* handle, io_status_block* status); +__declspec(dllimport) unsigned long __stdcall RtlNtStatusToDosError(long status); } inline bool ok(long status) { return status >= 0; } diff --git a/src/win32.h b/src/win32.h new file mode 100644 index 0000000..cf71357 --- /dev/null +++ b/src/win32.h @@ -0,0 +1,307 @@ +// The whole of what this implementation uses from this system's own interface. +// +// ⭐⭐ WHY THIS FILE EXISTS, AND WHY IT IS NOT A DEVIATION. +// +// openkal is a specification. It says nothing about how a backend is +// implemented, and the four that exist do it four ways: openkal-linux issues +// this kernel's calls directly, openkal-opensbi issues `ecall` into firmware, +// openkal-macos calls two names it borrows through a stub it wrote itself, and +// this one calls the Win32 API. **Calling the Win32 API is not the question.** +// +// The question is where the DECLARATIONS come from, and until this file they +// came from `` — a vendor SDK that has to be found somewhere. Found +// where? On a machine with a Windows-targeting toolchain payload, the payload's. +// On a machine with a system-wide mingw, that one, at whatever version that +// machine has. On a machine with neither, the build fails naming a header +// rather than a missing dependency. +// +// ⚠️ Measured 2026-08-23, cross-compiling this package with the target side +// coming from packages rather than from a payload: +// +// win.h:35 → /usr/x86_64-w64-mingw32/include/windows.h +// → …/xim-x-llvm/…/include/c++/v1/ctype.h +// → __config:13 '__config_site' file not found +// +// Three layers from the cause, and the cause is that a file belonging to nobody +// was on the search path. On a machine with a DIFFERENT mingw it may silently +// succeed against different declarations, which is the shape of every false +// green this repository has recorded. +// +// ⭐ AND THE OTHER THREE IMPLEMENTATIONS ALREADY SHOW THE ANSWER. None of them +// takes a vendor SDK: openkal-linux writes the system-call numbers, openkal- +// opensbi writes the SBI extension identifiers, openkal-macos writes a stub +// listing the two names it borrows. This file is that, for this system. +// +// ⚠️ AND HALF OF IT WAS ALREADY WRITTEN. `win.h` beside this file has carried +// the entire NT object-manager layer since it was written, with the reason +// stated: `` "is present in one toolchain's sources and partial in +// another's". That argument was always the same argument; it had only been +// applied to the half where a toolchain disagreed with another toolchain, +// rather than to the half where a MACHINE disagrees with another machine. +// +// HOW THE LIST WAS OBTAINED +// +// ⚠️ Not by reading the sources. `` was removed and the compiler was +// asked what it then could not resolve; the answer is this file. That is the +// same method openkal-macos used for its stub, and it is preferred for the same +// reason: a reading produces names the configured build never uses, and the +// compiler does not. +#pragma once + +// ── the machine's own words ───────────────────────────────────────────────── +// +// ⚠️ Written out rather than taken from a C library. This package is built by +// three toolchains and this file must mean the same thing under all of them, +// and ``'s own spellings are ultimately these. +using BOOL = int; +using BYTE = unsigned char; +using WORD = unsigned short; +using DWORD = unsigned long; // ⚠️ `long`, not `int` — this is LLP64 +using UINT = unsigned int; +using ULONG = unsigned long; +using LONG = long; +using HANDLE = void*; +using HLOCAL = void*; +using LPVOID = void*; +using LPCVOID = const void*; +using LPWSTR = wchar_t*; +using LPCWSTR = const wchar_t*; +using LPSTR = char*; +using LPCSTR = const char*; + +// The calling convention. ⚠️ It is ignored on the 64-bit ABI and load-bearing on +// the 32-bit one, and writing it costs nothing on either — while omitting it +// would make this file wrong for a target it is otherwise correct for. +#define OKW_API __stdcall + +// ⭐⭐ AND WHERE THEY LIVE, WHICH IS NOT AN OPTIMISATION. +// +// Every function below is in a DLL, and `` says so with +// `__declspec(dllimport)`. Omitting it still LINKS: the linker notices the +// symbol resolves through an import library and synthesises a thunk that jumps +// through the import table. The program runs, so nothing here fails — and that +// is exactly why it has to be written rather than discovered. +// +// ⚠️ Measured 2026-08-23. This package's own independence check permits +// `__imp_*` because those names ARE this environment's interface reached +// through its import table, and it rejects everything else because everything +// else would be a C runtime. Declaring these without `dllimport` made the +// objects name them bare, and the check reported twenty-two of this system's +// own functions as symbols the implementation "must not" reference: +// +// the implementation references a symbol it must not: WriteFile +// the implementation references a symbol it must not: HeapAlloc +// +// The check was right and the declarations were wrong. It was `` +// that had been supplying this attribute, and replacing that header without it +// changed what the objects say about themselves. +#define OKW_IMPORT __declspec(dllimport) + +union LARGE_INTEGER { + struct { DWORD LowPart; LONG HighPart; } u; + long long QuadPart; +}; + +struct FILETIME { DWORD dwLowDateTime; DWORD dwHighDateTime; }; + +struct SECURITY_ATTRIBUTES { + DWORD nLength; + LPVOID lpSecurityDescriptor; + BOOL bInheritHandle; +}; + +struct OVERLAPPED { + unsigned long long Internal; + unsigned long long InternalHigh; + union { + struct { DWORD Offset; DWORD OffsetHigh; } u; + LPVOID Pointer; + }; + HANDLE hEvent; +}; + +// ⚠️ THE LAYOUT IS THE CONTRACT. These two are filled in by this package and +// read by the system, so a member of the wrong width does not fail to compile — +// it shifts everything after it. The order is the documented one. +struct STARTUPINFOW { + DWORD cb; + LPWSTR lpReserved; + LPWSTR lpDesktop; + LPWSTR lpTitle; + DWORD dwX, dwY, dwXSize, dwYSize; + DWORD dwXCountChars, dwYCountChars, dwFillAttribute, dwFlags; + WORD wShowWindow, cbReserved2; + BYTE* lpReserved2; + HANDLE hStdInput, hStdOutput, hStdError; +}; + +struct PROCESS_INFORMATION { + HANDLE hProcess; + HANDLE hThread; + DWORD dwProcessId; + DWORD dwThreadId; +}; + +// ── the constants this package names ──────────────────────────────────────── +// ⚠️ NOT `constexpr`. A cast from an integer to a pointer is not a constant +// expression, and the compiler says so — `` spells this as a macro +// for the same reason. `inline const` gives one object across every translation +// unit without claiming something the language does not allow. +inline const HANDLE INVALID_HANDLE_VALUE = reinterpret_cast(-1); + +// The two the system's own headers define as macros, written as what they are. +enum : BOOL { FALSE = 0, TRUE = 1 }; + +enum : DWORD { + STD_INPUT_HANDLE = static_cast(-10), + STD_OUTPUT_HANDLE = static_cast(-11), + STD_ERROR_HANDLE = static_cast(-12), + + GENERIC_READ = 0x80000000u, + GENERIC_WRITE = 0x40000000u, + SYNCHRONIZE = 0x00100000u, + + DELETE = 0x00010000u, + FILE_SHARE_READ = 0x1, FILE_SHARE_WRITE = 0x2, FILE_SHARE_DELETE = 0x4, + + // The access rights this package asks for, and the two composites the + // system documents. ⚠️ A composite written as its own number rather than + // assembled here: assembling it would be this file deciding what the system + // means by "generic read", and it does not get to decide that. + FILE_READ_DATA = 0x0001, + FILE_WRITE_DATA = 0x0002, + FILE_APPEND_DATA = 0x0004, + FILE_READ_ATTRIBUTES = 0x0080, + FILE_WRITE_ATTRIBUTES = 0x0100, + FILE_GENERIC_READ = 0x00120089u, + FILE_GENERIC_WRITE = 0x00120116u, + + FILE_ATTRIBUTE_DIRECTORY = 0x10, + FILE_ATTRIBUTE_READONLY = 0x1, + FILE_ATTRIBUTE_REPARSE_POINT = 0x400, + + FILE_BEGIN = 0, FILE_CURRENT = 1, FILE_END = 2, + + CREATE_UNICODE_ENVIRONMENT = 0x00000400u, + OPEN_EXISTING = 3, + FILE_ATTRIBUTE_NORMAL = 0x80, + FILE_FLAG_BACKUP_SEMANTICS = 0x02000000u, + FILE_LIST_DIRECTORY = 0x1, + FILE_NAME_NORMALIZED = 0x0, + VOLUME_NAME_DOS = 0x0, + FILE_TYPE_DISK = 0x0001, + + HANDLE_FLAG_INHERIT = 0x1, + STARTF_USESTDHANDLES = 0x00000100u, + + INFINITE = 0xFFFFFFFFu, + WAIT_OBJECT_0 = 0x00000000u, + + CP_UTF8 = 65001, + MB_ERR_INVALID_CHARS = 0x8, +}; + +// The error values this package translates. ⚠️ Only these — openkal's error set +// is closed, and a value with no mapping is reported as `kal_err_io` rather than +// invented, so listing more would be listing names nothing reads. +enum : DWORD { + ERROR_SUCCESS = 0, + ERROR_INVALID_FUNCTION = 1, + ERROR_ACCESS_DENIED = 5, + ERROR_INVALID_HANDLE = 6, + ERROR_NOT_ENOUGH_MEMORY = 8, + ERROR_OUTOFMEMORY = 14, + ERROR_WRITE_PROTECT = 19, + ERROR_SHARING_VIOLATION = 32, + ERROR_HANDLE_EOF = 38, + ERROR_HANDLE_DISK_FULL = 39, + ERROR_NOT_SUPPORTED = 50, + ERROR_INVALID_PARAMETER = 87, + ERROR_CALL_NOT_IMPLEMENTED = 120, + ERROR_NEGATIVE_SEEK = 131, + ERROR_DISK_FULL = 112, + ERROR_INVALID_NAME = 123, + ERROR_FILENAME_EXCED_RANGE = 206, + ERROR_BROKEN_PIPE = 109, + ERROR_NO_DATA = 232, + ERROR_PIPE_NOT_CONNECTED = 233, + ERROR_TIMEOUT = 1460, + ERROR_FILE_NOT_FOUND = 2, + ERROR_PATH_NOT_FOUND = 3, + ERROR_NO_MORE_FILES = 18, + ERROR_FILE_EXISTS = 80, + ERROR_ALREADY_EXISTS = 183, + ERROR_DIRECTORY = 267, + ERROR_DIR_NOT_EMPTY = 145, + ERROR_IO_PENDING = 997, +}; + +// ── the functions ─────────────────────────────────────────────────────────── +extern "C" { + +OKW_IMPORT HANDLE OKW_API GetStdHandle(DWORD); +OKW_IMPORT BOOL OKW_API CloseHandle(HANDLE); +OKW_IMPORT DWORD OKW_API GetLastError(void); +OKW_IMPORT DWORD OKW_API GetFileType(HANDLE); +OKW_IMPORT BOOL OKW_API SetHandleInformation(HANDLE, DWORD, DWORD); +OKW_IMPORT BOOL OKW_API GetConsoleMode(HANDLE, DWORD*); + +OKW_IMPORT BOOL OKW_API ReadFile(HANDLE, LPVOID, DWORD, DWORD*, OVERLAPPED*); +OKW_IMPORT BOOL OKW_API WriteFile(HANDLE, LPCVOID, DWORD, DWORD*, OVERLAPPED*); +OKW_IMPORT BOOL OKW_API FlushFileBuffers(HANDLE); +OKW_IMPORT BOOL OKW_API SetFilePointerEx(HANDLE, LARGE_INTEGER, LARGE_INTEGER*, DWORD); +OKW_IMPORT HANDLE OKW_API CreateFileW(LPCWSTR, DWORD, DWORD, SECURITY_ATTRIBUTES*, + DWORD, DWORD, HANDLE); +OKW_IMPORT DWORD OKW_API GetFinalPathNameByHandleW(HANDLE, LPWSTR, DWORD, DWORD); +OKW_IMPORT DWORD OKW_API GetLogicalDriveStringsW(DWORD, LPWSTR); +OKW_IMPORT DWORD OKW_API GetCurrentDirectoryW(DWORD, LPWSTR); + +OKW_IMPORT HANDLE OKW_API GetProcessHeap(void); +OKW_IMPORT LPVOID OKW_API HeapAlloc(HANDLE, DWORD, unsigned long long); +OKW_IMPORT BOOL OKW_API HeapFree(HANDLE, DWORD, LPVOID); +OKW_IMPORT HLOCAL OKW_API LocalFree(HLOCAL); + +OKW_IMPORT LPWSTR OKW_API GetCommandLineW(void); +OKW_IMPORT LPWSTR OKW_API GetEnvironmentStringsW(void); +OKW_IMPORT BOOL OKW_API FreeEnvironmentStringsW(LPWSTR); + +OKW_IMPORT BOOL OKW_API CreateProcessW(LPCWSTR, LPWSTR, SECURITY_ATTRIBUTES*, + SECURITY_ATTRIBUTES*, BOOL, DWORD, LPVOID, + LPCWSTR, STARTUPINFOW*, PROCESS_INFORMATION*); +OKW_IMPORT BOOL OKW_API GetExitCodeProcess(HANDLE, DWORD*); +OKW_IMPORT BOOL OKW_API TerminateProcess(HANDLE, UINT); +OKW_IMPORT HANDLE OKW_API GetCurrentProcess(void); +OKW_IMPORT DWORD OKW_API WaitForSingleObject(HANDLE, DWORD); + +OKW_IMPORT HANDLE OKW_API CreateThread(SECURITY_ATTRIBUTES*, unsigned long long, + DWORD (OKW_API*)(LPVOID), LPVOID, DWORD, DWORD*); +OKW_IMPORT DWORD OKW_API GetCurrentThreadId(void); +OKW_IMPORT void OKW_API Sleep(DWORD); +OKW_IMPORT BOOL OKW_API SwitchToThread(void); + +// The address-based wait, which is what openkal.task's suspension primitive +// rests on here. ⚠️ In `API-MS-Win-Core-Synch-l1-2-0`, which is why the link +// line names `-lsynchronization` rather than only `-lkernel32`. +OKW_IMPORT BOOL OKW_API WaitOnAddress(volatile void*, void*, unsigned long long, DWORD); +OKW_IMPORT void OKW_API WakeByAddressSingle(void*); +OKW_IMPORT void OKW_API WakeByAddressAll(void*); + +OKW_IMPORT void OKW_API GetSystemTimePreciseAsFileTime(FILETIME*); +OKW_IMPORT BOOL OKW_API QueryPerformanceCounter(LARGE_INTEGER*); +OKW_IMPORT BOOL OKW_API QueryPerformanceFrequency(LARGE_INTEGER*); + +OKW_IMPORT int OKW_API MultiByteToWideChar(UINT, DWORD, LPCSTR, int, LPWSTR, int); +OKW_IMPORT int OKW_API WideCharToMultiByte(UINT, DWORD, LPCWSTR, int, LPSTR, int, + LPCSTR, BOOL*); + +// From shell32, and the only name this package takes from it. +OKW_IMPORT LPWSTR* OKW_API CommandLineToArgvW(LPCWSTR, int*); + +// ── ntdll ─────────────────────────────────────────────────────────────────── +// +// The object-manager entry points. Their STRUCTURES are declared in win.h and +// have been since this package was written, for the reason recorded there; +// these are the calls that take them. +OKW_IMPORT DWORD OKW_API RtlNtStatusToDosError(long); + +} // extern "C"