Is this what is meant when people say that Windows doesn't have stable APIs? I've not programmed windows/desktop software and have often been confused as to why if you're writing an assembly program, you have to use at least a little C if you want to make Windows system calls[1]. The compiler must be compiling the call into "something" so can't you just write that "something" directly in assembly?
[1]: The classic example being Chris Sawyer writing nearly all of Rollercoaster Tycoon in x86 assembly but requiring just enough C for the system calls.
> have often been confused as to why if you're writing an assembly program, you have to use at least a little C if you want to make Windows system calls
This isn’t true. The Win32 API is C-based. You can call a C-based API from hand-written assembly just fine. You just have to understand the ABI calling convention so you know how to do it. You can also write some C code to call the API and then get the C compiler to output assembly and then you can copy/paste the relevant portion into your hand-written assembly
> The classic example being Chris Sawyer writing nearly all of Rollercoaster Tycoon in x86 assembly but requiring just enough C for the system calls.
I don’t think he had to do it that way. Maybe he just decided that was the path of least resistance for him.
Ultimately, he has to have assembly call C at some point, since his own assembly code has to call his C function which calls the Win32 API (technically not system calls, as other commenters have pointed out). Maybe, given the Win32 API involves some rather complex data structures, numerous constant definitions, various helper macros, etc, he may have just found it easier to use some of that stuff from C instead of trying to replicate the complexity in hand-written assembly, or having to translate all the C header definitions he needed into corresponding assembler macros
--- hello_world.asm ---
bits 64
default rel
segment .data
msg db "Hello world!", 0xd, 0xa, 0
segment .text
global main
extern ExitProcess
extern printf
main:
push rbp
mov rbp, rsp
sub rsp, 32
lea rcx, [msg]
call printf
xor rax, rax
call ExitProcess
---nasm -f win64 -o hello_world.obj hello_world.asm
link hello_world.obj /subsystem:console /entry:main /out:hello_world_basic.exe
You don't need to use C if you want to call Win32 functions, although the calling convention is based on C. The stable API is not the user-kernel boundary but instead a set of DLLs (kernel32, user32, gdi32, etc.), which makes sense if you look at the history: Windows started out as a GUI on top of DOS. The actual system call mechanism is very different between the DOS-based lineage and the NT-based ones, but the Win32 API remains nearly the same.
Windows system call numbers aren't stable. It is done through their system libraries. So rather than invoking a syscall number as you do on Linux, if you want your code to be portable you import the DLL with that functionality and call the function. You can do this all from ASM without needing C, though the system libraries were likely written in a higher level language like C. You can also invoke system calls by number directly, it is just liable to not work on another version of Windows.
Windows is legendary for having the most stable APIs of any major OS. Windows software compiled in 1997 will still run on it.
Unlike Linux, Windows syscalls aren't documented and their IDs constantly change[1]. Instead, you're supposed to call wrapper functions provided by ntdll.dll. That said, most programs use even higher level functions from kernel32.dll and friends.
You don't need C/C++ to call a function from a DLL, but it makes things easier, especially for more complex APIs like DirectX.
[1] - https://j00ru.vexillium.org/syscalls/nt/64/