PDFium 컴포넌트는 네이티브 라이브러리를 운영체제 로더에 맡기지 않고 고정되고 순서 있는 검색 사슬로 찾습니다. 명시적인 배포 트리는 디버그할 수 있는 배포 트리이기 때문입니다. Windows에서 그 사슬은 설치 프로그램이 이미 실어 보내는 Win32 또는 Win64 하위 디렉터리를 찾습니다. 다른 타깃에서는 Free Pascal 타깃 매크로에서 <cpu>-<os>처럼 하위 디렉터리 이름을 만들어, 배포 트리가 컴파일 유닛 트리와 정확히 같게 읽히게 합니다. 바로 그 마지막 결정이 이 글 전체의 가치가 있는 버그를 들여왔습니다. 원인은 대문자 하나였고 증상은 침묵이었습니다
사슬, 순서대로
네 위치를 순서대로 시도한 뒤 최후의 수단으로 플랫폼 로더입니다. 첫째 선호 배치, 실행 파일 옆에 타깃당 하위 디렉터리 하나를 담은 DLLs 디렉터리입니다. 둘째 타깃 하위 디렉터리가 실행 파일 바로 옆에 있는 대안 배치입니다. 셋째 평면 구식 배치, 하위 디렉터리 없이 실행 파일 옆에 사는 라이브러리입니다. 넷째, Windows에만, 시스템 디렉터리입니다. 32비트 프로세스는 SysWOW64를, 64비트 프로세스는 System32를 봐야 하고 32비트 Windows에는 전자가 없어 조회가 폴백해야 하므로 주의가 필요합니다. 그 모든 뒤에야 로더에 자체 검색을 부탁합니다
Windows 밖에는 의도적으로 시스템 디렉터리 단계가 없습니다. 런타임 링커 구성과 라이브러리 경로 환경이 이끄는 플랫폼 로더의 자체 검색 경로가 이미 그 땅을 커버하며, 이를 Pascal에서 복제한다는 것은 배포판마다 달라지는 규칙을 다시 구현한다는 뜻입니다. Windows 사슬의 실패 진단은 PDFium DLL 배포와 로드 실패 진단에서 별도로 다룹니다
하위 디렉터리 이름이 오는 곳
Windows에서는 Win32 또는 Win64이며, 운영체제가 아니라 실행 중인 프로세스의 비트니스가 정합니다. 어느 바이너리를 로드할 수 있는지를 결정하는 것이 프로세스이기 때문입니다. 그 밖의 모든 곳에서 이름은 컴파일러 타깃 매크로에서 만들어, 두 아키텍처용으로 빌드하는 기계가 명확히 분리된 두 트리를 내놓고, 네이티브 라이브러리를 담는 폴더가 같은 이름으로 컴파일 유닛을 담는 폴더 옆에 앉게 합니다
function BuildDllSubDir(UseV8: Boolean): string;
begin
{$IFDEF MSWINDOWS}
if IsWin64 then
Result := 'Win64'
else
Result := 'Win32';
{$ELSE}
// 컴파일러 매크로는 OS를 대문자로 시작해 쓰고("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를 찾는 로더에게 보이지 않으므로, 사슬의 명시적 네 단계가 모두 빗나가고 코드는 플랫폼 로더에게 검색을 맡기는 것으로 떨어집니다. 라이브러리가 우연히 시스템 전역에 설치되어 있다면 그것이 통할 때도 있고 아닐 때도 있으며, 어느 쪽이든 정성껏 배열한 배포 트리는 아무것도 기여하지 않습니다. 실패에는 오류 메시지가 없습니다. 실패한 것이 없기 때문입니다. 모든 단계가 자기가 본 곳에 파일이 없다고 올바르게 보고했을 뿐입니다
프로브 프로그램, 컴파일하고 실행하기
이 부류의 버그는 읽어서 찾을 수 없고 컴파일해서도 찾을 수 없습니다. 개발 기계에서 절대 컴파일되지 않는 플랫폼 분기를 검증하는 평범한 기법은 유닛을 임시 디렉터리로 복사하고, 이름을 바꾸고, 플랫폼 조건부를 절대 정의되지 않는 심볼로 바꾸고 사본을 컴파일하는 것입니다. 컴파일되면 그 경로의 uses 절과 호출 시그니처는 최소한 자기 일관적입니다. 자족적 유닛에는 잘 통하는 방법입니다
여기서는 통하지 않습니다. 메인 바인딩 유닛은 매우 크고 LCL을 끌어들이므로, Windows 심볼을 끈 채 단순히 복사해 컴파일할 수 없습니다. 그래서 대신 변경이 만진 몇 안 되는 함수들을 그대로 작은 자족적 프로그램에 옮겨 적고 그 프로그램을 실행했습니다. x86_64-Win64를 출력했고, 불일치가 출력 한 줄에 보였습니다. 같은 프로그램을 컴파일하는 것은 아무것도 말해 주지 않았을 것입니다. 문자열은 완벽히 유효하고 값만 틀리기 때문입니다
program ProbeSubDir;
{$MODE DELPHI}
uses
SysUtils;
begin
// 단정하지 말고 출력하십시오. 요점은 매크로가 이 툴체인에서
// 실제로 펼쳐지는 값을 보는 것입니다
Writeln('raw: ', {$I %FPCTARGETCPU%}, '-', {$I %FPCTARGETOS%});
Writeln('folded: ', LowerCase({$I %FPCTARGETCPU%} + '-' +
{$I %FPCTARGETOS%}));
end.
일반 교훈은 이렇습니다. 교차 플랫폼 변경이 타입이 아니라 무엇인가의 값에 관한 것이라면 컴파일만 하는 검증은 검증이 아닙니다. 출력하십시오. Delphi와 Free Pascal 사이의 더 넓은 교차 컴파일러 차이 집합은 Delphi와 FPC 교차 컴파일러 함정 문서에 모아져 있습니다
플랫폼이 자기 로드 실패를 설명하게 둡니다
로더의 Windows 분기는 로드가 실패할 수 있는 이유들을 손으로 나열합니다. 그곳의 유용한 구분들, 즉 아키텍처 불일치, 빠진 전이 의존성, 해석되지 않는 경로가 개별적으로 이름 붙일 가치가 있는 오류 코드로 매핑되기 때문입니다. Windows 밖에서는 이식성 로더 유닛이 같은 땅을 커버하는 기술적 문자열을 이미 돌려주므로, non-Windows 분기는 서로 다른 시스템에서 다른 뜻이 되는 오류 번호에서 범주를 다시 유도하는 대신 그것을 직접 씁니다
둘을 한 메시지로 정규화하고 싶은 충동을 누르는 것은 의도입니다. 로드 실패는 배포 문제이며, 메시지를 읽는 사람이 그것을 검색하려면 플랫폼 자신의 어휘가 필요합니다
재귀하는 이름 충돌
작고 날카로운 함정이 하나 더 있습니다. 이식성 로더 유닛은 UnloadLibrary라 불리는 프로시저를 export하고, 바인딩 유닛은 핸들을 놓아주기 전에 자기 장부 정리를 하는 같은 이름의 프로시저를 갖고 있습니다. 그 프로시저 안에서 수식 없는 UnloadLibrary 호출은 현재 유닛의 것으로 귀결되며, 그것은 자기 자신을 부릅니다. 해결책은 유닛 이름으로 호출을 수식하는 것입니다
이것은 일반적으로 Free Pascal 포트를 지배하는 식별자 가림 문제와 같은 모양입니다. Windows 유닛은 부동소수점 함수들을 가리는 정수 타입 최소·최대 함수를 export하고, 같은 이름의 클래스를 가리는 동기화 타입을 export하며, 모든 경우에 귀결은 uses 절의 순서에 달려 있습니다. 호출 지점을 수식하는 것이 나중에 누군가 그 순서를 지켜 주기에 의존하지 않는 해결책입니다
배포 체크리스트
경로 산술이 맞는 한 로드 실패 대부분은 세 가지가 설명합니다. 아키텍처는 기계가 아니라 프로세스와 일치해야 하므로, 64비트 Windows의 32비트 애플리케이션에는 32비트 바이너리가 필요합니다. V8 활성 빌드는 다른 파일 이름을 갖으므로 둘을 섞는 배포는 올바르게 보이면서 아무것도 로드하지 않습니다. 그리고 한 번에 한 변형만 시스템 디렉터리에 살 수 있으므로, 무엇이든 시스템 전역에 설치하는 것보다 명시적 하위 디렉터리 배치를 선호할 좋은 이유가 됩니다
Lazarus라면 구체적으로 네이티브 라이브러리를 실행 파일 옆의 소문자 DLLs/<cpu>-<os> 아래에 두십시오. 모든 타깃에서 사슬의 첫 단계가 찾아냅니다. Lazarus에서 이것을 구동하는 뷰어 샘플은 Lazarus 및 FPC 뷰어 문서에서 설명하고, 현재 플랫폼 지원은 PDFium Delphi component 제품 페이지에 정리되어 있습니다