Will there be cross-compile support for Windows 7?

Hello!
I discovered Crystal some time ago and tried using it on my Linux notebook. This works fine, so I thought with the arrival of version 1.0, there should be a way using it on Windows (at least for hobby purposes).

I tried both suggested ways: either cross-compiling from scratch on a Linux host, or using the artifacts from Github CI with a precompiled crystal.exe (and libraries). Both ways showed me the same error message on executing any cross-compiled .exe file:

The procedure entry point “GetCurrentThreadStackLimits” could not be located in the dynamic link library KERNEL32.dll

The precompiled crystal.exe itself prints out this message and stops, so this is no compilation problem on my side, but an executing problem.

From the porting guide I tried compiling the helloworld program, which gives no errors on compiling or linking but on running the resulting .exe file. So I assume the cross-compiled .obj file is just not working on my Windows 7, which might originate in LLVM or some settings for it when using the switches -cross-compile --target x86_64-pc-windows-msvc.

On Windows 10 both the precompiled crystal.exe as well as my helloworld.exe (linked on Windows 7) execute just fine.

FWIW Windows 7 is end of life and is no longer supported so I would think native cross compiling support would not be something that is high on the priority list.

Well yes, clearly that function is being used and clearly it’s not available in Windows 7.

You could try your luck replacing such functions until it works. Nobody else needed to do that and nobody will in the near future.

Perhaps GetCurrentThreadStackLimits function (processthreadsapi.h) - Win32 apps | Microsoft Docs → NtCurrentTeb function (winnt.h) - Win32 apps | Microsoft Docs

Thanks for the hint. So now I know that GetCurrentThreadStackLimits is only available on Windows 8 or newer. Is there some kind of list of all these functions? Probably not and I would either have to look in all source files, or try/err until it works …

What is the lowest Windows version the LibC requires?

I don’t think anyone has ever checked that. Windows support is not finalized yet, so the focus is definitely on getting the basics running at least on current version. Backporting to older Windows versions is of course possible, but not a priority.

Off the top of my head, I recall the time implementation uses GetSystemTimePreciseAsFileTime which is available at Windows 8+. But I also left a to-do for older versions.

Apart from that and the stack limits, I wouldn’t expect there are much more functions not supported in Windows 7.

1 Like

Not only crystal.exe, but also compiled crystal programs won’t run on Win7 due to these two functions. I’ve created an issue about it some time ago - Crystal programs doesn't work on Windows 7 due to GetCurrentThreadStackLimits · Issue #10085 · crystal-lang/crystal · GitHub . There is a link to Stackoverflow on how to replace GetCurrentThreadStackLimits on Win7: https://stackoverflow.com/a/52404641.

PRs are welcome! Shouldn’t be hard to do, even without win32 experience.

Finally I have managed compiling LLVM (version 10 had some bug which prevents compiling on Windows, but LLVM11 works) and then Crystal successfully. This was more effort than implementing the patch …

The compiler compiles itself on Windows 7, but my changes do not distinguish between Win7 and Win8+ yet. I wanted to include version checks, using Version Helper Functions, but cl keeps telling me unresolved external symbol for IsWindows8OrGreater, despite the fact that this is included in VersionHelpers.h in Windows SDK.

Also other functions cannot be found, whereas the mentionend NtCurrentTeb (and others) are found. I am at a loss how to tell which functions from which libraries/include files the linker can see. Where is this defined in Crystal source? Somewhere must be a definition of “include this DLL and that DLL, and then try to find all referenced functions” …

I could implement the version helper functions directly in Crystal, but then would need to reference other things like GetVersionExW, which also might or might not be found. (I did not test this yet.)

(Sorry for possibly trivial questions, but I do not develop every day with external C functions and I might just need a tiny step in the right direction.)

1 Like

Features from header files are not available in the binary libraries. Everything provided in a header file needs to be implemented in Crystal. For reference, the library bindings in Crystal (in src/lib_c/x86_64-windows-msvc) are essentially the equivalents of C header files. That’s where the library functions are defined so that Crystal knows about them.
So the version helper functions should be implemented in src/lib_c/x86_64-windows-msvc/version_helpers.cr.
GetVersionExW should be a symbol in Kernel32.lib, you just need to define the lib fun (in sysinfoapi.h).

Actually, choosing which library function to call based on runtime properties isn’t trivial. Because when you do it like this:

if LibC.IsWindows8OrGreater
  LibC.GetSystemTimePreciseAsFileTime
else
  LibC.GetSystemTimeAsFileTime
end

The binary is still linked against both functions which fails for Windows 7.

This can be solved by a compile time flag to distinguish between windows 7- and windows 8+ APIs.

# compile with `crystal build -Dwindows7` to enable the latter path
{% unless flag?("windows7") %}
  LibC.GetSystemTimePreciseAsFileTime
{% else %}
  LibC.GetSystemTimeAsFileTime
{% end %}

That could be used for getting started, but it’s not a great because you’d need to build two different windows binaries.

A more sophisticated solution is to look up the potentially unavailable symbols dynamically at runtime.
But that’s another step. For now, you could just use a compile time flag to get the basic implementation going.

1 Like

I thought so, but on the other hand using NtCurrentTeb() compiles fine with referencing as library binding, despite it is an inline function in winnt.h. I am a bit confused here.

Thanks. I tested some similar function definitions yesterday, but they did not work that way. (Also it was too late for coding.) I discovered today these were randomly chosen from User32.lib, which seem to not be in the library search path. This has nothing to do with this issue, but I am curious if User32.lib would need to be defined somewhere? Or some functions are found and others … not?

Ah thanks for the hint. I did not consider this yesterday and would have clearly bumped into this problem as soon as the version check works. So it’s no use of reimplementing the version helper functions now …

My Windows7-only compilation already works, I just wanted to implement the version check the best way possible. Is it ok using the simple check via compiler flag and then file a pull request on the issue?

Huh, that seems… unlikely. Maybe some lib has this symbol though for whatever reason. It’s hard to say without seeing the thing happen. It looks a bit weird, but I’m sure there’s a reasonable explanation. But there’s no way this could miraculously appear out of a header file in Crysta land.

Yeah it seems you would need to add information for the linker to know it has to link User32.lib. It seems, until now we don’t use any function from that library.
An example for this would be in ntsecapi.cr which defines a function RtlGenRandom provided by Advapi32.dll. That’s indicated by the linker annotation @[Link("advapi32")].

Yes, sure. Having the Windows 7 implementation done is already a good progress. Even if it temporarily means we can’t have a single executable.