C++Builder, a Delphi developer perspective
Two years after the release of Delphi, C++Builder was born.
By then, Delphi had already taken the world by storm. With its easy-to-use design tools and brilliantly engineered VCL component framework, Delphi had knocked the competition straight out of the park. And now they would do it again, this time for the C/C++ community.
With the release of C++Builder, C developers could finally enjoy the same rapid application development (RAD) approach that Delphi developers enjoyed.
While I was firmly in the Delphi camp at the time, I still remember what a massive leap this was for C/C++. Borland was firing on all cylinders and developers who were used to Watcom or Storm C were flabbergasted at what they saw; toolchains that today are mere footnotes in the annals of IT history.
Just like Delphi, there was simply nothing like it.
C++ for Delphi developers
One of the things that really blew people away about Delphi and C++Builder, was that these two products once shared a surprising amount of the same compiler technology. At one point the language layer, if we can call it that, was the only distinct difference between the products.
Now before you object and point out important differences between the languages and their runtime behavior, I am talking about how closely knit these products were under Borland. Binary compatibility was (for a time) one of Borland’s architectural goals.
Since then the internal mechanics of both the languages and products have diverged, but I still remember developers outside the Borland camp trying to pitch C/C++ against Delphi, only to be gobsmacked when they realized that Object Pascal and C/C++ were practically joined at the hip. The two languages had evolved side-by-side, and share a rich history together.
Like most Delphi developers I have often felt the urge to learn more about C++Builder and to immerse myself in the language. After all, both products and compilers make use of the VCL; a framework I am intimately familiar with; as well as the new and sexy FireMonkey framework for multi-platform work.
So if I am to learn yet another language, then surely C++Builder would be the natural choice.
Benefits of C++Builder for Delphi developers
Out of the box, C++Builder gives me the following advantages:
- The same IDE that I am used to working with
- Form designer is the same
- Keyboard shortcuts
- Debugger and breakpoints
- The package system is the same
- I can use the VCL which I know inside and out
- And FireMonkey is there for multi-platform applications
Besides these comforting familiarities, there are benefits of a more technical, low-level nature that can give my Delphi applications greater scope:
- Access to the overwhelming body of material that is exclusive to C/C++
- The ability to compile libraries to .obj files that can be linked into Delphi projects
- The ability to write components and packages that are Delphi compatible
- Write proxy libraries that makes C/C++ exclusive frameworks available for Delphi (or any language capable of loading DLL libraries)
- Enjoy the full might of LLVM based code generation and optimization
That last point, that of LLVM based code generation, is definitively a selling point for me. Delphi enjoys LLVM compiled binaries on mobile targets, but desktop applications (including macOS and x86 Linux) are still compiled with the traditional Delphi compiler.
The traditional Delphi compiler is very powerful and well written, but LLVM represents the collaboration of hundreds of computer scientists that are all at the top of their field. As far as compiler technology goes, Clang (LLVM) has seen a tremendous amount of work and refinement, far more than any single company can deliver. LLVM is backed financially by academia (and the industry at large), and while the research originated at the University of Illinois, it is today governed by its own organization.
Delphi continues to be my favorite language and development platform, I don’t think any language could ever change that. It strikes a perfect balance between speed, functionality and productivity that is difficult to match. But there are situations where the raw, primal power of C/C++ will yield better results. Especially when dealing with binary data processing and manipulation.
C++ is not C
Way back in the previous century, I used vanilla C quite a bit; mostly for shared libraries where performance was key; where my only other option was resorting to assembly. Those that have followed my blog or frequent my groups on social media, know that I started writing commercial software on the now vintage Commodore Amiga computer in my teens; A system that I still enjoy coding for in my spare time as a hobby.
The downside of having used C back in the late 80s early 90s, is the C that I learned had not yet evolved many of the standards we take for granted today. I never bothered with object orientation back then, because libraries were (and continues to be) largely procedural. And by the time OOP became the norm on that platform, I had already fallen hopelessly in love with Pascal; which led me to x86 and Turbo Pascal – and consequently Delphi in 1995.
Fast-forward 25 years, and I find myself drawn to C++Builder.
First of all for practical reasons, but also because C/C++ have some intellectual aspects that are unique. Subtle aspects that I keep encountering when dealing with Linux on Embedded and IoT projects. Nothing that can’t be solved with Object Pascal, but I have never subscribed to the “one language to rule them all” philosophy.
All developers should keep a well stocked toolbox, and it’s wise to learn languages immediately within and outside your comfort zone.
So if C is your main language, learning assembly and Delphi is a good move. If Delphi is your main language, then learning C/C++ and JavaScript is probably a good idea (not that I consider JavaScript to be the next evolutionary step from Delphi, that would be blasphemy). But from a market perspective it makes sense. If you enjoy assembly coding within Delphi, well then you have ever greater wingspan.
One epiphany I had when looking at the many C++Builder examples and resources, is that C++ is indeed not C (as many wise men have said). I bought two large books on the C++ language to accompany my studies, and where I expected an introduction to the traditional constructs such as records (struct), pointers, for-next loops, datatypes and variables — C++ appears to me almost like a separate language. Thankfully, C++Builder allows you to start where you are. You don’t have to dive into the deep-end to enjoy the benefits that C/C++ has to offer.
Delphi is equal to C/C++ in functionality (give or take multiple inheritance), but until LLVM is used by Delphi unequivocally for all targets, C++Builder will have some speed advantages.
My first C++ class
While I have decades worth of experience with Object Pascal and Delphi, C++ is utterly new to me. So please keep in mind that my total experience with writing C++ code is six hours, where 90% of that time was spent googling around to formulate an approach.
Learning how to learn is more important than what you learn in many cases, and finding a way to relate to a subject helps eliminate any psychological resistance you might encounter (there is a fair bit of psychology involved in programming; self motivation techniques being the most important of them all).
My very first C++ class is a simple one, namely a memory buffer class. This was the simplest class I could think of that actually does something useful. So it’s just a class that manages a standard memory allocation, exposing simple to use methods and properties that makes accessing the raw data easy.
I actually have a much more elaborate library of such buffer classes implemented in Delphi. These are obviously more evolved and help unify memory, file and cloud storage, so that you can access raw data, regardless of location or medium, through a common interface. Those classes include methods like insert() and remove() which will grow and shrink the buffer accordingly, and move the trailing-data up or down inside the file (something that should have been added to TStream ages ago).
Those Delphi classes are actually the basis for a database engine, so who knows – maybe a C/C++ tutorial on “how to code your own database engine” is something we can do in the future.
Purpose of the exercise
The purpose of this coding exercise is simply to demonstrate how wonderful the C++Builder environment is to work with, and that learning C++ doesn’t have to be difficult.
It’s easy to talk about something, but unless you are willing to put your money where your mouth is, you are basically just a spin doctor. I have written about Delphi and published code for decades and know that product inside and out, but the same cannot be said about C++Builder.
My criteria when talking about technology has always been, and will always be, my own personal experience. I can’t stand tech writers that produce articles on subjects they have no experience with, because their articles risk confusing the reader more than informing them, turning young students away from subjects that should in fact inspire them.
So this article is me going out of my comfort zone, and putting my own philosophy to the test. Will my Delphi skill help me get to grips with C++Builder faster? Is C++Builder as easy to use as Embarcadero claims it is? Well, let’s find out!
Outside my comfort zone
I already had RAD Studio 10.3 installed, but truth be told I have never used the C++ aspect of RAD Studio to write my own projects. On rare occasions I have compiled the odd C library, such as the font-rendering engine FreeType. I would then statically link the generated .obj files into a Delphi graphics library I use from time to time (a process we can look at in later articles).
Like I have underlined many times before, both in my presentations, personal blog and the odd interview: if you take the time to learn an archetypal language, that knowledge will benefit you for a lifetime; because archetypal languages deal with how computers really work, not how human beings would like them to behave.
As of writing there are only two archetypal languages on the planet:
- C/C++
- Object Pascal
What constitutes an archetypal language is a large topic that would be out of scope for this article, but to quickly sum it all up.
Archetypal languages
In short, it means that the language is designed to interface directly with the hardware, and that the language is capable of generating code that has no dependency on a separate run-time system (e.g Java or .Net). And furthermore, that it operates with principles unbound by an operating system.
Optimistic, context based languages
The second language type is often called optimistic; a term that stems from the world of databases, where “optimistic locking” takes for granted that certain criteria is already met. These are also called context based languages, in that they cannot operate outside a pre-existing infrastructure (e.g operating system or run-time environment). C#, Java, JavaScript, Python etc are all context based languages.
Note: I would add the Rust language to the list of archetypal languages. One could argue that Rust represents a reformation of the C/C++ language, one that moves in a radically different direction than what Bjarne Stroustrup aimed for. Having said that, the Linux community has categorically refused to accept drivers written in Rust, so the suitability of Rust for kernel development is not yet resolved.
How to approach a new language
The human mind will always look for familiarities when faced with a new situation; and if that cannot be found – the mind can even make one up (called associative mythologization).
This is why I spent a few hours evolving an approach before I jumped into coding, because without a mental compass, progress can be slow and demotivating.
I concluded that the most practical approach was simply to imagine how I would do things in Delphi, isolate the key language elements I would use, and then find the C++ equivalents. So I spent a few hours reading up on the essentials, and narrowed my needs to the following:
- The relationship between .cpp files and .h files
- How and where to define a class
- Constructors and Destructors
- How to define a property, with getters and setters
- Dealing with pointers
- Especially the difference between “pointer of” and “pointer to”
- Throwing and catching exceptions
- Standard operators (and, or, not)
- Standard conditionals (if, then, else, case / switch)
Since C++Builder is 100% standards compliant, Google provided copious amounts of material to learn from (so those expensive books I bought might have been premature). Taking into account that I haven’t touched C since the 80s, I was afraid there would be conflicting dialects. Back when I used C to write shared libraries, compiler vendors were battling it out for dominance. The never-ending “who would define the next official standard” (not unlike Microsoft vs Sun with Java). Companies like Manx (Aztec C), Haage & Partner (Storm C) and Watcom made it very hard for developers back then.
Thankfully, all of that is ancient history – and as far as C++Builder is concerned, it is 100% compatible with C++ 17, which is the latest ISO/IEC 14882 standard.
The Interface
I must admit that I like C/C++ a great deal. The only thing I find annoying is having to define those header files. In short, the header-file is where you define classes and custom data types (if you need them). It plays the exact same role as the interface section of a Delphi unit. Having to work with two separate files rather than one will take some getting used to. After so many years with Object Pascal, I have become mentally conditioned to regard interface and implementation holistically; which technically they are not.
But let’s start with my header file (or interface for us Spartans). You must forgive the formatting of the text here; My text editor was not kind to my tabs, and it likes to uppercase things.
#ifndef bufferH #define bufferH #include <memory> #include <vcl.h> class TQTXBuffer { private: UInt8* FBuffer = NULL; int FSize = 0; protected: bool getEmpty(); int getSize(); UInt8 getItem(int Index); void setItem(int Index, UInt8 Value); public: __property bool Empty = { read = getEmpty }; __property int Size = { read = getSize }; __property UInt8 Items[int Index] = { read = getItem, write = setItem }; bool allocate(int BytesToAllocate); bool release(); int read(int Index, int BytesToRead, UInt8* Target); int write(int Index, int BytesToWrite, UInt8* Source); void zero(); void fillAll(UInt8 Value); void fill(int Index, int BytesToFill, UInt8 Value); ~TQTXBuffer(); }; #endif
The implementation
With the interface out of the way, let’s look at the implementation!
#pragma hdrstop #include "buffer.h" #include <memory> #include <vcl.h> #pragma package(smart_init) const char* cnt_err_no_memory = "No memory allocated error"; const char* cnt_err_index_invalid = "Invalid index error"; TQTXBuffer::~TQTXBuffer() { if (FBuffer != NULL) release(); } void TQTXBuffer::zero() { if (!getEmpty()) { memset(FBuffer, 0, FSize); } } void TQTXBuffer::fillAll(UInt8 Value) { if ( !getEmpty() ) { memset(FBuffer, Value, FSize); } } void TQTXBuffer::fill(int Index, int BytesToFill, UInt8 Value) { if (!getEmpty()) { if ( (Index >= 0) && (Index < FSize) ) { if (BytesToFill > 0) BytesToFill -= (FSize - Index); if (BytesToFill < 1) return; UInt8* lRef = FBuffer; lRef += Index; memset(lRef, Value, BytesToFill); } else throw new Exception(cnt_err_index_invalid); } else throw new Exception(cnt_err_no_memory); } bool TQTXBuffer::allocate(int BytesToAllocate) { if (!getEmpty()) release(); try { FBuffer = (UInt8*) malloc(BytesToAllocate); FSize = BytesToAllocate; } catch (...) { FBuffer = NULL; FSize = 0; return false; } memset(FBuffer, 0, FSize); return true; } bool TQTXBuffer::release() { if (!getEmpty()) { free(FBuffer); FBuffer = NULL; FSize = 0; return true; } else return false; } int TQTXBuffer::getSize() { return FSize; } bool TQTXBuffer::getEmpty() { return (FBuffer == NULL); } UInt8 TQTXBuffer::getItem(int Index) { if ( !getEmpty() ) { if ( (Index >= 0) && (Index < FSize) ) { UInt8* lRef = FBuffer; lRef += Index; return *lRef; } else throw new Exception(cnt_err_index_invalid); } else throw new Exception(cnt_err_no_memory); } void TQTXBuffer::setItem(int Index, UInt8 Value) { if ( !getEmpty() ) { if ( (Index >= 0) && (Index < FSize) ) { UInt8* lRef = FBuffer; lRef += Index; *lRef = Value; } else throw new Exception(cnt_err_index_invalid); } else throw new Exception(cnt_err_no_memory); } int TQTXBuffer::read(int Index, int BytesToRead, UInt8* Target) { if ( !getEmpty() ) { if ( (Index >= 0) && (Index < FSize) ) { if (BytesToRead > 0) BytesToRead -= (FSize - Index); if (BytesToRead < 1) return 0; UInt8* lRef = FBuffer; lRef += Index; memcpy(Target, lRef, BytesToRead); return BytesToRead; } else throw new Exception(cnt_err_index_invalid); } else throw new Exception(cnt_err_no_memory); } int TQTXBuffer::write(int Index, int BytesToWrite, UInt8* Source) { if ( !getEmpty() ) { if ( (Index >= 0) && (Index < FSize) ) { if (Index + BytesToWrite > FSize) BytesToWrite -= (FSize - Index); if (BytesToWrite < 1) return 0; UInt8* lRef = FBuffer; lRef += Index; memcpy(lRef, Source, BytesToWrite); return BytesToWrite; } else throw new Exception(cnt_err_index_invalid); } else throw new Exception(cnt_err_no_memory); }
Now before you chop my head off and preach the gospel of the mighty std::vector<>, let me remind you that I can only start with what I know. And the above was what I managed to scrape together in six hours of looking at the language. That is also the point: you can start with what you have, and then expand your knowledge at your own pace.
What does the class do?
As a Delphi developer you will recognize the private, protected and public sections of the class. The properties are defined in a curious way if you are used to Delphi. Here getter and setter methods are assigned within a {} code-block (there are reasons for this).
Other than that I think the syntax is pretty self-explanatory. The only word that is unfamiliar to Object Pascal developers is perhaps “void”, which simply means “nothing”. In the C language, all methods are functions, and the data type is defined before the method name. Void simply tells the compiler that this function doesn’t return any data (and is thus what we call a procedure in Object Pascal).
Delphi developers are used to defining a class method as:
Procedure TSomeClass.MethodName; begin end;
But in C++ the same looks like:
void TSomeClass::MethodName { }
This would probably be a good place to talk about partial classes, but that is a subject for one of those large books I bought. While I might not need those books for simple stuff like I have demonstrated in this article, I do like having them around for reference. I’m one of those that like to read before going to bed, and whenever I encounter something I want to learn more about, I read it before going to sleep so that my subconscious can digest the information while I sleep.
So how do we use this class I just wrote? Simple. Think of it as a large array of bytes. The class itself could be useful if you give it more work (inheriting from TPersistent would be a logical next step, and implementing Assign() so you can move data between instances. Saving and loading to streams would likewise make it more useful, and perhaps adding functions for setting and getting individual bits?).
Here is a short example of how to use it:
// create a new instance TQTXBuffer *buff = new TQTXBuffer(); // allocate a 50 byte buffer buff->allocate(50); // manually set each byte int x = 0; while (x < buff->Size) { buff->Items[x] = x; x++; } // Release memory and free instance buff->release(); delete(buff);
So if you are looking for a second language to compliment Delphi, then I urge you to give C++Builder a dedicated try – and to get a couple of good books in the process! It will save you a lot of time and frustration. Google is awesome, but some things are best learned the old fashion way.
Oh, and keep in mind: This was the result of six hours with a new language, so there are probably 1000 better ways of achieving the same result with better means.
Reflections
This article was never meant to be a tutorial of any sort. I had a simple goal in mind, namely to show how easy it can be to learn C++ if you already use and master Delphi.
After all, there is a reason that these two languages co-inhabit RAD Studio. I can think of far easier languages to bundle with Delphi – but these languages are meant to be used in symphony for a reason. They represent the archetypal development cycle that Embarcadero is thankfully committed to.
I must admit that the task was a bit intimidating at first, but when I settled on an approach I truly began to enjoy the experience a great deal! I find myself asking, why did I wait so long to give C++Builder the attention it so clearly deserves!
Using C++Builder stands in stark contrast to my experiences with Visual Studio. The VCL really is worth its weight in gold, and I mean that from the bottom of my heart. Writing software for WinAPI in C/C++ without the VCL or FMX was nothing short of a complete nightmare.
But with C++Builder I was able to re-apply my skillset as a Delphi developer and become productive with only a few hour’s effort.
I’m not proposing that I am anything but a beginner when it comes to C++. But most of my knowledge from Delphi is directly transferable – which means I am able to bridge these two languages and realities with greater ease.
I will no doubt return to this subject shortly, looking at how combining the power of Delphi and C++Builder can be used to great effect. My experience so far has been thoroughly enjoyable! One I highly recommend for all Delphi developers!