So I have several questions:
Not really sure if it would work (I'm not much experienced in C) but what about using --passC option of Nim to give "-include <your header file>" to gcc ?
The file is included as a part of a compiler magic when I use the header pragma. I'm not sure if can rely on the success if this really helps, it could break in the next version of a compiler. But there's an option to do define`s which would disable all the conflicting symbols in `windows.h. The risk is that some of the disabled functionality is actually used in the Nim code and that would make it a hard block to use Windows API together with my library NimrayilbNow.
I wasn't aware that it was but I would assume that windows APIs would be used by the standard library on windows.
There's winlean standard library module which I believe uses APIs from windows.h. And my question would be, is this the only module? Or does os module also requires this file? Anyway Nim compiler includes windows.h even in the simplest examples which don't use any of the mentioned modules.
We already generally avoid {.include.} for Win32 API stuff, as winlean mirrors the API (basically a translation of windows.h w/ WIN32_LEAN_AND_MEAN def'd) in general but pulls the necessary functions in through {.dynlib.} instead of through the C header.
There are a few places where we're still pulling in headers even though I don't think we necessarily have to? Really, as far as I can tell, the only thing we need is `GetProcAddress()` & co. and it seems like this could be fixed to the more precise libloaderapi.h anyway instead of windows.h.
Raylib defines functions and types without prefix and simplest form. https://github.com/raysan5/raylib/blob/master/src/raylib.h
Vector3, Matrix, Rectangle, Image, InitWindow, CloseWindow, HideCursor, DrawPixel, LoadImage, etc. C language doesn't have overloading or module like Nim. These names can easily collide with other C libraries if they define functions and types in same way.
Even if Nim can fix windows.h problem, using Raylib with other C libraries that define functions without prefix can cause similar problems. If a Nim project links Raylib and a C libraries that defines LoadImage function, you will get a link error (like LoadImage was redefined) even if two LoadImage C functions have different parameters.
Maybe, Raylib is supposed to be used for making simple game quickly and not supposed to be used with other libraries.
Maybe, Raylib is supposed to be used for making simple game quickly and not supposed to be used with other libraries.
Or maybe not. I'm not as much interested in the metaphysics of the original intent, I just want to make it work.
Here is result of git grep windows.h on latest devel Nim. Some APIs are imported on several places. For example, GetLastError is declared in lib/system/excpt.nim and lib/system/threadlocalstorage.nim. These APIs should be moved to winlean module?
GetCurrentFiber is not used in Nim repository. Result of git grep GetCurrentFiber is only one line. It is exported from winlean module and there might be user code using it. Such unused windows API import should be removed?
Including windows.h can be replaced to other specific header files.
But these header files still declares many functions without prefix.
If raylib add support to multithreading and add a function CreateThread, it is already declared in processthreadsapi.h. raylib already have many Load* functions. If it add a function LoadResource, it is already declared in libloaderapi.h.
So stop including "windows.h" might works with raylib today but I don't know what happens tomorrow. Also, people might forget that we should not use windows.h and someone might create a pull request that contains windows API with header: "<windows.h>" pragma.
compiler/cgen.nim: m.includeHeader("<windows.h>")
lib/cycle.h:#include <windows.h>
lib/pure/dynlib.nim: proc FreeLibrary(lib: HMODULE) {.importc, header: "<windows.h>", stdcall.}
lib/pure/dynlib.nim: importc: "LoadLibraryA", header: "<windows.h>", stdcall.}
lib/pure/dynlib.nim: importc: "GetProcAddress", header: "<windows.h>", stdcall.}
lib/pure/ioselects/ioselectors_select.nim: #include <windows.h>"""
lib/pure/reservedmem.nim: dwFreeType: int32): cint {.header: "<windows.h>", stdcall,
lib/pure/reservedmem.nim: header: "<windows.h>", stdcall, importc: "VirtualAlloc".}
lib/system/atomics.nim: proc cpuRelax* {.importc: "YieldProcessor", header: "<windows.h>".}
lib/system/dyncalls.nim: importcpp: "(void*)GetProcAddress(@)", header: "<windows.h>", stdcall.}
lib/system/dyncalls.nim: importc: "GetProcAddress", header: "<windows.h>", stdcall.}
lib/system/dyncalls.nim: importc: "FreeLibrary", header: "<windows.h>", stdcall.}
lib/system/dyncalls.nim: importc: "LoadLibraryA", header: "<windows.h>", stdcall.}
lib/system/excpt.nim: proc GetLastError(): int32 {.header: "<windows.h>", nodecl.}
lib/system/excpt.nim: header: "<windows.h>", nodecl.}
lib/system/osalloc.nim: header: "<windows.h>", stdcall, importc: "VirtualAlloc".}
lib/system/osalloc.nim: dwFreeType: int32): cint {.header: "<windows.h>", stdcall,
lib/system/syslocks.nim: header: "<windows.h>", final, pure.} = object # CRITICAL_SECTION in WinApi
lib/system/syslocks.nim: SysCond {.importc: "RTL_CONDITION_VARIABLE", header: "<windows.h>".} = object
lib/system/syslocks.nim: header: "<windows.h>".}
lib/system/syslocks.nim: header: "<windows.h>".}
lib/system/syslocks.nim: header: "<windows.h>".}
lib/system/syslocks.nim: header: "<windows.h>".}
lib/system/syslocks.nim: header: "<windows.h>".}
lib/system/threadlocalstorage.nim: importc: "TlsAlloc", stdcall, header: "<windows.h>".}
lib/system/threadlocalstorage.nim: importc: "TlsSetValue", stdcall, header: "<windows.h>".}
lib/system/threadlocalstorage.nim: importc: "TlsGetValue", stdcall, header: "<windows.h>".}
lib/system/threadlocalstorage.nim: importc: "GetLastError", stdcall, header: "<windows.h>".}
lib/system/threadlocalstorage.nim: importc: "SetLastError", stdcall, header: "<windows.h>".}
lib/system/threadlocalstorage.nim: importc: "SetThreadAffinityMask", stdcall, header: "<windows.h>".}
lib/windows/winlean.nim:proc GetCurrentFiber*(): pointer {.stdcall, importc, header: "windows.h".}
lib/windows/winlean.nim: SID_IDENTIFIER_AUTHORITY* {.importc, header: "<windows.h>".} = object
lib/windows/winlean.nim: SID* {.importc, header: "<windows.h>".} = object
tools/trimcc.nim:unistd.h wait.h varargs.h windows.h zlib.h
Thanks everybody for contributing to this discussion! Eventually I think I will leave the NimraylibNow! situation as-is. I really have to control the order in which windows.h is included into the generated C file from Nim. Maybe in a year or two we will be free from the hegemony of windows.h but for now I will endure the suffering.
I made an attempt to get free from windows.h and it failed https://github.com/greenfork/nimraylib_now/pull/54. The problem is that I need to absolutely make sure that windows.h is included only after my C file. There are several #define declarations that control what is included in windows.h, for Raylib necessary ones are here https://github.com/raysan5/raylib/issues/1217#issuecomment-618428626. So if windows.h is included before my C file with all these define declarations, I'm in no luck. And since there's no specification on the order of inclusion, if I pursue this way, it may as well break in the next Nim release or just by passing --threads:on flag (as practice shows --threads:on can also alter the order of includes https://github.com/greenfork/nimraylib_now/issues/50).
On the point of removing windows.h from the Nim compiler/standard lib:
GetLastError is declared in lib/system/excpt.nim and lib/system/threadlocalstorage.nim. These APIs should be moved to winlean module?
I would avoid doing this. winlean is still pretty large (considering it is a transliteration of windows.h) There are places where it is more prudent to bind upfront.
Most of these can just be bound directly to their associated DLL export to like we do for most of the winlean procs, with a few outliers.
proc cpuRelax* {.importc: "YieldProcessor", header: "<windows.h>".}
YieldProcessor could probably be changed to the _mm_pause() intrinsic in emmintrin.h.
SysLock {.importc: "CRITICAL_SECTION",
header: "<windows.h>", final, pure.} = object # CRITICAL_SECTION in WinApi
DebugInfo: pointer
LockCount: int32
RecursionCount: int32
OwningThread: int
LockSemaphore: int
SpinCount: int
SysCond {.importc: "RTL_CONDITION_VARIABLE", header: "<windows.h>".} = object
thePtr {.importc: "ptr".} : Handle
We already seem binary-compatible with the definitions as they appear in windows.h, our links to the header could be removed.
In the compiler:
if m.config.target.targetOS == osWindows and
m.config.globalOptions * {optGenGuiApp, optGenDynLib} != {}:
m.includeHeader("<windows.h>")
# ...
if m.config.target.targetOS == osWindows and
m.config.globalOptions * {optGenGuiApp, optGenDynLib} != {}:
if optGenGuiApp in m.config.globalOptions:
const nimMain = WinNimMain
appcg(m, m.s[cfsProcs], nimMain,
[m.g.mainModInit, initStackBottomCall, m.labels, preMainCode, m.config.nimMainPrefix])
else:
const nimMain = WinNimDllMain
appcg(m, m.s[cfsProcs], nimMain,
[m.g.mainModInit, initStackBottomCall, m.labels, preMainCode, m.config.nimMainPrefix])
This one is trickier, we import windows.h if we're --app:gui/lib due to the alternate entry point signature, i.e.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow);
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason LPVOID lpReserved);
However, we could probably forego that by specifying the subsystem (e.g. -mwindows on MinGW, /subsystem:windows) in the compiler options instead (and also using void mainCRTStartup() as our crt0-side entry point), and then using the standard int main() user-side entry point instead. The only parameter for these entry points that isn't deprecated and that you can't alternatively get from any of the DLL params is nCmdShow which I don't think we ever use anyway. The specifics of DllMain() seem trickier: see here for more info.
While this also runs into the fact that the CRT imports `windows.h`, the runtime is managed through a static lib anyway, so it's kinda neither here nor there.
All in all, this seems doable - with some effort.
Actually, silly me: you can define wWinMain() without needing windows.h like this:
int __stdcall wWinMain(void* w, void* x, void* y, int z) {}