C programming guide
Libraries, your OS, and SDL
Your operating system has a lot more functionality than C gives you by itself. Some of the C functions like malloc
are actually just calling your operating system's functions for you behind the scenes, such as VirtualAlloc
on Windows. Calling the OS yourself is mostly optional, but it gives you much more control over your program, each operating system has differences that the language and libraries don't always give you control over.
The most notable thing that C does not provide for you is the ability to open a new window. Thus if we want to open a window, we need to call the operating system ourselves, or use a third party library that does it for us.
SDL is one such library, it has many functions that help you talk to the OS, and it works for many different operating systems. However SDL may not be available on all platforms such as game consoles or weird embedded devices, so it is important to properly understand how libraries really work and how to use them when there's nobody telling you what to copypaste.
NOTE: this page is mostly Windows-based for now. Linux works in a very similar way but has some differences, for example dynamic libraries do not use the ".dll" extension and Linux often expects you to "install" libraries.
How to call functions from a library
There's 4 different ways to use a library.
Including code files
This method requires you to have the source code for the library. Just #include the library code files and they will become part of your program. You may want to use some of your own code files this way when you split your project into multiple files. It will have to be compiled along with your program though so it can slow down compile times, but the compiler will be able to do the best optimizations that way. A popular example of third party libraries that work this way are the STB libraries.
#include "stb_image.h"
That was easy.
Note that many third party libraries have additional requirements and will not work just by including them. For example they may require a specific compiler or build tool to be used. It is usually easier to use one of the other methods for this reason unless the library is specifically designed to be imported like this.
Linking with pre-compiled static libraries
This is what we've done so far with C's stdio and the like. The library has already been compiled and exists in some folder along with the compiler.
For this method we need 2 different files: the .a
library file, and a .h
header file. The .a file has compiled functions in it, and the .h file has C code with definitions that allow us to understand what's in the .a library.
We need to #include the .h
header file, and then inform the compiler where the related .a
library file is.
#include "cool_library.h"
If you just include the header and the compiler doesn't know where to find the library file, the compiler will tell you that there's "undefined references". In order to inform the compiler about a library file that you want to use, you must add -l"file_name_to_look_for"
into the compile command. You can also use -L"/my/library/files/folder"
to add a new folder where to look for the files from.
gcc main.c -l"cool_library/lib/cool_library" -o testprogram
The compiler already knows where to find it's own files like stdlib
, so we don't need to inform it ourselves. The compiler usually also comes with (or knows where to find) operating system header files, so we could load an operating system library just by doing #include <windows.h>
. We only need to locate things manually when we use a third party library that we download ourselves like SDL.
Linking with pre-compiled dynamic libraries
This is very similar to the previous method, except dyamic libraries do not become part of your program, instead you must include a separate .dll file with your program executable, which your program then loads functions from every time the program starts. If the .dll is missing, then the program may no longer start.
Dynamically Linked Library files use the .dll
extension instead of .a
.
TODO
Loading functions from dynamic libraries manually
The final method is to use a dynamically linked library as above, except instead of linking with it, we can load functions from it with our own code.
For this method you technically don't need to #include anything, you could write the definitions yourself based on documentation. You could for example only define a single function and only load that single function from a huge library, which may improve compile times. You probably want to include the header files that came along with the dll though to prevent making a mistake.
There's 4 advantages to loading dynamic libraries like this: faster code, better error handling, flexibility, and re-loading.
- I've only heard that functions loaded this way are faster for the program to call or load, but don't quote me on that. If the library is huge though, it will be faster to start your program since it only needs to load the functions that you need, not everything that's in the library.
- If the library is missing or broken, you can create your own error handling, for example you could tell the user of your program where to download the missing/broken DLL.
- If the library is missing or broken, you can create your own fallback. For example if an audio library for your game is not working, you could just play the game without audio. If you're loading mods for your game, you can just disable the mods that don't work.
- Since you're just loading function pointers from the library, you can re-load the functions any time you want. This allows you to load functions from libraries while the program is running, for example you could update a library without having to restart the program.
The main downside is that it's a lot more work to load libraries this way.
In order to load functions from the .dll file, you need to tell the operating system to grab them to you. The compiler makes the relevant operating system functions available for us automatically:
void (*cool_function) (int) = NULL; // Function pointer that we will load a function into. void main () { HMODULE my_cool_library = LoadLibraryExA("my_cool_library.dll", NULL, 0); // Load the library. cool_function = (void(*)(int)) GetProcAddress(my_cool_library, "cool_function"); // Load a function from the library. The function pointer that comes out of GetProcAddress is a placeholder and must to be cast to the correct type. }
(Note: the above functions are specific to Windows, they will not work on other operating systems.)
First off you need to put the function pointer somewhere so you can call it. In this case it's a global variable so it can be used anywhere.
Next, load the library file with LoadLibraryExA by giving a path to the file. Then retrive functions from it with GetProcAddress by giving it the function name. GetProcAddress returns a void pointer, the compiler usually complains about function pointers so you need to cast it to the correct function type.
DLL files contain hidden information about function names, which allows the OS to find them. However there's no information about what the functions are like, there's no way to find out what the function returns or what it wants as input. That's why you need to define functions and structs and other types yourself, which is why usually you'll want to include the library's header files.
Calling the operating system
TODO
Note: link to MSDN (also screenshot so I don't need to babysit their website changes)
void* (*VirtualAlloc) (int len) = NULL; // Function pointer that we will load a function into. void main () { HMODULE Kernel32 = LoadLibraryExA("Kernel32.dll", NULL, 0); // Load the library. VirtualAlloc = (BOOL(*)(HGLRC)) GetProcAddress(Kernel32, "VirtualAlloc"); // Load a function from the library. }
Setting up SDL
SDL is a library for interacting with the operating system: opening a window and drawing things into it, listening to keyboard/mouse input, playing audio, and so on.
Linux: do de doo doo
Windows: get the library from their website. Download the "development libraries", we're not using Visual C++ compiler so get the MinGW version.
Note about the downloads: runtime binaries are usually something you can install to make other programs that use the library work. Source code may be hairy to deal with and require all kinds of weird build processes so we don't want to mess with that if we can help it. What we want are the development libraries.
"tar.gz" is like a .zip file, you can extract it the same way. Install 7zip if you can't do it.
Inside there's 2 different versions, "i686" and "x86_64". The first is 32bit version and the second is 64bit version (don't ask me why they have confusing names like that), it needs to match the program you're compiling. We'll be making a 64bit program so use the x86_64 version.
When downloading libraries, what you usually want to look for are folders called bin
, lib
and include
.
include
has the header files that you need to #include from your code. lib
has the .a library files that you need to inform your compiler about. bin
has the final compiled files, such as .dll or .exe. Since /bin has a .dll, we can choose to link with SDL either dynamically or statically. We'll static link with the .a files first since it's simpler.
Put the entire SDL folder in the same folder as your main.c file. You can now include it from your code:
#include "SDL/x86_64-w64-mingw32/include/SDL2.h"
Next, you need to tell your compiler where the compiled library files are. Add this to your compile command in your build file:
-L"SDL/x86_64-w64-mingw32/lib" -lSDL2
A full command as an example:
gcc main.c -L"SDL2-2.0.14/x86_64-w64-mingw32/lib" -lSDL2 -o testprogram
TODO:
? start this page by compiling test libraries that the examples can load
? a sanic version of this page, just dump all the code examples
clarify <include> and "include" (for the header file)
explain 64bit vs 32bit program somewhere, what's the difference in terms of compiling it.
open window
draw rectangle
set up a loop that flashes rectangles with arrow keys
go read some SDL tutorials
-> different page: a simple game
If you want to open a Window by calling the operating system directly, there's a guide for both Windows and Linux in the example programs.
https://github.com/inputsh/awesome-c
array libraries, publish tfarr and mfstr