Feilen lyder Please load the document before using BeginDoc, og den dukker nesten alltid opp på andre forsøk. Det første dokumentet skrives helt fint. Deretter blir den samme THotPDF-instansen bedt om å starte et nytt, BeginDoc kaster feil, og meldingen peker på å laste inn et dokument, noe som er det motsatte av det koden prøver å gjøre. Avviket mellom symptomet og meldingen er det som gjør denne vanskelig. Det virkelige temaet er komponentens livssyklus, og når du forstår den, slutter feilen å være mystisk

En THotPDF-instans er ett dokument, ikke en dokumentfabrikk
Den fristende mentale modellen er at THotPDF er et tjenesteobjekt du starter opp én gang og mater dokumenter til, på samme måte som du kan holde en databasetilkobling åpen og kjøre spørring etter spørring gjennom den. Det er det ikke. En instans modellerer et enkelt dokument som bygges, og dens interne tilstandsmaskin bærer antagelsen om at den går veien én gang: fra tom, via et åpent dokument, til en lagret fil. BeginDoc åpner den veien og markerer instansen som å ha et dokument under arbeid. EndDoc serialiserer alt til FileName og avslutter det. Å kalle BeginDoc igjen på den samme ferdige instansen ber den om å gå inn igjen i en tilstand den aldri forlot rent, og sikringen som utløses er den hvis melding tilfeldigvis nevner lasting, fordi internt sjekkes betingelsene "klar til å begynne" og "har et lastet dokument" sammen
Så meldingen er misvisende, men sikringen gjør jobben sin. Den nekter å la deg starte et ferskt dokument oppå en komponent som fortsatt tror den er midt i et dokument. Løsningen er ikke å omgå sikringen. Det er å slutte å gjenbruke en oppbrukt instans
Livssyklusen, i rekkefølgen den må skje
Hvert dokument HotPDF skriver fra bunnen av følger de samme fire slagene, og rekkefølgen kan ikke diskuteres. Create tildeler komponenten. BeginDoc åpner dokumentet og fastsetter strukturvalgene, så alt som påvirker hele filen (sidestørrelse, komprimering, kryptering, utdatafilnavn) må settes mellom Create og BeginDoc. Deretter tegner du. Deretter skriver EndDoc bytene til disk. Free frigjør instansen. Tegnekall plassert før BeginDoc har ingen side å lande på; hel-dokument-egenskaper tildelt etter dette blir ignorert uten klage
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;
Les dette som en arbeidsenhet. Én Create, én BeginDoc, én EndDoc, én Free, én fil på disk. I det øyeblikket du ønsker en andre fil, starter du en ny arbeidsenhet, noe som betyr en ny instans
Hva "gjenbruk" bør bety: en fersk instans per fil
Versjonen som knekker prøver å være nøysom med tildeling: bygg komponenten én gang, løp over en gruppe (batch), kall BeginDoc og EndDoc inne i løkken. Den andre iterasjonen kaster feil. Versjonen som fungerer behandler hvert utdata som sitt eget kortlivede objekt, og tildelingskostnaden ved å opprette en komponent er triviell sammenlignet med arbeidet med å legge ut og serialisere en PDF, så det er ingenting å spare på å hamstre instansen
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 som sitter inne i løkken, er den delen det er verdt å forsvare i en gjennomgang (review). Hvis BeginDoc eller et tegnekall kaster feil halvveis gjennom ett dokument, blir den iterasjonens instans fortsatt frigjort før den neste begynner, slik at en dårlig oppføring ikke strander en halvbygd komponent og forgifter resten av kjøringen. Trekk Create ut over løkken for å "optimalisere", og du er tilbake til den opprinnelige feilen, nå iført en batch-løkke
Modifisering av en eksisterende fil er et annet inngangspunkt
Det er en andre tolkning av "gjenbruk" som er helt legitim: du vil ikke ha et tomt dokument, du vil åpne en PDF som allerede eksisterer og endre den. Den veien går ikke gjennom BeginDoc i det hele tatt, noe som er nøyaktig grunnen til at feilmeldingen navngir lasting. Du laster filen, redigerer den, og lagrer under det navnet du måtte ønske
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 returnerer sideantallet, og en verdi på null eller under betyr at lastingen mislyktes, så det er verdt å sjekke før du rører CurrentPage. Paringen betyr noe: et dokument du åpnet med LoadFromFile lagres med SaveLoadedDocument, ikke med BeginDoc/EndDoc-paret, som tilhører dokumenter du forfatter fra ingenting. Å blande de to er den vanligste måten å forvirre den samme tilstandsmaskinen som produserte den opprinnelige feilen. Hold de to flytene mentalt adskilt: BeginDoc ... EndDoc oppretter, LoadFromFile ... SaveLoadedDocument redigerer
Filsperringsproblemet (file-lock) er reelt, og svaret er ikke å drepe visningsvinduer
Gjenbruksfeilen reiser ofte sammen med en andre klage, og de to flettes sammen fordi de dukker opp i den samme generer-filen-på-nytt-arbeidsflyten. En bruker åpner PDF-en du nettopp produserte, lar den være åpen i Acrobat eller Foxit, og utløser deretter en ny bygging. EndDoc prøver å skrive til den samme stien, operativsystemet nekter fordi visningsprogrammet holder en lesedeling som blokkerer skrivere, og du får en nektet tilgang-feil. Dette er genuint et Windows-filsperringsproblem (file-locking) i stedet for et komponenttilstandsproblem, og det fortjener et ekte svar i stedet for en omgåelse (workaround)
Omgåelsen som sirkulerer, å oppregne vinduer på toppnivå og poste WM_CLOSE til alt hvis tittel ser ut som et PDF-visningsprogram, er det feil instinkt. Den strekker seg over prosessgrenser for å lukke vinduer programmet ditt ikke eier, den gjetter på visningsprogrammer via titteltekst, og den kan kaste bort en brukers ulagrede merknader uten å spørre. Behandle hele den tilnærmingen som en uting (smell). Den pålitelige løsningen er å aldri skrive til en sti en annen prosess kan holde. Serialiser til en midlertidig fil i den samme katalogen, bytt den deretter på plass med et atomisk navnebytte når EndDoc lykkes. Hvis et visningsprogram fortsatt har den gamle filen åpen, vil navnebyttet enten lykkes rent eller feile høylytt, og du viser en tydelig melding i stedet for å kjempe mot sperringen
For en server med høyt volum som kontinuerlig genererer dokumenter på nytt, er den renere disiplinen å skrive hver utdata under et unikt navn (et tidsstempel eller en jobb-id) slik at to kjøringer aldri kjemper om én sti, og la en separat retensjonspolicy rydde opp i gamle filer. Uansett er prinsippet det samme: design slik at filen du skriver er din alene i det øyeblikket du skriver den. Sperringen forsvinner ikke fordi du tvang et vindu lukket, men fordi ingenting annet rører bytene
Formen på løsningen
Skrell de to problemene tilbake til røttene, og de handler begge om å respektere grenser. Tilstandsmaskinfeilen vil at du skal hedre instansgrensen: én THotPDF, ett dokument, gi så slipp på det og lag et nytt. Filsperringsfeilen vil at du skal hedre filgrensen: skriv der ingenting annet leser, flytt deretter resultatet på plass. Ingen av dem krever lapping (patching) av biblioteket eller skripting av skrivebordet. Begge løses ved å behandle hvert dokument som en selvstendig arbeidsenhet, opprettet fersk, skrevet rent, og frigjort, noe som er det samme mønsteret som gjør resten av komponenten forutsigbar
BeginDoc-, EndDoc-, LoadFromFile- og SaveLoadedDocument-kallene som vises her, er en del av HotPDF Component for Delphi og C++Builder