This is kind of a tricky thing to explain, but when you understand it, you will understand how executables and libraries work much better. The solution to your problem has already been posted, but if you want the long explanation of what's going on, here it is! The main thing you need to understand is how the compiler and the linker work together. Pay close attention to the words declare and define.
The compiler turns all your source code into object files (as mentioned earlier), which contain actual executable code (basically). For each method, a callable segment of machine code is created. However, within most functions, there are also calls to other functions. Since there is no need to reproduce the called function (inline functions excepted), a dependency is created on the code of the called function. So for instance, the first two lines of following function will be compiled, but one external dependency exists.
foo.cpp
void foo() {
int x = 0;
x = x + 2;
bar(x);
}
You will have a compilation error if this was the entirety of the source file, since bar(int) is never declared or defined. So, either you must define it in this source file, or declare it with a prototype with the signature bar(int). Here's where the linker comes in:
Suppose bar(int) is defined in another source file. You need a way of telling the compiler not to worry about it right now, but that it can count on it existing when the final executable is being built (it's the linker's job to connect unresolved functions/variables to their instantiations), so that's what a header file does:
bar.h
#pragma once
void bar(int x);
We have now declared bar(int). We now need to look at foo.h:
#pragma once
#include "bar.h"
void foo();
And of course we need to include foo.h in foo.c (I didn't show that earlier since I wanted to make sure that it would be understood that the compiler would have no way of knowing that bar(int) existed at that point). So, what happens is that the compiler is told: 'there will be a method with the signature bar(int)', so you can make that call in the code. Now, as far as we've gotten, we still haven't defined bar(int), so if we tried to build as is, the compilation process would go fine, but we would get an unresolved external symbol: ...bar(int) from the linker. The reason is that the linker's job is to find all the unresolved calls and resolve them. This means: find the compiled code for bar(int) and actually jump to it ('link' it). To fix the problem, we must define the function with the matching signature:
bar.cpp
#include "bar.h"
void bar(int x) { x++; } // now we've generated some code for the linker to 'link' the previously unresolved dependency to
Now the code compiles and links. So, we've covered half of your problem, the other half is that the linker was unable to resolve the external variables that you were trying to access. So, if you understand everything so far, it's just more of the same. An extern variable means that the compiler will allow usage of a variable without actually knowing anything about it (starting with a new set of code):
foo.h:
#pragma once
extern int foo;
foo.cpp:
#include "foo.h"
int foo;
bar.h:
#pragma once
#include "foo.h"
bar.cpp:
#include "bar.h"
void bar() {
foo = 5; // this can be resolved by the linker since we know about foo from foo.h through bar.h
}
We can see that foo is a global int that anything that includes foo.h is able to access. The linker resolves the variable when creating the final executable. Alpha_ProgDes gave the right way to fix your problem (since your source file would now know about the extern SDL_Surfaces, which would allow the function/variables to be used).
The reason your old code worked was lucky inclusion order. Depending on the order that things are compiled, you can cheat the system, but this is a dangerous way to go about things, as the build will become very fragile based on the order of inclusions (certain dependency graphs are also impossible using that method). Projects built like that may, in effect, have *one* source file (aka compilation unit, since #include literally inserts source into a file), so everything must be defined in the right order instead of the proper way where it doesn't matter, since you're using the compiler and linker correctly. One other advantage you'll find in a large project is that where you used to have to recompile the entirety of the source for any change you made (since it was in effect one compilation unit), now your project can compile *only* the source that has changed, which is really great (can you tell I learned all this the hard way?).
This also explains libraries. Libraries are just compiled code. The header files for a library are just there to let source code depend on the library, and the linker will find the code and resolve the dependencies. For dynamic libraries, the 'cannot resolve dependency' error will come at run-time (eg when you're missing a dll), but it's the same principle.
I've sadly left out quite a bit, and used C for brevity, but C++ works the same way. In short:
- functions/methods (code units with a particular signature), and variables with any particular name can only be *defined* once in a compilation unit. A compilation unit is the entirety of the code being compiled (ie after all the #included files are inserted to make one big 'unit')
- to get code to compile, there *must* be *either* a definition or a declaration within the compilation unit.
- any variable/function that is only declared (not defined) within a compilation unit *must* be resolved by the linker if it is used (ie you can declare something and never define it if you never try to use it anywhere).
- a declaration *must* be resolved for an executable to be generated. If the definition is not within the compilation unit, it is the linker's job to resolve the dependency.