Τεχνικό Άρθρο

Δημιουργία και Απελευθέρωση Στοιχείου Ελέγχου HotPDF Δυναμικά στο C++Builder

Η ρίψη (dropping) ενός THotPDF σε μια φόρμα κατά το χρόνο σχεδίασης (design time) είναι μια χαρά για ένα γρήγορο πρωτότυπο (prototype), αλλά συνδέει (ties) το στοιχείο (component) με τη διάρκεια ζωής (lifetime) της φόρμας, κάτι που σπάνια επιθυμεί ο κώδικας παραγωγής (production code). Μια γεννήτρια αναφορών (report generator) που εκτελείται μία φορά ανά κλικ κουμπιού, ένα νήμα υπηρεσίας (service thread) που ομαδοποιεί εξαγωγές (batch exports) κατά τη διάρκεια της νύχτας, μια βοηθητική κλάση (helper class) που δεν έχει καθόλου φόρμα: σε καθεμία από αυτές τις περιπτώσεις θέλετε το στοιχείο ελέγχου να υπάρχει ακριβώς για τη διάρκεια μιας εργασίας PDF και στη συνέχεια να εξαφανίζεται. Αυτό σημαίνει εκχώρηση κατά τον χρόνο εκτέλεσης (runtime allocation), και αλλάζει δύο πράγματα που αξίζει να κατανοήσετε πριν γράψετε την πρώτη γραμμή: ποιος είναι ο κάτοχος (owner) του αντικειμένου και πώς εκτελείται ο καθαρισμός (cleanup) όταν κάτι πάει στραβά

Σημασιολογία κατόχου (Owner semantics) στο VCL

Κάθε κατασκευαστής (constructor) στοιχείου VCL παίρνει μια παράμετρο Owner τύπου TComponent*. Το να περάσετε (Passing) this (τη φόρμα) καταχωρεί το νέο αντικείμενο στη λίστα κατεχόμενων στοιχείων της φόρμας, επομένως εάν η φόρμα καταστραφεί ενώ το στοιχείο είναι ακόμα ζωντανό, το VCL το απελευθερώνει (frees) αυτόματα. Το να περάσετε nullptr σημαίνει κανένας κάτοχος: αναλαμβάνετε την αποκλειστική ευθύνη για τον δείκτη (pointer) και τίποτα δεν θα τον καθαρίσει για εσάς εάν μια εξαίρεση (exception) ξετυλίξει τη στοίβα (unwinds the stack) πριν από το ρητό (explicit) delete σας

Για μια εξαγωγή μιας χρήσης (one-shot export) που ολοκληρώνεται μέσα σε μία μόνο συνάρτηση, και οι δύο επιλογές λειτουργούν, αλλά οι δύο έχουν διαφορετικούς τρόπους αποτυχίας (failure modes). Με το this ως κάτοχο (owner), μια διαρροή (leak) είναι αδύνατη, αρκεί η φόρμα τελικά να κλείσει· με το nullptr, ο δείκτης πρέπει να φτάσει σε ένα μπλοκ __finally. Στην πράξη, το μοτίβο nullptr συν __finally είναι ελαφρώς πιο καθαρό για αντικείμενα μικρής διάρκειας ζωής επειδή καθιστά το όριο της διάρκειας ζωής ορατό με μια ματιά και αποφεύγει τη συσσώρευση (accumulating) στη φόρμα κατεχόμενων αντικειμένων που προορίζονταν να είναι προσωρινά

Δομή ασφαλής για εξαιρέσεις (Exception-safe structure)

Η δημιουργία PDF μπορεί να αποτύχει για λόγους που δεν έχουν καμία σχέση με το API: ο κατάλογος εξόδου (output directory) είναι μόνο για ανάγνωση (read-only), λείπει ένα αρχείο γραμματοσειράς, μια ροή (stream) αδειάζει πρόωρα, ή δεδομένα που παρέχονται από τον καλούντα χτυπούν ένα όριο μήκους. Όποια και αν είναι η αιτία, η διαδρομή καθαρισμού (cleanup path) πρέπει να τρέξει. Ο ιδιωματικός τρόπος (idiomatic way) του C++Builder για να το εγγυηθεί αυτό είναι το try/__finally:

void __fastcall TMainForm::ExportPdfBtnClick(TObject *Sender)
{
    // Pass nullptr to take ownership of the instance
    THotPDF *Pdf = new THotPDF(nullptr);
    try
    {
        Pdf->FileName = "runtime-export.pdf";
        Pdf->Compression = cmFlateDecode;
        Pdf->FontEmbedding = true;
        
        Pdf->BeginDoc();
        
        Pdf->CurrentPage->SetFont("Arial", TFontStyles(), 12, DEFAULT_CHARSET);
        Pdf->CurrentPage->TextOut(72, 720, 0, "Created dynamically at runtime.");
        
        Pdf->EndDoc();
    }
    __finally
    {
        // Executes whether the block succeeded or raised an exception
        delete Pdf;
    }
}

Λίγα πράγματα σε αυτή τη λίστα αξίζει να επισημανθούν. Ο κάτοχος είναι nullptr, καθιστώντας τη διάρκεια ζωής (lifetime) ρητή. Η συμπίεση (Compression) και η ενσωμάτωση γραμματοσειράς (FontEmbedding) ορίζονται πριν από το BeginDoc: και τα δύο είναι επιλογές σε επίπεδο εγγράφου που το HotPDF δεσμεύει (commits) όταν ανοίγει το έγγραφο, και η εκχώρησή τους εκ των υστέρων δεν έχει κανένα αποτέλεσμα. Το TextOut παίρνει συντεταγμένες σε σημεία (points) μετρούμενα από την κάτω αριστερή γωνία της σελίδας, με το Υ να αυξάνεται προς τα πάνω· το ζεύγος 72, 720 τοποθετεί κείμενο κοντά στην επάνω αριστερή πλευρά μιας σελίδας μεγέθους letter με αριστερό περιθώριο μιας ίντσας. Το delete Pdf στο μπλοκ __finally εκτελείται ανεξάρτητα από το εάν το BeginDoc, η σχεδίαση (drawing) ή το EndDoc ήγειραν (raised) μια εξαίρεση ή όχι

Αποφύγετε να καλέσετε οποιαδήποτε μέθοδο στο Pdf μετά το delete. Εάν ο δείκτης (pointer) είναι αποθηκευμένος σε μια μεταβλητή μέλους (member variable), ορίστε την σε nullptr αμέσως μετά τη διαγραφή ώστε οποιαδήποτε τυχαία μετέπειτα πρόσβαση να παράγει μια καθαρή κατάρρευση (crash) παρά σιωπηλή καταστροφή (silent corruption)

Διαμόρφωση Έργου (Project configuration)

Το C++Builder εντοπίζει το THotPDF μέσω ενός συνδυασμού διαδρομών συμπερίληψης (include paths), διαδρομών βιβλιοθήκης (library paths) και μιας οδηγίας pragma (pragma directive). Η δημιουργηθείσα κεφαλίδα (header) βρίσκεται δίπλα στο HPDFDoc.pas στον κατάλογο (directory) πηγαίου κώδικα (source) του HotPDF· προσθέστε αυτόν τον κατάλογο στο Project > Options > C++ Compiler > Include path. Η οδηγία #pragma link "HPDFDoc" λέει στον συνδέτη (linker) να τραβήξει μέσα (pull in) τη μεταγλωττισμένη μονάδα (compiled unit) χωρίς να τη συμπεριλάβει (listing) στο αρχείο του έργου χειροκίνητα. Εάν χρησιμοποιείτε το πακέτο χρόνου εκτέλεσης (runtime package) αντί για στατική σύνδεση (static linking), εγκαταστήστε πρώτα τα πακέτα σχεδίασης (design) και χρόνου εκτέλεσης (runtime) του HotPDF· η pragma εξακολουθεί να ισχύει

Διατηρήστε αμετάβλητο το όνομα της μονάδας HPDFDoc. Το C++Builder παράγει το όνομα της κεφαλίδας (header name) από το όνομα της μονάδας Pascal, επομένως η μετονομασία του αρχείου ή η χρήση ψευδωνύμου διαδρομής (path alias) στην pragma χαλάει αθόρυβα την αναζήτηση (lookup)

Εμβέλεια (Scoping) και εργασίες (jobs) πολλαπλών εγγράφων

Για μια μεμονωμένη εξαγωγή που πυροδοτείται (triggered) από ενέργεια του χρήστη, μια τοπική μεταβλητή (local variable) με εμβέλεια τον χειριστή κουμπιού (button handler) είναι η σωστή απάντηση: δημιουργείται, χρησιμοποιείται και καταστρέφεται μέσα σε ένα πλαίσιο κλήσης (call frame) και η πρόθεση (intent) είναι προφανής σε όποιον διαβάζει τον κώδικα αργότερα. Η εναλλακτική λύση στο χρόνο σχεδίασης (design-time alternative) δικαιολογείται όταν η ίδια φόρμα (form) οδηγεί μια συνεχή ροή εργασίας (continuous workflow), όπως ένα πάνελ προεπισκόπησης εκτύπωσης (print-preview panel) που επαναδημιουργεί το έγγραφο κάθε φορά που ο χρήστης αλλάζει μια ρύθμιση· σε αυτή την περίπτωση, η διατήρηση του στοιχείου ζωντανού και η επανειλημμένη κλήση BeginDoc/EndDoc είναι λιγότερο ενοχλητική (disruptive) από τη συνεχή (repeatedly) εκχώρηση και απελευθέρωση αντικειμένων σωρού (heap objects)

Για εργασίες ομαδοποίησης (batch jobs) που παράγουν πολλά έγγραφα στη σειρά (sequence), η δημιουργία εμβέλειας (scoping) ενός THotPDF ανά έγγραφο αξίζει την επιβάρυνση εκχώρησης (allocation overhead). Η κατάσταση (State) δεν μεταφέρεται (carry) μεταξύ των εγγράφων εάν δεν υπάρχει αντικείμενο για να τη μεταφέρει, και αυτή είναι μια κατηγορία διακοπτόμενων σφαλμάτων (intermittent bug) που δεν χρειάζεται ποτέ να αποσφαλματώσετε (debug). Εκχωρήστε, δημιουργήστε, διαγράψτε, επαναλάβετε

Μια ιδιότητα (property) που εμφανίζεται σε αρκετά demo του HotPDF είναι η AutoLaunch, η οποία ανοίγει το δημιουργημένο (generated) αρχείο στο πρόγραμμα προβολής (viewer) PDF του συστήματος αμέσως μετά το EndDoc. Είναι χρήσιμη κατά τη σύνταξη του πρώτου προσχεδίου μιας διάταξης (layout). Στην παραγωγή, παραλείψτε την: ανοίξτε ρητά τη διαδρομή εξόδου (output path), επιβεβαιώστε ότι το αρχείο υπάρχει και έχει μέγεθος διάφορο του μηδενός (non-zero size), καταγράψτε (log) το αποτέλεσμα, και αφήστε την καλούσα (calling) ροή εργασίας να αποφασίσει εάν ένα πρόγραμμα προβολής (viewer) είναι σχετικό. Σε μια εργασία ομαδοποίησης (batch job), το AutoLaunch εκκινεί (launches) ένα παράθυρο προβολής ανά έγγραφο και θα μπλοκάρει τη διεργασία (process) σε ορισμένα συστήματα, περιμένοντας να κλείσει το πρόγραμμα προβολής (viewer)

Το στοιχείο THotPDF και όλες οι κλήσεις σχεδίασης (drawing calls) που εμφανίζονται εδώ αποτελούν μέρος του Στοιχείου Ελέγχου HotPDF για το Delphi και το C++Builder