ספירת דפים בארכיון סרוק של 1.4 GB אמורה להיות זולה. קרא ל-LoadFromFile על הקובץ הזה והיא מפסיקה להיות זולה: HotPDF מנתח את נתוני ההפניה הצולבת ובונה אובייקט בזיכרון עבור כל אחד ממאות אלפי האובייקטים העקיפים של המסמך, ועובד 32-ביט פוגע בתקרת מרחב הכתובות של 2 GB איפשהו באמצע הניתוח הזה. הפעולה שרצית, ספירת הדפים, מעולם לא הייתה צריכה שום אובייקט מאלה. היא הייתה צריכה את עץ הדפים ולא יותר. הפער הזה, בין מה שעבודה מבקשת לבין מה שטעינה מלאה מספקת, הוא הסיבה כולה לקיום ה-Direct File API
ה-Direct File API מעניק ל-Delphi ו-C++Builder גישה ברמת קובץ ל-PDF: ספירות דפים, עותקים, פענוח, הוספות מצטברות, הכול קורא מהדיסק מה שנדרש בפועל במקום לבנות מחדש את מודל המסמך כולו בזיכרון RAM. המיומנות היא התאמת כל עבודה לרמה הקלה ביותר שיכולה לענות עליה. תקבל את ההתאמה נכון ושירות ישמור זיכרון שטוח על כל גודל קלט. תטעה וקובץ גדול מדי ראשון יפיל את העובד
מה טעינה מלאה עולה לך
LoadFromFile אינה האויבת. היא מרוויחה את הזיכרון שלה: ברגע שהעץ נמצא בזיכרון RAM יש לך גישה אקראית לכל דף ולכל אובייקט, שזה בדיוק מה ש-InsertPagesFromDocument, MovePage וסריאליזציה מחדש דרך SaveLoadedDocument דורשים. אין קיצור דרך לארגון מחדש אמיתי; צריך להחזיק את המסמך כדי לסדר אותו מחדש
הצרות מתחילות כאשר גדלי הקלט אינם בשליטתך. העלאות לקוחות, פלט סורק וארכיונים מעשור שעבר מתעלמים ממה שנניח קורפוס הבדיקה שלך. טען כל קלט ללא תנאי ותקרת הזיכרון שלך נקבעת על ידי הקובץ הגדול ביחיד שמישהו אי פעם יגיש. זמן הניתוח עוקב אחרי ספירת האובייקטים, וזיכרון השוהה מתייצב על כמה פעמים גודל הקובץ לאחר שמבני האובייקטים ושלבי הפענוח נלקחים בחשבון, כך שגיגה-בייט על הדיסק יכולה להיות ריבוי גיגה-בייטים שוהה
הידור מחדש ל-64-ביט מרים את תקרת מרחב הכתובות אבל משאיר את החשבון שלם. העובד עדיין שורף שניות של מעבד וכפול הקובץ בזיכרון RAM כדי לענות על שאלה שמבנה הקובץ עצמו יכול היה לענות עליה במילישניות. תחת מקביליות המתמטיקה הופכת עוינת: ארבע טעינות גדולות הרצות בו-זמנית חולקות תקציב זיכרון אחד, ותפוקה מתמוטטת בדיוק כאשר התור עמוק ביותר ואתה הכי פחות יכול להרשות לעצמך זאת
קריאת קובץ דרך ידית
הרמה לקריאה בלבד פותחת קובץ כידית, עונה על שאלות מבניות לגביו, וסוגרת אותו. ללא עץ אובייקטים, ללא עיבוד דפים, ללא זיכרון שגדל עם הקלט
var
Pdf: THotPDF;
Handle, PageCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Handle := Pdf.DAOpenFileReadOnly('archive-2026-06.pdf', '');
if Handle > 0 then
try
PageCount := Pdf.DAGetPageCount(Handle);
RouteByPageCount('archive-2026-06.pdf', PageCount);
finally
Pdf.DACloseFile(Handle);
end;
finally
Pdf.Free;
end;
end;
שלוש הרגלים שומרים על רמה זו ישרה. ראשית, בדוק את ערך ההחזרה. ידית לא-חיובית פירושה שהפתיחה נכשלה, וירי של DAGetPageCount על ידית מתה הוא סוג הבאג שנשאר נסתר עד ליום שלקוח שולח קובץ פגום. שנית, צמד כל פתיחה מוצלחת עם DACloseFile בתוך בלוק finally; שירות שמדליף ידיות לא קורס, הוא פשוט רקוב, שזה גרוע יותר. שלישית, כבד את מה שפרמטר הסיסמה עושה בפועל. DAOpenFileReadOnly מקבל אחת, אבל עבור קלטים מוצפנים הוא נופל בשקט לניתוח מלא כדי לקרוא את ספירת הדפים, כך שהאחריות לזיכרון שטוח מתאיידת. הפנה קבצים מוגנים דרך DecryptFile תחילה ושאר הצינור נשאר זול
אותו probe משמש גם כשער תיוג. קבצים מגיעים עם תיוג שגוי, חצי-מועלים, או שונה שמם מפורמט אחר לגמרי, ובדיקת DAOpenFileReadOnly דוחה את כולם בפתח תוך מילישניות, עם השגיאה מוצמדת לקובץ הפוגע. החלופה היא לתת לקובץ זבל לרכב עמוק לתוך עובד תור ולהתפוצץ שם, שבו פתרון המחרוזת על מה גרם לזה יכול לעלות אחר צהריים
העתקה, פענוח והצפנה של קבצים שלמים
הרמה השנייה מזיזה ומשנה קבצים שלמים מבלי לחשוף את הפנימה שלהם. אלה הקריאות שצינורות הקליטה נשענות עליהן ביותר
// Structural copy: validate-and-move without parsing the object tree
Status := Pdf.DACopyFile('incoming\statement.pdf', 'verified\statement.pdf');
LogDirectFileStatus('copy', Status);
// Decrypt while copying: the Direct File route into protected inputs
Status := Pdf.DecryptFile('incoming\protected.pdf',
'verified\plain.pdf', 'batch-password');
LogDirectFileStatus('decrypt-copy', Status);
// Encrypt while copying: protect an output without a full load
Status := Pdf.EncryptFile('verified\statement.pdf',
'outbound\statement.pdf', 'owner-secret', '', aes256, [prPrint]);
LogDirectFileStatus('encrypt-copy', Status);
כל קריאה מרוויחה את מקומה. DACopyFile היא העותק המאומת מספריית הסגר לאחסון מנוהל: היא פותחת ומוסיפה לאינדקס את מבנה ה-PDF תוך כדי, כך שקלט קטוע או לא-PDF נכשל כאן ישירות ולא שלושה שלבים במורד הזרם. DecryptFile כותבת עותק מפוענח לאורך נתיב שכתוב מחדש ישיר של AES-256 שמדלג על עץ האובייקטים כאשר הקלט מאפשר זאת, מקבילה הקבצים הגדולים לזרם הפענוח-וחידוש-שמירה בזיכרון המכוסה במאמר על הצפנת AES-256. EncryptFile מריצה את אותה תנועה הפוך, מיישמת הגנת סיסמה במהלך עותק ברמת קובץ עם פרמטרי סוג מפתח והרשאות שהנתיב בזיכרון כבר משתמש בהם
הוספת שינויים במקום כתיבה מחדש
עדכון מצטבר, המוגדר ב-ISO 32000-1 §7.5.6, הוא הרמה השלישית. הבייטים המקוריים נשארים היכן שהם על הדיסק, וכל אובייקטים חדשים או שונים מצורפים אחריהם, ואחריהם קטע הפניה צולבת חדש שמשרשר חזרה למקורי. עבור ארכיון של 900 MB שצריך עמוד בודד מוסף, עלות הכתיבה היא הדלתא, לא הקובץ כולו
// Append an audit page to a large archive without rewriting it
Pdf.BeginIncrementalUpdate('archive-2026-06.pdf');
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Processed by intake service 2026-06-11');
Pdf.SaveIncrementalUpdate('archive-2026-06-stamped.pdf'); // original bytes + delta
שתי נקודות משמעת חשובות כאן. BeginIncrementalUpdate חייב להצביע על הקובץ המקורי, מכיוון שנתוני ההפניה הצולבת המצורפים מתייחסים לקיזוזי בייטים בתוכו. והמודל הוא append-only בעיצובו: כל שמירה מצטברת מגדילה את הקובץ, לעולם לא מכווצת אותו. מסמך המוחתם כל לילה יתנפח ללא גבול עד לסריאליזציה מחדש תקופתית, טעינתו וכתיבתו חזרה דרך SaveLoadedDocument, שדוחסת אותו למטה. אותה טבע append-only הוא מה שהופך עדכון מצטבר לדרך הבטוחה היחידה לגעת במסמך חתום דיגיטלית, אילוץ הנבחן במאמר על חתימות דיגיטליות ו-PAdES. המנגנון הבסיסי של ההפניה הצולבת מקבל טיפול משלו במאמר על זרמי אובייקטים ועדכונים מצטברים
יש מלכודת בשמירות append-only שחומקת מרוב ביקורות. הבייטים המקוריים נשארים בקובץ, קריאים לכל מי שמוכן להסתכל. עדכון מצטבר שמ"מחליף" דף לא מוחק את הישן; הוא מחליף אותו בגרסה הנוכחית בעוד הגרסה הקודמת יושבת שם, ניתנת לשחזור במלואה. לכן עדכונים מצטברים הם הכלי הלא נכון להסרת תוכן רגיש. כדי להפיל היסטוריה שנמען לעולם לא אמור לראות, אתה צריך סריאליזציה מחדש מלאה: LoadFromFile ואחריו SaveLoadedDocument, שכותב רק את המצב הנוכחי ומשאיר את הגרסאות הקבורות מאחור
התאמת הרמה לפעולה
לוגיקת הבחירה קצרה מספיק כדי להחזיק בראש, ומשתלם לקודד אותה כהחלטת ניתוב מפורשת בראש צינור במקום לתת לכל עבודה לאלתר נתיב משלה. הפעולה שאתה צריך מחליטה את הרמה:
- ספירה, בדיקה או סיווג פותח ידית:
DAOpenFileReadOnly,DAGetPageCount,DACloseFile - הזזת, פענוח או הצפנת קובץ שלם נשאר ברמת קובץ עם
DACopyFile,DecryptFileאוEncryptFile - ארגון מחדש של דפים או מיזוג מסמכים צריך את הטעינה המלאה:
LoadFromFile, ואזInsertPagesFromDocumentאוMovePage, ואזSaveLoadedDocument - הוספת דלתא קטנה לקובץ ענק או חתום קורא ל-
BeginIncrementalUpdateושומר
צינורות מעורבים עושים טוב לשים סף גודל לפני נתיב הטעינה המלאה. שלח כל דבר מעבר לכמה מאות מגה-בייטים דרך רמות ה-Direct File, ושמור את הטעינה המלאה לארגון מחדש אמיתי על עובד 64-ביט עם תקציב זיכרון אמיתי. הסף הופך קריסה אזל-מזיכרון להחלטת ניתוב שאפשר לראות ולכוון
לא משנה איזו רמה מטפלת בעבודה, כתוב את הפלט שלה לשם זמני ושנה שם למיקום הסופי רק לאחר שהתוצאה מאומתת. קובץ חצי-כתוב שיושב תחת השם הסופי נראה בדיוק כמו טוב לשלב הבא של הצינור, וקריאות ה-Direct File הופכות את הבדיקה לזולה: אישור שפלט טוב הוא probe ידית של שורה אחת
ה-Direct File API מגיע כחלק מHotPDF Component עבור Delphi ו-C++Builder; דף המוצר מקשר את הפניית הפונקציות המלאה, כולל קריאות העדכון המצטבר שהוצגו כאן