مقاله فنی

بارگذاری کتابخانه بومی PDFium روی هر هدفی

مؤلفه PDFium کتابخانه بومی‌اش را از طریق یک زنجیره جست‌وجوی ثابت و مرتب پیدا می‌کند به‌جای آنکه آن را به لودر سیستم‌عامل بسپارد، چون درخت استقراری که صریح باشد درختی است که می‌توانید دیباگش کنید. روی Windows آن زنجیره دنبال زیرپوشه Win32 یا Win64 می‌گردد که نصب‌کننده از قبل عرضه می‌کند. روی هدف‌های دیگر نام زیرپوشه را از ماکروهای هدف Free Pascal می‌سازد، به‌صورت <cpu>-<os>، تا درخت استقرار دقیقاً مثل درخت یونیت‌های کامپایل‌شده خوانده شود. آن تصمیم آخر باگی را وارد کرد که ارزش کل مقاله را دارد، چون علتش یک حرف بزرگ بود و نشانه‌اش سکوت

زنجیره، به ترتیب

چهار مکان، به ترتیب امتحان می‌شوند، سپس لودر پلتفرم به‌عنوان آخرین چاره. اول چیدمان ترجیحی، یک پوشه DLLs کنار فایل اجرایی که یک زیرپوشه به‌ازای هر هدف دارد. دوم یک چیدمان جایگزین با زیرپوشه هدف مستقیماً کنار فایل اجرایی. سوم چیدمان تخت قدیمی، کتابخانه کنار فایل اجرایی بدون هیچ زیرپوشه‌ای. چهارم، فقط روی Windows، پوشه سیستم، که مراقبت می‌طلبد چون یک فرایند ۳۲بیتی باید در SysWOW64 نگاه کند و یک فرایند ۶۴بیتی در System32، و روی ویندوز ۳۲بیتی اولی وجود ندارد پس جست‌وجو باید عقب‌نشینی کند. فقط بعد از همه این‌ها از لودر خواسته می‌شود خودش جست‌وجو کند

نمودار زنجیره جست‌وجوی کتابخانه بومی PDFium برای دلفی، از زیرپوشه هدف DLLs از طریق چیدمان‌های جایگزین، تخت و پوشه سیستم ویندوز تا لودر پلتفرم
چهار مکان صریح به ترتیب پروب می‌شوند پیش از آنکه از لودر سیستم‌عامل خواسته شود خودش جست‌وجو کند

بیرون از Windows عمداً هیچ گامی برای پوشه سیستم نیست. مسیر جست‌وجوی خود لودر پلتفرم، که با پیکربندی لینکر زمان اجرا و محیط مسیر کتابخانه هدایت می‌شود، از قبل آن زمین را پوشش می‌دهد، و تکثیرش در پاسکال یعنی بازپیاده‌سازی قواعدی که به توزیع بستگی دارند. تشخیص شکست‌ها در زنجیره Windows جداگانه در استقرار DLL مربوط به PDFium و تشخیص شکست‌های بارگذاری پوشش داده شده است

نام زیرپوشه از کجا می‌آید

روی Windows برابر Win32 یا Win64 است، که با bitness فرایند در حال اجرا تصمیم گرفته می‌شود نه سیستم‌عامل، چون همان است که تعیین می‌کند کدام باینری قابل بارگذاری است. در همه‌جای دیگر نام از ماکروهای هدف کامپایلر ساخته می‌شود تا دستگاهی که برای دو معماری بیلد می‌کند دو درخت به‌وضوح جدا تولید کند، و تا پوشه‌ای که کتابخانه بومی را نگه می‌دارد با همان نام کنار پوشه‌ای که یونیت‌های کامپایل‌شده را نگه می‌دارد بنشیند

function BuildDllSubDir(UseV8: Boolean): string;
begin
{$IFDEF MSWINDOWS}
  if IsWin64 then
    Result := 'Win64'
  else
    Result := 'Win32';
{$ELSE}
  // ماکروهای کامپایلر سیستم‌عامل را با حرف اول بزرگ می‌نویسند ("Linux", "Darwin")
  // ولی پوشه خروجی یونیت پکیج نه، پس این دو فقط بعد از
  // تاکردن حروف موافق‌اند. روی یک فایل سیستم حساس-به-حروف آن تفاوت
  // کل جست‌وجو است
  Result := LowerCase({$I %FPCTARGETCPU%} + '-' + {$I %FPCTARGETOS%});
{$ENDIF}
end;

چرا یک حرف بزرگ کل زنجیره را شکست

ماکرو کامپایلر سیستم‌عامل هدف را با حرف اول بزرگ هجی می‌کند: Win64، Linux، Darwin. پکیج Lazarus خروجی یونیتش را در پوشه‌ای می‌نویسد که از متغیر هدف خودش نام گرفته، که حروف کوچک است: win64، linux، darwin. دو هجای یک چیز، و هیچ راهی برای فهمیدنش روی Windows، جایی که فایل سیستم آن‌ها را تفکیک نمی‌کند

روی Linux آن‌ها دو پوشه متفاوت‌اند. استقراری که شیء اشتراکی را در DLLs/x86_64-linux می‌گذارد برای لودری که دنبال DLLs/x86_64-Linux می‌گردد نامرئی است، پس هر چهار گام صریح زنجیره از دست می‌روند و کد به سپردن جست‌وجو به لودر پلتفرم می‌لغزد. گاهی آن کار می‌کند، اگر کتابخانه اتفاقاً سراسری نصب شده باشد، و گاهی نه، و به هر حال درخت استقرار مرتب‌چیده‌شده هیچ سهمی ندارد. شکست پیام خطایی ندارد چون چیزی شکست نخورد: هر گام درست گزارش کرد که فایل آن‌جا که نگاه می‌کرد نبود

چگونه یک حرف بزرگ در ماکرو هدف FPC جست‌وجوی DLL مربوط به PDFium را روی لینوکس می‌شکند: لودر دنبال DLLs/x86_64-Linux می‌گردد در حالی که پوشه مستقر DLLs/x86_64-linux است، که فقط روی ویندوز بی‌حساس-به-حروف مطابقت می‌کند
همان هدف با دو هجا روی ویندوز مطابقت می‌کند و روی یک فایل سیستم حساس-به-حروف بی‌سروصدا از دست می‌رود

برنامه پروب، کامپایل و اجرا شده

این رده باگ با خواندن پیدا نمی‌شود، و با کامپایل هم پیدا نمی‌شود. تکنیک معمول برای راستی‌آزمایی یک شاخه پلتفرمی که روی دستگاه توسعه هرگز کامپایل نمی‌شود این است که یونیت را در یک پوشه موقت کپی، نامش را عوض، شرطی پلتفرم را با سمبلی که هرگز تعریف نمی‌شود جایگزین، و کپی را کامپایل کنید؛ اگر کامپایل شد، بند uses و امضاهای فراخوانی در آن مسیر حداقل خودسازگارند. آن برای یک یونیت خودبسنده خوب کار می‌کند

اینجا کار نمی‌کند. یونیت بایندینگ اصلی بسیار بزرگ است و LCL را می‌کشد در، پس نمی‌تواند صرفاً کپی و با سمبل Windows خاموش کامپایل شود. پس به‌جایش مشت تابع‌هایی که تغییر لمس کرد عیناً در یک برنامه کوچک خودبسنده پیاده شد، و آن برنامه اجرا شد. x86_64-Win64 را چاپ کرد، و ناهمخوانی در یک خط خروجی مرئی بود. کامپایل همان برنامه هیچ به شما نمی‌گفت، چون رشته کاملاً معتبر است؛ فقط مقدارش غلط است

program ProbeSubDir;
{$MODE DELPHI}
uses
  SysUtils;
begin
  // چاپ کن، ادعا نکن. نکته نگاه‌کردن به مقداری است که یک ماکرو
  // در این toolchain واقعاً به آن باز می‌شود
  Writeln('raw:    ', {$I %FPCTARGETCPU%}, '-', {$I %FPCTARGETOS%});
  Writeln('folded: ', LowerCase({$I %FPCTARGETCPU%} + '-' +
    {$I %FPCTARGETOS%}));
end.

درس کلی: وقتی یک تغییر میان-پلتفرمی به مقدار چیزی مربوط می‌شود نه نوعش، راستی‌آزمایی فقط-کامپایل راستی‌آزمایی نیست. چاپش کنید. مجموعه گسترده‌تر تفاوت‌های میان-کامپایلری میان Delphi و Free Pascal در مقاله تله‌های میان-کامپایلری Delphi و FPC جمع شده است

بگذارید پلتفرم شکست‌های بارگذاری خودش را توضیح دهد

شاخه Windows لودر دلایل شکست یک بارگذاری را دستی برمی‌شمارد، چون تمایزهای مفید آنجا، یک ناهمخوانی معماری، یک وابستگی انتقالی غایب، یک مسیر که حل نمی‌شود، به کدهای خطایی نگاشت می‌شوند که ارزش نام‌بردن تکی دارند. بیرون از Windows یونیت لودر پرتابل از قبل یک رشته توصیفی برمی‌گرداند که همان زمین را پوشش می‌دهد، پس شاخه غیر-Windows مستقیم از آن استفاده می‌کند به‌جای اشتقاق دوباره رده‌ها از یک شماره خطا که روی سیستم‌های متفاوت معناهای متفاوت دارد

مقاومت در برابر وسوسه نرمال‌کردن آن دو به یک پیام عمدی است. یک شکست بارگذاری یک مشکل استقرار است، و کسی که پیام را می‌خواند به واژگان خود پلتفرم برای جست‌وجویش نیاز دارد

یک تصادم نام که بازگشت می‌زند

یک تله دیگر، کوچک و تیز. یونیت لودر پرتابل رویه‌ای به نام UnloadLibrary آشکار می‌کند، و یونیت بایندینگ رویه‌ای هم‌نام دارد که پیش از رهاکردن هندل دفترداری خودش را انجام می‌دهد. درون آن رویه، یک فراخوانی بدون صلاحیت به UnloadLibrary به همان یکی در یونیت جاری حل می‌شود، که خودش را فراخوانی می‌کند. fix صلاحیت‌دار کردن فراخوانی با نام یونیت است

این همان شکلی است که مسائل سایه‌اندازی شناسه دارد که به‌طور کلی پورت‌های Free Pascal را مسلط کرده‌اند: یونیت Windows توابع حداقل و حداکثر عدد صحیح را آشکار می‌کند که گونه‌های ممیز شناور را سایه می‌زنند، و یک نوع همگام‌سازی که کلاس هم‌نام را سایه می‌زند، و در هر حالت حل‌شدن به ترتیب بند uses بستگی دارد. صلاحیت‌دار کردن call site همان fixی است که به کسی که بعداً آن ترتیب را حفظ کند وابسته نیست

تصادم نام UnloadLibrary در مؤلفه PDFium: یک فراخوانی بدون صلاحیت در یونیت بایندینگ دلفی به خودش بازمی‌گردد، در حالی که یک فراخوانی با صلاحیت یونیت به یونیت لودر پرتابل می‌رسد و هندل را رها می‌کند
صلاحیت‌دار کردن call site رهاسازی را از طریق یونیت لودر می‌فرستد به‌جای بازگشت به یونیت بایندینگ

چک‌لیست استقرار

سه چیز بیشتر شکست‌های بارگذاری را وقتی حساب مسیر درست است توجیه می‌کنند. معماری باید با فرایند مطابقت کند نه دستگاه، پس یک برنامه ۳۲بیتی روی Windows ۶۴بیتی به باینری ۳۲بیتی نیاز دارد. بیلد فعال-V8 نام فایل متفاوتی دارد، پس استقراری که آن‌ها را مخلوط کند درست به‌نظر می‌رسد و هیچ بارگذاری نمی‌کند. و در هر لحظه فقط یک گونه می‌تواند در یک پوشه سیستم زندگی کند، که دلیل خوبی است برای ترجیح چیدمان زیرپوشه صریح به نصب هر چیزی سراسری

برای Lazarus مشخصاً، کتابخانه بومی را زیر DLLs/<cpu>-<os> با حروف کوچک، کنار فایل اجرایی، بگذارید و در اولین گام زنجیره روی هر هدفی پیدا می‌شود. نمونه بیننده‌ای که این را روی Lazarus ورز می‌دهد در مقاله بیننده Lazarus و FPC توصیف شده، و پشتیبانی پلتفرم فعلی در صفحه محصول PDFium Delphi component فهرست شده است