|
Antwort |
Registriert seit: 4. Jun 2010 15.472 Beiträge |
#1
The C++Builder 25th Anniversary: Historical Article on C++ Builder Language Extension2. Mär 2022, 12:40
As you have probably heard, soon after Delphi's 27th birthday, C++ Builder is celebrating its 25th anniversary. You can read more about the product history, on this great blog post by C++Builder PM David Millington*on "Celebrating 25 Years of C++Builder!" There is another very interesting blog post by David I on "The C++Builder 25th Anniversary: Visual Development, the Power of the C++ Language and 2.5 decades of Continuing Excellence". Even if you are mostly interested in Delphi, both articles help putting two two "twin" products in the right perspective.
At the time Delphi was released, I was*primarily focused on C++ (and in particular Borland C++ OWL library for Windows programming) and had written 3 books on the language, the Borland IDE and the library. While I switched to Delphi, C++ was still an active part of my focus when C++Builder came along. I gave a session*at the SD West Conference on C++ Builder language extensions and wrote on this topic quite a bit, even if I never wrote a full book on it, being fairly busy with the Delphi side.* To celebrate C++Builder 25th anniversary, I decided to publish one of my papers on the language exactly as it. This is from SD 97 West conference material. Not everything is still accurate after 25 years, but most of the general description remains valid. But rather than edit to today's languages, I felt it was nice to keep it exactly as it was originally written. Hope you enjoy reading it. C++ Builder Language Extensions*(or: Delphi Programming in C++) by Marco Cantu' When Delphi was first released many programmers complained: "Why isn't it based on the C++ language?". Some of them (including myself) replied: "Simply because this is not possible, due to many missing features of the C++ language". Now that Borland has released C++Builder, or Delphi for C++, you might think I'm going to start finding excuses for my wrong statement. I'm not! I still think my statement is correct. In fact Borland C++ Builder is not based on the ANSI C++ language, but on a heavily extended version of this language, which includes almost all the major features of the Object Pascal language found in Delphi. In this article I'm going to discuss all these language extensions in details, providing some background for programmers with no experience with Delphi. I'm not going to cover all of the new C++Builder features, such as the macros used to implement open-array parameters or the template used to clone Pascal sets. I'm going to focus only on the core features. What is a Property? One of the key elements of visual programming is the idea of the properties of an object. But what is a property? A property is a name with a type, related to some data or data access member functions of a class. One of the basic ideas of OOP is that data members should always be private. Then you'll often write public member functions to get and set that private value. If you later modify the implementation, you can easily change the code of these access functions, and avoid any change in the code which uses the class. Here is a basic example (written following the standard naming conventions for properties, fields, and access functions): private: int fTotal;protected: void __fastcall SetTotal (int Value); int __fastcall GetTotal (); The code of these two member functions (not shown) simply sets or returns the value of the private data member fTotal.. You can use them in the following way: int x = Form1->GetTotal();Form1->SetTotal (x + 5); This is the standard C++ approach. In C++Builder we can define inside a class a property wrapping these access functions: public: __property int Total = { read = GetTotal, write = SetTotal }; This means we can now access to this value in a more uniform way, since we use the same notation both for reading or writing the property: int x = Form1->Total;Form1->Total = x + 5; Depending on its role in a statement, the expression Form1->Total is translated by the compiler in a call to the read or the write function. Actually the compiler can translate a similar expression also into a direct data access. Look at the following property declaration: private: int fValue;public: __property int Value = { read = fValue, write = fValue }; Although this code seems strange at first, it fully demonstrate the role of properties as an encapsulation mechanism. In fact, we can later change the declaration of this property introducing one of two functions instead of direct data access. In this event, we'll need to recompile the code of the classes which use this property, but we won't need to modify it. Obviously access functions are not restricted to read and write private data, but can do anything. One of the typical effect of write functions is to update the user interface. Here is a new version of the Value property, followed by the code of its write function (written following the standard style): __property int Value = { read = fValue, write = SetValue };void __fastcall TForm1::SetValue (int newValue){ if (fValue != newValue) { fValue = newValue; Label2->Caption = "Value = " + IntToStr (fValue); };}; Now if we write the following statements: Value = Value + 2;Value += 2;Value ++; the effect of the change in the data will reflect automatically in the user interface. Notice that this works also with operators. When you apply the increment operator (++) to the property its value is read and the Set method is called. I really think properties are a sound OOP encapsulation mechanism! More Language Rules for Properties Beside the basic structure we have seen, property declarations allow you many alternatives. A fundamental idea is that properties have a data type, but are limited to a given set of data types (including most of the predefined types, strings, and classes). Properties can be read-write as in the examples above, but they can also be read-only or write-only. What is common is to see read-only properties, that is values you can read but not change. An obvious example of a read-only property in the VCL (the Visual Component Library, Delphi's class hierarchy used also by C++Builder) is the Handle property of window based controls. You might want to ask to a control its Windows handle, when you want to call Windows API functions directly, but you are not supposed to change the handle of a window. To define a read-only property you simply omit the write declaration: __property int ReadValue = { read = GetReadValue }; It is possible to declare array properties, that is properties with an index. In this case the required read and write functions have an extra parameter, the index itself. This index must also be used to access to the values, since you cannot access to the array as a whole. The array might not exist as an actual field of the class: when you read the Items of a list box you are actually asking to Windows the value of the item (which is not duplicated inside the TListBox object). It also possible to declare array properties with multiple indexes (see as an example the Pixels property of the TCanvas class of the VCL). The Access Visibility of a Property Properties can be declared using any of the access specifiers, including private (although this makes little sense), protected, and public, plus the two new specifiers: __published and __automated. Read-only properties cannot be published, by the way. A published field or method is not only available at runtime (as a public element) , but also at design-time. The Borland C++Builder compiler generates Delphi-style Runtime Type Identification (RTTI) for published properties of class derived from TPersistent. This type information is used by the design time environment, starting with the Object Inspector, but it is also available to programmers who want to delve into the undocumented TypInfo unit. The published keyword is generally used for properties or events, but forms generally have a published interface including also sub-components and event-handler methods. This information is automatically parsed by the development environment and made available in the Object Inspector even before you compile the program. For example, while the published interface of a component is used by the Object Inspector to show and edit property values at design time, the published interface of a form is used by the Object Inspector to find components compatible with a given data type and member functions compatible with a given event. There is a fifth access specifier, automated, which is used to define a public interface with corresponding OLE Automation type information, making it possible to create OLE Automation Servers. The __automated keyword is used in TAutoObject subclasses. Keep also in mind that the visibility of a property can be extended in derived classes. A protected property, for example, can be re-declared as a published property. To accomplish this you don't need to redefine the property, and only to re-declare it (Borland use the term "hoisted properties" to indicate this behavior). When re-declaring a property you can also change it, for example modifying its default value. Closures and Events When properties are of a "pointer to a member function" data type (also known as a closure)they are called events. But what is a closure? Another addition to the standard C++ language. A closure is a sort of member function pointer. Actually it associates a pointer to a member function with a pointer to a class instance, an object. The pointer to the class instance is used as the this pointer when calling the associated member function. This is the definition of a closure: typedef void __fastcall (__closure *TNotifyEvent)(TObject* Sender); Since in C++ member function pointers are available as well, but seldom used, you might wonder what do we need this awkward stuff for? Closures really matter in the Delphi model. In fact events are closures, holding the value of a member function of the form hosting the related component. For example, a button has a closure, named OnClick, and you can assign a member function of the form to it. When a user clicks on the button, this member function is executed, even if you have defined it inside another class (typically, in the form). Here is what you can write (and C++ Builder usually writes for you): BtnBeep->OnClick = BtnHelloClick;BtnHello->OnClick = BtnBeepClick; The code above exchanges two event handlers at runtime. You can also explicitly declare a variable of a closure type, as the common TNotifyEvent type, and use it to exchange the handlers of two events: TNotifyEvent event = BtnBeep->OnClick;BtnBeep->OnClick = BtnHello->OnClick;BtnHello->OnClick = event; This means you can assign a member function (as BtnHelloClick) to a closure or event both at design time and at runtime. Streaming Properties and Objects The classes of the derived from TPersistent have another important features. You can save objects of these classes to a stream. The VCL doesn't save the internal data of an object, but simply saves the values of all of its published properties (and events). When the object is loaded, the VCL first creates a new object, than re-assigns each of its properties (and events). The most obvious example of objects streaming is the usage of DFM files. These binary files store the properties and the components of a form, and I suggest you to load them in C++Builder editor to study their textual version (you can also use the command line CONVERT.EXE program to turn a DFM file into a TXT file or vice verse) The streaming process can be customized in different ways: adding a default or a stored clause to the declaration of properties, or overriding the DefineProperties method. When you add a property to a component you generally add to the definition a default clause. If the value of a property matches its default value, the property is not saved to a stream along with the other properties of an object. The constructor of the class must initialize the property to the same default value, or this technique won't work. The stored directive, instead, indicates if a property must be saved to file along with the object or not. The stored directive might be followed by a boolean value, or a member function returning a boolean result (so you can choose whether to save the value or not depending on the current status of the object). Finally, the DefineProperties method allows you to create pseudo-properties, adding extra values to the streamed object, and reloading them properly. More RTTI ( dynamic_cast and more) Beside RTTI information generated by the __published keyword, each object has several methods you can use to query itself. These methods are part of the TObject class, the base class of every VCL-based object. You can see the list of the methods of the class TObject (including static member functions) in Table 1. Table 1: TObject member functions // public member functions TObject Free ClassType CleanupInstance FieldAddress AfterConstruction BeforeDestruction Dispatch DefaultHandler FreeInstance ~TObject // static public members function InitInstance ClassName ClassNameIs ClassParent ClassInfo InstanceSize InheritsFrom MethodAddress MethodName To check if a class is of a given type, you can use the ClassType method. This is quite common with the Sender parameter of an event handler. This parameters refers to the object which has generated the event, but is of the generic TObject type. When you associate the same method to events of different objects, you might want to check the type of the object which has caused the event: if (Sender->ClassType() == __classid(TButton)) Beep(); The __classid is another keyword added to C++Builder. It returns the metaclass of the object (see the next section). The alternative (not always valid) is to use the Sender parameter of an event handler and cast it to a given data type, using the standard C++ dynamic_cast technique. This makes sense if we know the data type or that of a common ancestor class, as in this case: TWinControl* wc = dynamic_cast (Sender);if (wc != 0) wc->Left = wc->Left + 2; As I've mentioned, beside these RTTI capabilities, Delphi and C++ Builder share extensive type information, available at runtime for every object of a class derived from TObject and having published fields, properties, or methods. For example, you can access to a property dynamically, by name; you can get the list of the names of the properties of a class; you can get the list of parameters of a closure. This allows programmers to build very powerful add-on tools to the environment, and is used by the system itself as a basis of the visual development tools, starting with the Object Inspector (I've actually built a runtime clone of the Object Inspector). MetaClasses and Virtual Constructors Delphi introduced the concept of class reference, a pointer to the type information of a class. In C++ Builder this has been mapped into the idea of a TMetaclass class, and the TClass type is nothing but a pointer to this metaclass. In Delphi TClass has a similar role but a different definition (although the two implementations are totally compatible). The methods available for a metaclass correspond exactly to the static methods of the TObject class (in fact, there is no TMetaclass class in Delphi, but class reference, which can use the static methods of TObject directly). Once you have a TClass variable, you can assign to it a class, extracted from the object (with the ClassType function) or obtained from the class itself by using the __classid keyword. Here is an example: TClass myclass = __classid (TEdit); You can use a metaclass almost as you use a class reference in Delphi. What is not possible in C++Builder is to create a new object based on a metaclass. This is odd: C++Builder allows you to define virtual constructors, but then provides no way of calling them, unless you call a Delphi method. This is what happens when you define a new component in C++Builder and then let the VCL handle it (for example, when you load a component from a stream its virtual constructor is called). To create an object from a class reference we need to add a simple Pascal unit to our C++Builder application. Here is the code of the Pascal function we can use: function DynCreate (myclass: TComponentClass; Owner: TComponent): TComponent;begin Result := myclass.Create (Owner);end; We can use this function is a C++Builder application. This is the plain code you can use to create a component of a given class at runtime, and place it inside a ScrollBox control: void __fastcall TForm1::BtnNewEditClick(TObject *Sender){ TEdit* edit = new TEdit (this); edit->Parent = ScrollBox1; edit->Left = random (ScrollBox1->Width); edit->Top = random (ScrollBox1->Height); edit->Text = "Edit" + IntToStr (++Counter);} This is the code you can use to create a component of the same type of the Sender object: void __fastcall TForm1::OnDynNew(TObject *Sender){ TClass myclass = Sender->ClassType(); // illegal: // TComponent* Comp = new myclass (this); TComponent* Comp = DynCreate (myclass, this); TControl* Control1 = dynamic_cast (Comp); if (Control1 != 0) { Control1->Parent = ScrollBox1; Control1->Left = random (ScrollBox1->Width); Control1->Top = random (ScrollBox1->Height); Control1->Name = "Comp" + IntToStr (++Counter); }}; Conclusion As we have seen, Borland added many news features to the C++ language in C++ Builder to make this language compatible with Object Pascal, and tailored for component-based programming. Extending C++ this way may seem a little awkward, since we are trying to map the constructs of a language into another language. However, the easy of use of the environment and the visual development are probably worth this extra complexity, which most of the times remains behind the scenes. At the same time C++ Builder retains full compatibility with the ANSI C++ standard, including templates and the STL (to name two features not available to Delphi programmers), and allows you to mix VCL-based code with MFC or OWL code, although this makes sense only if you have existing applications. The VCL library, in fact, allows you to leverage visual programming and work at a higher abstraction than the typical C++ Windows class libraries. There are many other differences between Delphi and C++Builder, but the implementation of properties is the feature with the highest impact in term of C++ language extensions. Most of the other issues (message maps, sets, and open array parameters, to name just a few) have been solved using existing C++ keywords and language features. Marco Cantu'; is a freelance writer and consultant, based in Italy. He has written programming books on both C++ and Delphi, translated in 10 languages worldwide. He contributes to several magazines, enjoys speaking at conferences, and teaches advanced Delphi and C++Builder seminars worldwide. You can reach him at 100273.2610@ compuserve.com or http:// ourworld. compuserve. com/ homepages /marcocantu. Needless to say the Compupserve contact information and web site above are not valid any more, but I decided to leave the article as it was originally published including my bio. Weiterlesen... |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |