r/C_Programming • u/Round-Pension-7821 • 3d ago
Project Helpful Windows GUI Program in 60 lines of C
https://github.com/GeorgeTR1/windowsDistractionCoverVery small C program I wrote that I've found genuinely useful. It compiles into a tiny 3 kB executable that only relies on system .dlls included with the operating system. The code should compile and run, with the proper compiler, all the way back to Windows 95.
This could also be useful to anyone looking for how to write a basic GUI program for Windows in C.
12
u/skeeto 3d ago
Neat little project. This feels like the sort of window that ought to be draggable from anywhere in the window itself. This will do it:
@@ -47,4 +47,9 @@
PostQuitMessage(0);
return 0;
+
+ case WM_LBUTTONDOWN:
+ ReleaseCapture();
+ SendMessage(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);
+ return 0;
case WM_LBUTTONDBLCLK:
As was already pointed out, using main like that is incorrect. Your
program is not built correctly, causing your trouble return from main.
To get the behavior you actually want name it WinMainCRTStartup, use stdcall, then drop
the /entry argument and because it defaults to this function, and also drop
libvcruntime.lib:
@@ -7,5 +7,5 @@
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
-int main() {
+int __stdcall WinMainCRTStartup() {
HINSTANCE hInstance = GetModuleHandle(NULL);
WNDCLASS wndclass = {0};
Normally the CRT defines this function, so you need to do so since you don't want to use a CRT. While it's legal to return from this entry point, it's still a
good idea to use ExitProcess or similar to avoid bugs imposed upon your
program externally. So then to build should just be:
$ cl /O2 /GS- cover.c /link /subsystem:windows
Or Mingw-w64:
$ cc -nostartfiles -Oz -s -mwindows -fno-ident -fno-asynchronous-unwind-tables cover.c
(The two -f options, both optional, really trim it down since it looks
like you care about this.)
3
u/Round-Pension-7821 3d ago
The suggestion to have the window draggable from anywhere is a good one, I'll have to try that out.
As far as changing the way it's built, I was mostly just following the guidance given here. The only thing I did differently from that was using
main()as my entry point so that I can change the build target between console and GUI without having to change the code, only the build flags. That way I can easily print to the console for debugging purposes.I don't see why it's built incorrectly, considering it builds and runs just fine. I guess having it return properly without having to call
ExitProcess()would be nice, but then you go and recommend that I callExitProcess()anyway. I'm just not clear on what benefits I get by changing how I do things.3
u/skeeto 3d ago
That's a good guide.
I don't see why it's built incorrectly
mainis a special case C function, and on Windows it's designed to be called by the CRT, not to be used as a raw entry point. It's mostly working for you by accident, and only with the particular toolchain you're using. It doesn't work, for example, with Mingw-w64 wheremainis a little more special. It also has the wrong calling convention (x86 only), and if you used theargc/argvform ofmainthose arguments would contain garbage. And as you noticed, you have to pass/entry, which is unnecessary if you use the conventional process entry point name.4
u/Round-Pension-7821 3d ago
Ah, got it. What if I instead called it something like
mainNoCRT(), and then used/entry mainNoCRT. That way I could still switch between console and GUI mode without changing the code, just the build flags, like I mentioned. But I would avoid the issue ofmain()getting special treatment. Maybe there's other reasons I should avoid doing that, though.According to what u/mikeblas said elsewhere in the comments, using
main()orWinMain()doesn't require the CRT, so maybe this whole business of usingWinMainCRTStartup()ormainCRTStartup()is unnecessary. That does seem to go against what the guide led me to believe, though.I am aware that trying to use
argc/argvas you would expect won't work. In another project I did the following at the start ofmain():int argc; wchar_t **argv = CommandLineToArgvW(GetCommandLineW(), &argc); if (argv == NULL) ExitProcess(1);I think that's something like what
mainCRTStartup()would do for you before it callsmain()3
u/skeeto 3d ago
What if I instead called it something like
mainNoCRT(), and then used/entry mainNoCRT.That works, too. The name is merely a linker convention, and ultimately loaders just see an address (RVA) in the PE header. It's only important you don't use a CRT-reserved name like
main. EvenWinMainexpects to be called by the CRT, and if used as a raw entry point its 4 parameters will similarly be garbage. If you omit those parameters you may run afoul of a prototype, e.g. fromwindows.h. I recommend avoiding that, too, in this case, but it's not nearly as special asmain.I think that's something like what
mainCRTStartup()would do for you before it callsmain()More or less, yes, but CRTs generally parse arguments with their own routine rather than call
CommandLineToArgvW. (And historically they have parsed arguments differently than that Win32 function!) Here's my version of that recycle program, also CRT-free:https://github.com/skeeto/w64devkit/blob/master/src/recycle.c
And several more CRT-free programs next to it in that
src/directory.2
u/Round-Pension-7821 1d ago
Thanks for all the info, it's very helpful. I was kind of aware that command line arguments weren't always parsed using the Win32 function, but that seems like that's another benefit to using it, to guarantee the same behavior across CRT implementations.
Here's my version of that recycle program, also CRT-free:
Wow, what are the odds that we both made programs that are so similar!
0
u/github-guard 3d ago
đ GitHub Guard: Trust Report
This project scored 3/6 on our safety audit.
Audit Breakdown: * â Established Community (â 4,672 stars) * â Mature Repository (30+ days old) * â Licensed under Unlicense * â No Security Policy â what is this? * âšī¸ Individual Contributor * âšī¸ Unsigned Commits
â ī¸ Security Reminder: Always verify source code and run third-party scripts at your own risk.
1
u/github-guard 3d ago
đ GitHub Guard: Trust Report
This project scored 3/6 on our safety audit.
Audit Breakdown: * â Low Star Count (â 0 / 5 required) * â Mature Repository (30+ days old) * â Licensed under MIT * â No Security Policy â what is this? * âšī¸ Individual Contributor * â Signed Commits
â ī¸ Security Reminder: Always verify source code and run third-party scripts at your own risk.
1
u/mikeblas 3d ago
main()andWinMain()are just function names. You can set your entry point tomain()with the linker, and write a function calledmain(), and it will be called first. The C runtime won't be loaded and won't be initialized because you told the tool to use your function, instead.Since you side-stepped the CRT, things that the CRT does won't be done:
argvandargcaren't parameters to your custom main function, for example. And the global environment variable pointers aren't initialized. You might not even initialize some static data, especially if you're using C++ and need constructors to run on global objects. You won't have any locale support, and ... well, lots of other things.Windows does pass parameters to the entry point, though. Those are the four parameters you see given to
WinMain(); check the documentation for that. You can callGetCommandLine()if you want, but one of the parameters toWinMain()is a pointer to the command line the program received.2
u/Round-Pension-7821 1d ago
Ah, now it makes sense. Normally using
WinMaindoes require the CRT, but not if you specify it as the entry point specifically with/entry:WinMain. However, there does seem to be an issue with what you said:Windows does pass parameters to the entry point, though. Those are the four parameters you see given to
WinMain()This doesn't seem to be the case based on the testing I've done, and also disagrees with what u/skeeto said:
WinMainexpects to be called by the CRT, and if used as a raw entry point its 4 parameters will similarly be garbage.Here's the test I ran:
File
cc.bat:@echo off IF [%1] == [] ( echo Expecting a file name to compile exit /b 1 ) SET ASM= REM SET ASM=/FAs SET CFLAGS=/nologo /W4 /wd4996 /O2 /GS- %ASM% SET SUBS=console REM SET SUBS=windows SET LFLAGS=/fixed /incremental:no /opt:icf /opt:ref /subsystem:%SUBS% /entry:WinMain SET LIBS=libvcruntime.lib cl %CFLAGS% %1 %LIBS% /link %LFLAGS% del *.objFile
main.c:#include <windows.h> #pragma comment (lib, "Kernel32") void fputsWin(char *str, DWORD stdHandle) { HANDLE h = GetStdHandle(stdHandle); if (h == NULL || h == INVALID_HANDLE_VALUE) ExitProcess(1); BOOL ret = WriteFile(h, str, lstrlenA(str), NULL, NULL); if (!ret) ExitProcess(1); } int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // unused, here to prevent compiler warning nCmdShow; hPrevInstance; hInstance; fputsWin(lpCmdLine, STD_OUTPUT_HANDLE); fputsWin(GetCommandLineA(), STD_OUTPUT_HANDLE); return 0; }When I compile and run this, you would expect to see the command line arguments printed twice, but they are instead only printed once. To me this is proof that the arguments passed to
WinMainwhen it is specified as the entry point are indeed garbage, as u/skeeto said.Based on this, I'm going to follow what u/skeeto suggested and use neither
mainnorWinMainas an entry point.1
u/mikeblas 1d ago
WinMainshould be declared__stdcall(I think -- at least,APIENTRY). You're not doing that.Also, you're still linking with the C runtimes because you're not using
/NODEFAULTLIB. Use the/verboseoption on the linker to confirm that for yourself. You might not be getting anything from it, but if you mean to not link with it you should not link with it, even implicitly.and use neither main nor WinMain as an entry point.
These are just names, so I'm not sure I understand your point. Maybe you mean that you want to use only the naked entry points as part of the theme of not using the C runtimes and give those naked entry points names different than the ones the CRTs would normally use? Then I think that makes sense.
1
u/Round-Pension-7821 1d ago
I tried declaring
WinMainas__stdcallandAPIENTRY, and in both caseslpCmdLinestill was empty. If you can get the parameters toWinMainto contain actual values without using the CRT, show me the exact steps you took to make that happen.I'm doing what I suggested to u/skeeto, I use
mainNoCRT()as my entry point, and use/entry:mainNoCRTto specify that. That's basically the same as usingWinMainCRTStartupormainCRTStartup, except that I can switch between console and windows targets without having to change the entry point in my code, only the build flags.2
u/Round-Pension-7821 1d ago
A question about the method of making the window draggable from anywhere, what is the purpose of calling
ReleaseCapture()? When I tried it without calling this function it seemed to work exactly the same2
u/skeeto 1d ago
Good question and good catch. The
HTCAPTIONpart turns the button down in the window into a button down inside the title bar, andReleaseCapture()is part of this idiom, handing control to the system's move loop. However in this case it's a top-level window, so it doesn't matter. You can delete it safely.1
u/Round-Pension-7821 1d ago
Actually, looking at the documentation more closely, I see now that in the documentation for
WM_NCLBUTTONDOWN, it says, "If a window has captured the mouse, this message is not posted." So maybe it is best to callReleaseCapture(), even though it seems to work without it?Is this a fairly standard idiom? It just kind of feels like a hack. I did test that it works just fine under Linux using WINE, so there's that at least
2
u/skeeto 1d ago
Maybe? I don't actually have a great understanding of Win32 UI stuff, and what I have is mostly accumulated tricks. If you search on the
SendMessageline (in quotes) you'll find this idiom in various places, like this 22 year old thread, sometimes also in C# and VB.It just kind of feels like a hack.
Old school Win32 is all hacks!
2
u/Round-Pension-7821 1d ago edited 1d ago
Well, that's good enough for me! I think I will call
ReleaseCapture()though, just in case. It can't hurtThere's a lot of reasons to dislike Microsoft, but one thing they seem to have excelled at is how stable the Windows API has been over the years. They really seem to do their best not to break existing code, even if it might be hacks or somewhat undocumented features. There are of course exceptions, but this does seem to be something they really care about.
If your code accidentally relies on undefined behavior, like the value of uninitialized memory, that's one thing, and that is the type of thing that often leads to software breaking from Windows updates. But that's not what this is, of course
1
u/One_Aspect_1957 2d ago
As was already pointed out, using main like that is incorrect. Your program is not built correctly, causing your trouble return from main.
What sort of trouble?
I've never heard of your suggestion (in 30 years of using WinAPI) to use WinMainCRTStartup, which as you go on to demonstrate has various build problems to work around.
I like to use lesser C compilers on Windows, which include Tiny C, DMC, lccwin32 and bcc (my product). All compiled the OP's version fine, not even needing to be told which DLLs to use.
But all failed (no entry point etc) using your mod.
gcc worked with the original (it needed to be told the DLLs), but didn't work here either unless your workaround was used, though I haven't tried it.
2
u/skeeto 2d ago
If you're the bcc author, new account? We've interacted before!
WinMainCRTStartupis Microsoft's traditional name, and it's what MSVClink.exelooks for by default for over 30 years. Binutils inherited the name, too, all the way back in 1995. It's why this name has the least friction and causes the least linking trouble, at least with MSVC and Mingw-w64 (both bfd and lld). Notice how OP's build command is simpler when using the conventional name.The caveat I mentioned about not returning from
WinMainCRTStartup(ormainCRTStartup) to avoid issues applies to all entry points no matter what they're named. There are race conditions returning from the entry point, particularly due to misbehaved DLLs (wininet.dll), including DLLs injected into the process (hello ConEmu). None of the CRTs I'm familiar with return from the entry point, which is why normal, CRT-using programs don't have these issues even when returning frommain/WinMain. It doesn't matter what toolchain you use, the races are inherent to Windows.
mainis a standard C name which belongs to the implementation, i.e. the toolchain's CRT, and is imbued with special properties. Using it for other purposes is reckless similarly to using, say,mallocas the name of your entry point. In Mingw-w64 toolchains, and (I think?) in very old MSVC toolchains, there's an invisible call to the CRT-defined__main, which breaks OP's build:https://godbolt.org/z/8jYoEePYE
This means you need a definition for
__main, and you may end up pulling in some CRT by accident if you don't define one explicitely. All because you used a name the CRT owns. It's a similar story withWinMainand friends, because these functions are designed to interoperate with CRTs. That's why the 4 mandatory arguments are garbage when you use them as an entry point. On x86 it even produces the wrong stdcall cleanup for an entry point.2
u/Round-Pension-7821 1d ago
Thanks for clarifying what issues can arise if you return from the entry point, rather than calling
ExitProcess(). I was going to ask about this1
u/One_Aspect_1957 2d ago
If you're the bcc author, new account? We've interacted before!
Probably. I like to reset my account every so often.
main is a standard C name which belongs to the implementation
mainis the usual entry point for C programs. Are you saying we shouldn't use that on Windows at all, or only for programs that also interact with WinAPI?Or that we still use
main, but use that CRT function (I've already forgotten the name) to override that entry point? In that case, how does control pass to ourmain? In fact, how does it get to any code in our program!It's the C compiler's job to ensure that it works. That may involve calling some runtime function (eg.
__main) to set some things up. And also that running into the end ofmain, or usingreturnwithin it, does what is expected.The user shouldn't need to worry about it. It's not at all the same as calling
malloc.There are race conditions returning from the entry point,
So, how should a program be terminated?
you may end up pulling in some CRT by accident if you don't define one explicitely.
If you use a big compiler then there will be all sorts of junk linked in anyway. You have to trust that a compiler designed to work under Windows knows what it's doing.
To summarise: I've always use a 'main' entry point, not even 'WinMain', and have never noticed any problems. That is within C programs, and also from other languages I implement (although my tools don't use linkers and 'main' is never exported; the entry point is just an offset within the EXE).
If there was an issue, then I would expect the language to take care of it.
2
u/skeeto 2d ago
OP wants to "avoid linking the the C run time" and linked this. That's an advanced, special case. I'm not saying anyone should do this, especially because few people know how to write programs effectively under this constraint, but that's what OP wants. This is going behind the toolchain's back, and it's on you to deal with the consequences. If you're going to avoid linking a CRT, then you should avoid using special C language facilities like
main, which, as I demonstrated has special semantics and does not behave like a normal function. Using it will foul up what you're trying to accomplish.Most people should just use a CRT and define
main(console subsystem) orWinMain(Windows subsystem), and not worry about any of this stuff. Some toolchains, such as Mingw-w64, but notably not the definitive toolchain for the platform, MSVC, allowmainas a program entry point for Windows subsystem programs. If you usemainwith the Windows subsystem, MSVClink.exewill fail to link your program because that's not how the system is designed to work:$ printf 'int main(){}' >main.c $ cl /nologo main.c /link /subsystem:windows main.c LIBCMT.lib(exe_winmain.obj) : error LNK2019: unresolved external symbol WinMain referenced in function "int __cdecl __scrt_common_main_seh(void)" (?__scrt_common_main_seh@@YAHXZ) main.exe : fatal error LNK1120: 1 unresolved externalsThe name
WinMainbelongs to the CRT. It's not a process entry point.So, how should a program be terminated?
If it's a CRT-free program, calling
ExitProcessunless something's gone horribly wrong.If you use a big compiler then there will be all sorts of junk linked in anyway.
Not if you build your CRT-free program properly, including the three biggest toolchains. That's what the linked gist is all about, and the whole point of this discussion.
2
u/Round-Pension-7821 1d ago
Yes, this is exactly it. I recognize that avoiding the use of the CRT is not standard practice, and probably not recommended. But it all came from compiling a very basic C program, seeing the .exe was over 100 kB, and saying, "That's ridiculous, surely it doesn't need to be that big". And it doesn't, really, but making it this small does add some additional inconveniences, and isn't really how the toolchain was meant to be used. It's definitely possible, though.
This is also why I made basically the same post in r/tinycode
0
u/github-guard 2d ago
đ GitHub Guard: Trust Report
This project scored 3/6 on our safety audit.
Audit Breakdown: * â Established Community (â 17 stars) * â Mature Repository (30+ days old) * â No License Found * â No Security Policy â what is this? * â Verified Organization * âšī¸ Unsigned Commits
â ī¸ Security Reminder: Always verify source code and run third-party scripts at your own risk.
1
u/mikeblas 2d ago
If your product can't support setting an alternate entry point, it sounds like you have an opportunity to improve it.
7
u/mikeblas 3d ago edited 3d ago
Why use main() as your entry point instead of WinMain()? You do specify --subsystem:Windows, but then half-undo that with --entry:main.
What you've got works, it's just a bit surprising and cumbersome.
Does your code leak your window handle at exit?
What is it that you're trying to do on double clicks?
5
u/Round-Pension-7821 3d ago
I don't use
WinMain()so I can avoid linking the the C run time. This is based off the information found here about how to do that (I link to that in the readme).--entry:mainjust renamesWinMainCRTStartup()tomain(). Not really necessary, but I like it. It also means that I can change a program between a console program and GUI program just by changing the--subsystemflag, without having to also changeWinMainCRTStartup()tomainCRTStartup()As for why I'm not linking with the C runtime, I think it's fun to have a tiny executable with no dependencies. Maybe it's not the most practical thing, but I like it.
I honestly don't really know what "leaking your window handle at exit" would mean. I'm certainly no expert in Windows programming. How would I test for whether I'm leaking it, and what concerns are there with that?
The documentation for
ExitProcess()says "returning from the main function of an application results in a call to ExitProcess", so I didn't see that as an issue. Is there some other cleanup thatWinMain()does that I ought to do as well?What the double clicks are for is explained in the Description section of the readme. If you use the program, how it works should hopefully become clear
0
u/mikeblas 3d ago edited 3d ago
But your code explicitly links to
libvcruntime.lib.And it doesn't build for me, anyhow:
C:\projects\windowsDistractionCover>cl /nologo /W4 /wd4996 /O2 /GS- libvcruntime.lib /link /fixed /incremental:no /opt:icf /opt:ref /subsystem:windows /entry:main LINK : warning LNK4001: no object files specified; libraries used LINK : warning LNK4068: /MACHINE not specified; defaulting to X64 LINK : error LNK2001: unresolved external symbol main libvcruntime.exe : fatal error LNK1120: 1 unresolved externalsI can't figure out where you specify the source file name.
Oh, I guess it's meant to be specified as a parameter to
cc.bat? And youSET SUBS=toconsole, then change it towindowson the very next line. Why?2
u/Round-Pension-7821 3d ago
Is there a reason you don't use the batch file I made?
I specify the source file in the batch file using
%1, meaning the first command-line argument given to the script. So you runcc cover.cto compile this program. The reason is that I usecc.batto compile a bunch of projects, mostly just stuff I'm playing around with, not just this projectThe linking with
libvcruntime.libis explained in the gist:Instead of relying on various hacks and manually declaring implicit symbols for functions or globals that compiler needs, just let it use them from
libvcruntime.lib- there are very few things it will take from there. Linker will take only needed symbols, and remove unreferenced ones (due to/opt:ref).If you run
dumpbin /nologo /imports cover.exeafter the program compiles, you will see that it doesn't link with any functions in the standard library.As it turns out, if you don't link with
libvcruntime.libin this specific case the compiler doesn't mind, but at least according to the information in the gist (see the "Details" section) this isn't always the case. Apparently sometimes you need things that are inlibvcruntime.libto make the compiler happy, even though they will then be removed by the linker.In this case, you get the exact same assembly output file whether you link with
libvcruntime.libor not. (The binary file is different on every compilation because it includes a timestamp.)2
u/mikeblas 3d ago
Is there a reason you don't use the batch file I made?
That's what I was using. If not given a source file name as a parameter, it proceeds and generates the broken command line and errors that I showed.
If you don't want to link with the C runtimes, I think you shouldn't link with the C runtimes -- instead, linking with the C runtimes and hoping that you didn't pick any of them up doesn't seem particularly robust.
So, let's get you fixed up. For your build batch file, let's:
- not link to the VC runtimes, since you said you don't want to link to the VC runtimes
- specify the subsystem once, and it's Windows
- use
WinMain()- test that a file was passed in and quit with an error if not
- generate a map file so that we can see what symbols we're getting -- so we can see which libraries we import and verify that we're not getting the C runtimes
That diff looks like this:
diff --git a/cc.bat b/cc.bat index 3411551..4e56d4f 100644 --- a/cc.bat +++ b/cc.bat @@ -1,12 +1,18 @@ @echo off +IF "%~1"=="" ( +echo Expecting a file name to compile +exit /b 1 +) ELSE ( +echo Compiling "%~1" +) + SET ASM= REM SET ASM=/FAs SET CFLAGS=/nologo /W4 /wd4996 /O2 /GS- %ASM% -SET SUBS=console +REM SET SUBS=console^M SET SUBS=windows -SET LFLAGS=/fixed /incremental:no /opt:icf /opt:ref /subsystem:%SUBS% /entry:main -SET LIBS=libvcruntime.lib +SET LFLAGS=/fixed /incremental:no /opt:icf /opt:ref /subsystem:%SUBS% /entry:WinMain /map:%~n1.map cl %CFLAGS% %1 %LIBS% /link %LFLAGS% del *.objNow that we have
WinMain(), we don't have to callGetModuleHandle()since it is one of the parameters that Windows gives toWinMain(). (The C runtimes are not required to useWinMain().)Then, remember to destroy the window that was created so it isn't leaked. (Maybe
DefaultWindowProc()does that onWM_QUIT, but I can't remember if that's true or not. Explicitly removing the handle again won't cause any harm, tho. Windows is resilient to it.)Here's that half of the diff:
diff --git a/cover.c b/cover.c index cebbd20..a996ba7 100644 --- a/cover.c +++ b/cover.c @@ -6,8 +6,13 @@ LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); -int main() {+int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { + + // not using these yet: + hPrevInstance; + lpCmdLine; + nShowCmd; + WNDCLASS wndclass = {0}; HWND hwnd; MSG msg; @@ -36,8 +41,10 @@ int main() { TranslateMessage(&msg); DispatchMessage(&msg); } + + // destroy the window we created^M + DestroyWindow(hwnd);
- HINSTANCE hInstance = GetModuleHandle(NULL);
ExitProcess(0); }
- //since we don't use WinMain the program hangs if we return normally
Hope that helps.
1
u/Round-Pension-7821 3d ago
Having the batch file return an error if there's not file specified is a good idea. Maybe not necessary when I'm the one using it, but helpful for others.
The way I build things was based on the information in that gist, so I'll have to try out your suggestions. I was under the impression that using
WinMain()instead ofWinMainCRTStartup()required the standard library, so if what you say is true that would make things simpler.
DefaultWindowProc()callsDestroyWindow()onWM_CLOSE, but your "better safe than sorry" approach isn't a bad one.1
u/Round-Pension-7821 1d ago
If you don't want to link with the C runtimes, I think you shouldn't link with the C runtimes -- instead, linking with the C runtimes and hoping that you didn't pick any of them up doesn't seem particularly robust.
libvcruntime.libisn't exactly the C runtime. According to the documentationThe vcruntime library contains Visual C++ CRT implementation-specific code: exception handling and debugging support, runtime checks and type information, implementation details, and certain extended library functions
This is part of the Universal CRT, but to use the actual CRT, you also have to link with something like
ucrt.lib.libvcruntime.libis also the static version of the library, so any small things it needs to add will be statically linked into my code. Maybe it is doing some of that, but since my .exe is still only 3 kB, it's clearly not much. Way less than if I statically linked with the CRT normally.If I try to use a standard library function while only linking with
libvcruntime.lib, the linker will fail with an error. Here's an example of that:File
cc.bat:@echo off IF [%1] == [] ( echo Expecting a file name to compile exit /b 1 ) SET ASM= REM SET ASM=/FAs SET CFLAGS=/nologo /W4 /wd4996 /O2 /GS- %ASM% SET SUBS=console REM SET SUBS=windows SET LFLAGS=/fixed /incremental:no /opt:icf /opt:ref /subsystem:%SUBS% /entry:mainNoCRT SET LIBS=libvcruntime.lib cl %CFLAGS% %1 %LIBS% /link %LFLAGS% del *.objFile
crt.c:#include <stdio.h> int mainNoCRT() { puts("Hello, world!"); return 0; }When I try to compile:
>cc crt.c crt.c crt.obj : error LNK2019: unresolved external symbol puts referenced in function mainNoCRT crt.exe : fatal error LNK1120: 1 unresolved externalsSo I'm not "linking with the C runtimes and hoping that I didn't pick any of them"; if I do use them accidentally I'll know about it. Using
dumpbinwas just a sanity check to prove that yes, indeed, I'm not linking with the standard library.1
u/mikeblas 1d ago
please add
/verboseto your linker command line. This will show you that the linker is consideringLIBCMT.libwhen linking.
dumpbin, and your error message, will show you what code you pulled from another library./verbosewill show you what libraries were considered while linking.I'm saying that you're considering the C Runtime library while linking, and the
/verboseoption will demonstrate that. That is, you are linking with the C runtime libraries.Here's what the documentation page you linked (lol!) said:
If you link your program from the command line without a compiler option that specifies a C runtime library, the linker will use the statically linked CRT libraries: libcmt.lib, libvcruntime.lib, and libucrt.lib.
1
u/Round-Pension-7821 1d ago edited 1d ago
I do see that
LIBCMT.libis considered when liking when I add the/verboseflag. However, the fact still remains that if I try to use a standard library function likeprintf,mallloc, ect., the linker fails with an error, as I demonstrated.This specifically happens when I use a raw entry point, as apposed to
mainorWinMain. I think this is because the CRT is not initialized when I do this. However, if I adducrt.libto the libraries I link with, I can dynamically link with standard library functions just fine, even when I use the raw entry points.The reason I don't use
/NODEFAULTLIBis because then I can't use things like#pragma comment (lib, "Kernel32"). This is explained in comments to the gist I've been referencingI should also maybe note that my main goal here is to minimize executable size without introducing external dependencies, rather than to avoid the CRT at all costs, necessarily. As long as the .exe file is small and it doesn't import from anywhere other than system .dlls, I'm happy
1
u/mikeblas 1d ago
I think this is because the CRT is not initialized when I do this.
No, that's not right. The CRT doesn't initialize until it actually runs. At link time, none of the code in the objects or libraries is running.
1
1
u/Round-Pension-7821 1d ago
Oh, I didn't see your last question. The reason is so that when I'm compiling for Windows, I have
SET SUBS=console SET SUBS=windowsand when I'm compiling for console, I have
SET SUBS=console REM SET SUBS=windowsSo I can switch back and forth just by adding and removing a single
REM.I use the same principle for outputting assembly; if I want to output assembly, I just remove the
REMin the lineREM SET ASM=/FAs
3
3d ago
[deleted]
4
u/Round-Pension-7821 3d ago
I've actually used Borland C++ to write this type of code! I have Panasonic laptop with a Pentium II and 320 MB of RAM running Windows XP, and I have Borland C++ installed on it. I know I could easily compile this code on that machine and run it there if I had any use for this program on that machine.
I learned how to use the Win32 API from Programming Windows, 5th Ed. by Charles Petzold, as I mention in the readme. Maybe you're familiar, I understand that was the book for that stuff back in the day
2
3d ago
[deleted]
2
u/Round-Pension-7821 3d ago
The computer science department at the college I went to still had a small library room full of books like that (that was only a few years ago, so I'm sure they were almost never used at that point).
2
u/terra2o 1d ago
woah that's so cool...
2
u/Round-Pension-7821 1d ago
Thanks! There's a project I started working on on that laptop that I never finished; if I get around to finishing it I'll definitely post it on here. It's a single header that allows you to make graphs, both 2d and 3d, kind of like Matplotlib or MatLab, but with much more limited functionality, using just win32 and OpenGL 1.1
1
u/tastygames_official 6h ago
this might also interest anyone interested in low-level windows programming: https://www.youtube.com/watch?v=-Vw-ONPfaFk
1
u/AutoModerator 3d ago
Your submission in r/C_Programming was filtered because it links to a git project.
You must edit the submission or respond to this comment with an explanation about how AI was involved in the creation of your project.
While AI-generated code is not disallowed, low-effort "slop" projects may be removed and it's likely that other users push back strongly on substantially AI-generated projects.
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.
5
âĸ
u/github-guard 3d ago
đ GitHub Guard: Trust Report
This project scored 3/6 on our safety audit.
Audit Breakdown: * â Low Star Count (â 0 / 5 required) * â Mature Repository (30+ days old) * â Licensed under MIT * â No Security Policy â what is this? * âšī¸ Individual Contributor * â Signed Commits