You are probably used to compiling your main.bas
file with fbc main.bas
, creating a file main.exe
. This is an oversimplified view however. Let's see what the docs have to say about that:
At its simplest, fbc takes a source file as a command-line argument and produces an executable file. It does this by compiling the source file (.bas) into an assembly (.asm) file, then compiling this into an object file (.o) using GAS and finally linking using LD this object file to other object files and libraries it needs to run, producing the final executable file. The assembly and compiled object files are deleted at this point by default.
So, actually, this is a multistep process:
main.bas
is turned into a generated assembly code filemain.asm
(or with-gen gcc
it would be a generated C code filemain.c
). We can execute that step alone usingfbc -r main.bas
.main.asm
is compiled (using GAS, the assembler) to an object filemain.o
. We can execute that step alone usingas test.asm -o test.o
, or step 1 and 2 together withfbc -c main.bas
.main.o
is linked (using LD, the linker), together with the default static libraries and other object files that FreeBasic requires (for examplefbrt0.o
), to an executable filemain.exe
. We could do this step alone using a command withld -o main.exe main.o <all the libraries here>
but it'd be quite a complex command. We can also do steps 2 & 3 together with simplyfbc main.o
.
An object file is a compiled file that contains machine code but has missing links in various locations - kind of like pointers that don't have a target yet. These are the references to "symbols", i.e. named addresses. Instead of having the final address of some function, they only have the internal name of that function, and it's the linker's job at the end to resolve those references and put the final addresses into the code before joining all the different parts of machine code into a final executable.
We can see all that happening if we call fbc
with the verbose argument -v
:
C:\Users\david\Temp\test1>fbc -v main.bas
FreeBASIC Compiler - Version 1.03.0 (04-12-2015), built for win32 (32bit)
Copyright (C) 2004-2015 The FreeBASIC development team.
standalone
target: win32, 486, 32bit
compiling: main.bas -o main.asm (main module)
assembling: z:\programme\fb\bin\win32\as.exe --32 --strip-local-absolute "main.asm" -o "main.o"
linking: z:\programme\fb\bin\win32\ld.exe -m i386pe -o "main.exe" -subsystem console "z:\programme\fb\lib\win32\fbextra.x" --stack 1048576,1048576 -s -L "z:\programme\fb\lib\win32" -L "." "z:\programme\fb\lib\win32\crt2.o" "z:\programme\fb\lib\win32\crtbegin.o" "z:\programme\fb\lib\win32\fbrt0.o" "main.o" "-(" -lfb -lgcc -lmsvcrt -lkernel32 -luser32 -lmingw32 -lmingwex -lmoldname -lgcc_eh "-)" "z:\programme\fb\lib\win32\crtend.o"
More information about what object files and linking are can be found in the Wikipedia article about linkers.
Note that library files (.a
) are essentially a group of "half-linked" object files which can be linked with other object files and other libraries to an executable - kind of like a ZIP file of object files (in fact the a
comes from "archive" for that reason).
Let's go back to the docs we looked at before and continue with the next paragraph:
fbc can accept multiple source files at once, compile and link them all into one executable. For example, the following command,
fbc foo.bas bar.bas baz.bas
produces the executable
foo.exe
in DOS and Windows, and./foo
in Linux. Sincefoo.bas
was listed first, it will be the main entry point into the executable, and also provide its name.
What this does is essentially the following:
- For each of the three files, steps 1 & 2 of the compilation process are executed. So,
foo.bas
is turned intofoo.asm
and thenfoo.o
, the same forbar.bas
andbaz.bas
. - In step 3 of the compilation process, the linking, all of the three object files
foo.o
,bar.o
andbaz.o
are linked together intofoo.exe
.
We can also separate these steps manually:
fbc -c foo.bas bar.bas baz.bas -m foo
(the-m foo
specifiesfoo
as main module - this is required because with-c
fbc won't automatically use the first module as main module, and only the main module will contain the entry point functionWinMain@16
that Windows expects).fbc foo.o bar.o baz.o
- will producefoo.exe
.
Let's try this:
main.bas
Print "Hello from Main!"
module2.bas
Print "Hello from Module 2!"
> fbc main.bas module2.bas
> main.exe
Hello from Module 2!
Hello from Main!
As you can see, both source files are compiled together to one executable file.
You may be surprised to see Hello from Module 2!
printed before Hello from Main!
, but it will start making sense if you think about the fact that you would usually have the actual start of the code in your main module and the other modules would just initialize themselves as necessary, so you'd probably want those modules to be initialized before the main code starts, so their top-level code actually executes first.
We can use -v
again to see exactly what's happening:
C:\Users\david\Temp\test1>fbc -v main.bas module2.bas
FreeBASIC Compiler - Version 1.03.0 (04-12-2015), built for win32 (32bit)
Copyright (C) 2004-2015 The FreeBASIC development team.
standalone
target: win32, 486, 32bit
compiling: main.bas -o main.asm (main module)
compiling: module2.bas -o module2.asm
assembling: z:\programme\fb\bin\win32\as.exe --32 --strip-local-absolute "main.asm" -o "main.o"
assembling: z:\programme\fb\bin\win32\as.exe --32 --strip-local-absolute "module2.asm" -o "module2.o"
linking: z:\programme\fb\bin\win32\ld.exe -m i386pe -o "main.exe" -subsystem console "z:\programme\fb\lib\win32\fbextra.x" --stack 1048576,1048576 -s -L "z:\programme\fb\lib\win32" -L "." "z:\programme\fb\lib\win32\crt2.o" "z:\programme\fb\lib\win32\crtbegin.o" "z:\programme\fb\lib\win32\fbrt0.o" "main.o" "module2.o" "-(" -lfb -lgcc -lmsvcrt -lkernel32 -luser32 -lmingw32 -lmingwex -lmoldname -lgcc_eh "-)" "z:\programme\fb\lib\win32\crtend.o"
Now let's try something else. Let's create a constant MyName
in each module with Main
and Module 2
in it respectively and print that:
main.bas
Const MyName = "Main"
Print "Hello from " & MyName & "!"
module2.bas
Const MyName = "Module 2"
Print "Hello from " & MyName & "!"
If we compile this again with fbc main.bas module2.bas
and run it, we'll see it still works. But why does this work, shouldn't we get an error Duplicated definition, MyName in 'Const MyName = "Main"'
?
The reason we don't get such an error is because the two source files are compiled separately - they are separate "compilation units". Only the already-compiled object files are then linked together.
This is different from using for example #Include
to add the contents of one file into the other via the preprocessor (you can use the -pp
flag to see the resulting file - it will be essentially both source files concatenated) because in that case the whole code is compiled as a single compilation unit.
The benefits are mainly that one module won't step on another module's toes with naming conflicts* and that a change in one module will only require recompilation of that module and not all modules. A good IDE like FBEdit or a build tool like Make will detect which source files changed since the last compilation and recompile only those when you hit "build" (followed by linking of course).
*: Note: Subs/functions are by default public and therefore can cause conflicts, but see below how to avoid that.
Just having two modules that individually run code isn't tremendously useful. It becomes much more exciting if you expose certain functions or variables from one module to use in other modules.
Let's start with the naive approach of defining a function in one module and using it in another:
sum.bas
Function CalculateSum (x As Integer, y As Integer) As Integer
Return x + y
End Function
main.bas
Dim As Integer a, b
Input "First number: ", a
Input "Second number: ", b
Print "Sum:"; CalculateSum(a, b)
> fbc main.bas sum.bas
main.bas(4) error 41: Variable not declared, CalculateSum in 'Print "Sum:"; CalculateSum(a, b)'
That didn't work. And if we try fbc sum.bas main.bas
or fbc sum.bas main.bas -m main
it won't work either. Why is that? Well, since main.bas
and sum.bas
are compiled as separate units, main.bas
won't know about any function CalculateSum
since it's not defined inside of its own compilation unit! That should become obvious if you think about it that you should be able to compile them on their own and later manually link them (fbc -c main.bas -m main
, fbc -c sum.bas
, fbc main.o sum.o
- here, when we do fbc -c main.bas -m main
there isn't even any reference to our sum module, so how should it know about our function?).
The solution is to declare the function in main.bas
so that the compiler knows that such a function is supposed to exist, and what its parameters are. It will then happily compile main.bas
to an object file with a reference to a function CalculateSum
, even though that function is not defined in the same module, and it's up to the linker at the end to resolve that reference. This is the same thing as when you use any other function that's not defined in your own module - for example the FreeBasic function Chr
, which is provided by the library file libfb.a
at the end.
This is done using the Declare
keyword. Here is the updated main.bas
file:
Declare Function CalculateSum (x As Integer, y As Integer) As Integer
Dim As Integer a, b
Input "First number: ", a
Input "Second number: ", b
Print "Sum:"; CalculateSum(a, b)
> fbc main.bas sum.bas
> main.exe
First number: 2
Second number: 5
Sum: 7
Now the code compiles and works!
And, just out of curiosity, what happens if we try to compile one of the files alone, but not just to an object file but to a full executable?
> fbc sum.bas
> sum.exe
For sum.bas
, it compiles, but when we run sum.exe
, nothing happens - of course, since there is only a function definition in the file and no actual top-level code.
> fbc main.bas
main.o:fake:(.text+0x8d): undefined reference to `CALCULATESUM@8'
But for main.bas
, it doesn't compile properly. Actually, to be exact, the compilation part (to an object file) works just fine, but the linking then fails, because main.bas
is referring to a function CalculateSum
that we promised (using Declare
) that it is to be found somewhere eventually, but we lied - it couldn't be found at the end, therefore the linking operation fails with an undefined reference.
Now let's assume we have more modules already, and several of them want to use the CalculateSum
function, or other functions provided by other modules. It will become cumbersome and error-prone to add long Declare
lines with the function signature to every module that wants to use those functions. This is where header files (.bi) come into play.
If we create a file sum.bi
that has the Declare
line in it, then other modules only need to use #Include "sum.bi"
to be able to reference the function. With a project with many modules, we can also just create one "master" header file that includes all other headers files, so every module just needs to include that master header file and can now use all the functions that other modules provide!
Let's expand this idea and create a module that exposes a UDT named Person
, and have another module use it. Here, we would put the type definition for Person
into a header file as well, because both person.bas
and main.bas
would use it, however the implementation of methods of Person
would be defined in person.bas
:
person.bi
Type Person
Name As String
Age As Integer
Declare Constructor (Name As String, Age As Integer)
Declare Sub Greet ()
End Type
person.bas
#Include "person.bi"
Constructor Person (_Name As String, _Age As Integer)
Name = _Name
Age = _Age
End Constructor
Sub Person.Greet ()
Print "Hello, my name is " & Name & " and I'm " & Age & " years old!"
End Sub
main.bas
#Include "person.bi"
Var Bob = Person("Bob", 25)
Bob.Greet()
> fbc main.bas person.bas
> main.exe
Hello, my name is Bob and I'm 25 years old!
Remember when I mentioned that having different compilation units has the benefit of being able to write modular code without worrying about naming conflicts? You may notice that there was nothing I had to do in my modules to ensure that a sub/function is visible to other modules, I only had to declare it in those other modules. This is because in FreeBasic, subs and functions are public by default, i.e. can be seen by all other modules. That also means that we can have conflicts even if a function is designed only for internal use. So, what about that benefit then?
The solution is to make use of the Private
keyword for subs/functions. If a function is defined like this, then it will not be visible to other modules, and hence also can't clash with them:
Private Function InternalThing () As Integer
' ...
End Function
Finally, let's see how it works if we have global variables that we would like to share between modules. This is done with the Common
keyword:
shared.bi
Common Shared MySharedVariable As Integer
Declare Sub PrintIt ()
main.bas
#Include "shared.bi"
MySharedVariable = 42
PrintIt()
module2.bas
#Include "shared.bi"
Sub PrintIt ()
Print "Shared variable is"; MySharedVariable
End Sub
> fbc main.bas module2.bas
> main.exe
Shared variable is 42