Klaidos pranešimas teigia Please load the document before using BeginDoc, ir jis beveik visada pasirodo antrą kartą. Pirmasis dokumentas įrašomas puikiai. Tada to paties THotPDF egzemplioriaus (angl. instance) prašoma pradėti antrąjį, BeginDoc išmeta išimtį, o pranešimas nurodo į dokumento įkėlimą, o tai yra priešinga tam, ką kodas bando padaryti. Simptomo ir pranešimo neatitikimas yra tai, dėl ko ši klaida įsimena. Tikroji tema yra komponento gyvavimo ciklas, ir kai tai tampa aišku, klaida nustoja būti paslaptinga

Pakartotinis komponento naudojimas reikalauja atskirti dokumento būseną nuo puslapio būsenos. Prieš atidarydami kitą failą užbaikite ankstesnį dokumentą, atlaisvinkite laikinuosius objektus ir atstatykite nustatymus, kurie nėra automatiškai grąžinami į pradines reikšmes
Naujas egzempliorius kiekvienam įvesties failui yra paprasčiausia taisyklė, kai svarbus patikimumas. Ji pašalina paslėptą ankstesnio dokumento būseną ir leidžia aiškiai susieti išteklių gyvavimo ciklą su vienu išvesties failu
Jei egzempliorių būtina naudoti pakartotinai, valymo veiksmus atlikite prieš naują aktyvavimą ir po jo patikrinkite, kad dokumentas nebeturi atvirų failo rankenų, puslapių ar laikinų šriftų objektų
Šis modelis taip pat supaprastina lygiagretų apdorojimą: kiekviena gija turi savo komponento būseną, todėl vienas dokumentas negali netyčia perrašyti kito dokumento nustatymų
„THotPDF“ egzempliorius yra vienas dokumentas, o ne dokumentų gamykla
Gundantis psichologinis modelis yra tas, kad THotPDF yra paslaugos objektas (angl. service object), kurį paleidžiate vieną kartą ir maitinate dokumentais, taip, kaip galėtumėte laikyti atidarytą duomenų bazės ryšį ir vykdyti užklausą po užklausos per jį. Taip nėra. Egzempliorius modeliuoja vieną kuriamą dokumentą, o jo vidinė būsenų mašina (angl. state machine) daro prielaidą, kad ji pereina kelią vieną kartą: nuo tuščio, per atidarytą dokumentą, iki išsaugoto failo. BeginDoc atidaro tą kelią ir pažymi egzempliorių kaip turintį vykdomą dokumentą. EndDoc serijuoja viską į FileName ir jį uždaro. Vėl iškvietus BeginDoc tame pačiame užbaigtame egzemplioriuje prašoma jo iš naujo įeiti į būseną, kurios jis niekada švariai nepaliko, ir suveikia apsauga, kurios pranešimas atsitiktinai užsimena apie įkėlimą, nes viduje sąlygos „pasiruošęs pradėti“ ir „turi įkeltą dokumentą“ yra tikrinamos kartu
Taigi pranešimas yra klaidinantis, bet apsauga atlieka savo darbą. Ji atsisako leisti jums pradėti naują dokumentą ant komponento, kuris vis dar tiki, kad yra dokumento viduryje. Sprendimas nėra nugalėti apsaugą. Tai yra nustoti pakartotinai naudoti išeikvotą egzempliorių
Gyvavimo ciklas, tokia tvarka, kokia jis turi įvykti
Kiekvienas dokumentas, kurį „HotPDF“ rašo nuo nulio, seka tuos pačius keturis taktus, ir dėl tvarkos nesiderama. Create paskirsto komponentą. BeginDoc atidaro dokumentą ir užfiksuoja struktūrinius pasirinkimus, todėl viskas, kas turi įtakos visam failui (puslapio dydis, glaudinimas, šifravimas, išvesties failo pavadinimas), turi būti nustatyta tarp Create ir BeginDoc. Tada jūs piešiate. Tada EndDoc įrašo baitus į diską. Free atlaisvina egzempliorių. Piešimo iškvietimai, pateikti prieš BeginDoc, neturi puslapio, kuriame galėtų nusileisti; viso dokumento savybės, priskirtos po jo, yra ignoruojamos be jokių skundų
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice.pdf';
Pdf.BeginDoc; // opens the document
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Invoice 2026-042');
Pdf.EndDoc; // writes invoice.pdf, closes it out
finally
Pdf.Free; // one instance, one document
end;
end;
Skaitykite tai kaip darbo vienetą. Vienas Create, vienas BeginDoc, vienas EndDoc, vienas Free, vienas failas diske. Tą akimirką, kai norite antro failo, pradedate naują darbo vienetą, o tai reiškia naują egzempliorių
Ką turėtų reikšti „pakartotinis naudojimas“: naujas egzempliorius kiekvienam failui
Lūžtanti versija bando būti taupi paskirstydama: sukonstruokite komponentą vieną kartą, pereikite per partiją (angl. batch), iškvieskite BeginDoc ir EndDoc ciklo viduje. Antroji iteracija išmeta išimtį. Veikianti versija traktuoja kiekvieną išvestį kaip savo trumpalaikį objektą, o komponento sukūrimo paskirstymo kaina yra nereikšminga lyginant su PDF išdėstymo ir serijavimo darbu, todėl nėra ko sutaupyti kaupiant egzemplierių
procedure WriteBatch(const Names: TArray<string>);
var
I: Integer;
Pdf: THotPDF;
begin
for I := 0 to High(Names) do
begin
Pdf := THotPDF.Create(nil); // new instance each pass
try
Pdf.FileName := Names[I] + '.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 12);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Statement for ' + Names[I]);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
end;
try/finally blokas, esantis ciklo viduje, yra ta dalis, kurią verta ginti peržiūroje. Jei BeginDoc arba bet koks piešimo iškvietimas išmeta išimtį įpusėjus vienam dokumentui, tos iteracijos egzempliorius vis tiek atlaisvinamas prieš prasidedant kitai, todėl vienas blogas įrašas nepalieka pusiau sukonstruoto komponento ir neapnuodija likusio vykdymo. Ištraukite Create virš ciklo, kad „optimizuotumėte“, ir jūs grįšite prie pradinės klaidos, dabar dėvinčios partijos ciklo drabužius
Esamo failo modifikavimas yra kitas įėjimo taškas
Yra antrasis „pakartotinio naudojimo“ skaitymas, kuris yra visiškai teisėtas: nenorite tuščio dokumento, norite atidaryti jau esantį PDF ir jį pakeisti. Tas kelias visiškai neina per BeginDoc, ir būtent todėl klaidos pranešime įvardijamas įkėlimas. Jūs įkeliate failą, redaguojate jį ir išsaugote bet kokiu pasirinktu pavadinimu
var
Pdf: THotPDF;
PageCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
PageCount := Pdf.LoadFromFile('contract.pdf');
if PageCount > 0 then
begin
Pdf.CurrentPage.SetFont('Arial', [fsBold], 10);
Pdf.CurrentPage.TextOut(40, 30, 0, 'REVIEWED');
Pdf.SaveLoadedDocument('contract-reviewed.pdf');
end;
finally
Pdf.Free;
end;
end;
LoadFromFile grąžina puslapių skaičių, o nulinė ar mažesnė reikšmė reiškia, kad įkėlimas nepavyko, todėl verta patikrinti prieš paliečiant CurrentPage. Poravimas yra svarbus: dokumentas, kurį atidarėte naudodami LoadFromFile, išsaugomas naudojant SaveLoadedDocument, o ne su BeginDoc/EndDoc pora, kuri priklauso dokumentams, kuriuos kuriate nuo nulio. Šių dviejų sumaišymas yra labiausiai paplitęs būdas supainioti tą pačią būsenų mašiną, kuri sukėlė pradinę klaidą. Laikykite šiuos du srautus mintyse atskirus: BeginDoc ... EndDoc kuria, LoadFromFile ... SaveLoadedDocument redaguoja
Failo užrakto problema yra tikra, ir atsakymas nėra žudyti peržiūros programos langus
Pakartotinio naudojimo klaida dažnai keliauja su antruoju skundu, ir jie abu susipainioja, nes iškyla toje pačioje failo regeneravimo darbo eigoje. Naudotojas atidaro PDF failą, kurį ką tik sukūrėte, palieka jį atidarytą „Acrobat“ ar „Foxit“ programoje, tada suaktyvina perkonstravimą (angl. rebuild). EndDoc bando rašyti į tą patį kelią, operacinė sistema atsisako, nes peržiūros programa laiko skaitymo dalį, kuri blokuoja rašytojus, ir jūs gaunate prieigos atsisakymo gedimą. Tai yra tikrai „Windows“ failų užrakinimo (angl. file-locking) problema, o ne komponento būsenos problema, ir ji nusipelno tikro atsakymo, o ne problemos apėjimo (angl. workaround)
Problemos apėjimas, kuris cirkuliuoja, išvardijant aukščiausio lygio langus ir siunčiant WM_CLOSE bet kam, kieno pavadinimas atrodo kaip PDF peržiūros programa, yra klaidingas instinktas. Jis peržengia procesų ribas, kad uždarytų langus, kurie nepriklauso jūsų programai, atspėja peržiūros programas pagal pavadinimo tekstą ir gali išmesti naudotojo neišsaugotas anotacijas neatsiklausęs. Elkitės su visu šiuo požiūriu kaip su blogo kodo kvapu (angl. code smell). Patikimas sprendimas yra niekada nerašyti į kelią, kurį gali laikyti kitas procesas. Serijuokite į laikiną failą tame pačiame kataloge, tada sukeiskite jį vietoje naudodami atominį pervardijimą (angl. atomic rename), kai EndDoc pavyksta. Jei peržiūros programoje vis dar atidarytas senasis failas, pervardijimas arba pavyksta švariai, arba garsiai nepavyksta, ir jūs pateikiate aiškų pranešimą, o ne kovojate su užraktu
Didelės apimties serveriui, kuris nuolat regeneruoja dokumentus, švaresnė disciplina yra rašyti kiekvieną išvestį unikaliu pavadinimu (laiko žyma (angl. timestamp) arba užduoties ID), kad du paleidimai niekada nesivaržytų dėl vieno kelio, ir leisti atskirai saugojimo politikai išvalyti senus failus. Bet kokiu atveju principas yra tas pats: projektuokite taip, kad failas, kurį rašote, rašymo momentu priklausytų tik jums. Užraktas dingsta ne todėl, kad per prievartą uždarėte langą, o todėl, kad niekas kitas neliečia baitų
Sprendimo forma
Išnarpliokite šias dvi problemas iki jų šaknų ir pamatysite, kad abi jos yra apie ribų gerbimą. Būsenų mašinos klaida nori, kad gerbtumėte egzemplioriaus ribą: vienas THotPDF, vienas dokumentas, tada paleiskite jį ir sukurkite kitą. Failo užrakto klaida nori, kad gerbtumėte failo ribą: rašykite ten, kur niekas kitas neskaito, tada perkelkite rezultatą į vietą. Nė vienai iš jų nereikia pataisyti bibliotekos ar rašyti skriptų darbalaukiui. Abi jos išsprendžiamos traktuojant kiekvieną dokumentą kaip savarankišką darbo vienetą, sukurtą iš naujo, parašytą švariai ir atlaisvintą, o tai yra tas pats modelis, kuris daro likusią komponento dalį nuspėjamą
Čia parodyti BeginDoc, EndDoc, LoadFromFile ir SaveLoadedDocument iškvietimai yra „HotPDF“ komponento, skirto Delphi ir C++Builder, dalis