Blog
All Blog Posts | Next Post | Previous PostObject Pascal: Compiler Directives
Tuesday, June 26, 2018
Compilation Directives could help you to make your code multi-platform or even cross-compiled.
Introduction
Compilation directives are powerful commands that developers could use to customize how the compiler works.
These directives pass parameters to the compiler, stating the arguments of the compilation, how must be compiled, and which will be compiled.
There are basically 3 types of compilation directives.
- Switch directive
- Parameter directive
- Conditional compilation directive
The first two types change the compile parameters, while the last one changes what the compiler will perform on.
In this article we will deal with the last one: Conditionals.
Conditionals
They are powerful commands.
With just a few conditional commands, your Object Pascal code can be compilable across multiple platforms.
However, as we add more and more directives, the code will become more complex.
Let's take a look in the example below:
//http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Conditional_compilation_(Delphi) {$DEFINE DEBUG} {$IFDEF DEBUG} Writeln('Debug is on.'); // This code executes. {$ELSE} Writeln('Debug is off.'); // This code does not execute. {$ENDIF} {$UNDEF DEBUG} {$IFNDEF DEBUG} Writeln('Debug is off.'); // This code executes. {$ENDIF}
In this example, only the first and third Writeln function calls will be executed.
All directives and also the second function call won't be part of the final executable, I mean, the ASSEMBLY code.
Cool.
However, it looks like that the code is "dirty" and also we have a temporal coupling, because the constants need to be defined in a specific order.
Directives and definitions of constants that will be used in only a single unit may even be manageable, but what if you will work with tens or even hundreds of units that will use these directives and definitions, do you still think this approach is the best choice for the architecture of your project, with the purpose of building it as cross-compiled or multi-platform?
I don't think so.
Encapsulating Directives
Imagine a project that needs to be cross-compiled in Delphi and Free Pascal. We would like to use the same classes, the same code, but could exists some differences between these compilers.
The code needs to evolve independent of the compiler. I mean, if some changes could be done to improve the code when it is compiled on Free Pascal, for example, it should be done without thinking in some difference that might exist in Delphi.
To do so properly, we could not work in a code with many compilation directives, because some changes could broke the Delphi version or vice-versa.
Instead of seeing directives, it might be better seeing just classes.
I would called this, encapsulated directives.
Implementation
Imagine an unit witch contains a class to represents a MD5 algorithm.
Free Pascal already has a md5
unit which has functions to do the job and of course we have to make classes to encapsulate those functions.
In Delphi, the unit that do the same job is named hash
.
We do not want to "reinvent the wheel" then, let's use what is already done on both platforms.
So, how would you do this implementation neither using conditional directives in implementation code nor *.inc files?
First of all, let's create our MD5 unit:
unit MD5.Classes; interface uses {$ifdef FPC} MD5.FPC {$else} MD5.Delphi {$endif}; type TMD5Encoder = class(TEncoder); implementation end.
As you can see, there is almost nothing on this unit. The real code will stay in others units, i.e, MD5.FPC
and MD5.Delphi
, one for each platform.
Let's create the FPC version:
unit MD5.FPC; interface uses md5; type TEncoder = class public function Encode(const Value: string): string; end; implementation function TEncoder.Encode(const Value: string): string; begin Result := MD5Print(MD5String(Value)); end; end.
md5
unit.
Then, let's create the Delphi version:
unit MD5.Delphi; interface uses hash; type TEncoder = class public function Encode(const Value: string): string; end; implementation function TEncoder.Encode(const Value: string): string; begin Result :=THashMD5.GetHashString(Value); end; end.
Both units have the TEncoder
class definition (yes, same name in both). Then, we created a TMD5Encoder
class that inherits from the correct class, which is platform dependent, and voila! We have clean units, without any conditional directives inside methods.
Finally, we have two distinct classes in different units that can evolve its implementation independently, without any fear of breaking the code between platforms.
Conclusion
Compilation directives is a good tool for customize the code. However, it should be used with parsimony.
In object-oriented code, try to use more specialized objects than compilation directives.
For each conditional directive that you want to add to the code, I suggest implementing a new class that encapsulates the directive or a set of them.
Your code will be cleaner and sustainable.
See you.
Marcos Douglas B. Santos
This blog post has not received any comments yet.
All Blog Posts | Next Post | Previous Post