作っているアプリケーションに ARM64 対応を将来的に入れるにあたって、依存しているライブラリで PDFium だけが x86 / x64 だけの対応だったので、ARM64 向けビルドを試しておきました。
既に Chromium は Microsoft からのコントリビューションが行われているので、ARM64 向けビルドは多少はまりましたが比較的すんなりと行えました。
手順をメモしておかないと絶対に忘れるので残しておきます。
事前準備
ビルドに必要なものは Chromium と共通なので、以下のドキュメントを読みつつ準備します。
手順に従い ARM64 向けのツールをインストールする必要がありますが、実際のコンパイルには Clang が使われているようだったので、ここでインストールしたコンパイラは使われていない気がします。
中でも "Debugging Tools For Windows" は Visual Studio から Windows SDK をインストールしただけでは入らないので、アプリケーションの変更からコンポーネントを選択する必要があります。
depot_tools を展開しつつパスを通して、gclient
で PDFium のソースと依存関係をダウンロードすれば大体完了です。set DEPOT_TOOLS_WIN_TOOLCHAIN=0
は忘れがちなので注意です。
PDFium をビルドする
GN と Ninja を使ってビルドしていきますが、必要な args.gn
は以下のように用意しました。target_cpu = arm64
以外は x86 / x64 と共通です。
pdf_is_standalone = true pdf_enable_v8 = false pdf_enable_xfa = false pdf_use_win32_gdi = false is_component_build = false is_debug = false target_cpu = "arm64"
今回は必要なかったので v8 と XFA はオフにしてビルドします。GDI も使わないのでオフにしました。
普通にこのままビルドすると DLL が生成されないので、多少パッチを当てて DLL を生成しつつ呼び出し規約を一応 stdcall にしておきました。*1
diff --git a/BUILD.gn b/BUILD.gn index 8bfe0ca55..1abb71741 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -42,6 +42,7 @@ config("pdfium_common_config") { if (is_win) { # Assume UTF-8 by default to avoid code page dependencies. cflags += [ "/utf-8" ] + defines += [ "FPDFSDK_EXPORTS" ] } } @@ -139,7 +140,7 @@ group("pdfium_public_headers") { ] } -component("pdfium") { +shared_library("pdfium") { libs = [] configs += [ ":pdfium_core_config" ] public_configs = [ ":pdfium_public_config" ] diff --git a/public/fpdfview.h b/public/fpdfview.h index debe083be..a228cdc50 100644 --- a/public/fpdfview.h +++ b/public/fpdfview.h @@ -175,7 +175,7 @@ typedef int FPDF_ANNOT_APPEARANCEMODE; // Dictionary value types. typedef int FPDF_OBJECT_TYPE; -#if defined(COMPONENT_BUILD) +#if defined(COMPONENT_BUILD) || defined(FPDFSDK_EXPORTS) // FPDF_EXPORT should be consistent with |export| in the pdfium_fuzzer // template in testing/fuzzers/BUILD.gn. #if defined(WIN32) @@ -193,7 +193,7 @@ typedef int FPDF_OBJECT_TYPE; #endif // defined(WIN32) #else #define FPDF_EXPORT -#endif // defined(COMPONENT_BUILD) +#endif // defined(COMPONENT_BUILD) || defined(FPDFSDK_EXPORTS) #if defined(WIN32) && defined(FPDFSDK_EXPORTS) #define FPDF_CALLCONV __stdcall
これで DLL が生成されるようになります。ビルド自体は gn gen
を実行して Ninja の定義を作成した後に ninja -C directory pdfium
を実行すると行われます。
今回は out\arm64
以下に args.gn
を置いてあるので、以下のようなコマンドを実行します。
gn gen out\arm64 ninja -C out\arm64 pdfium
これで x86 と x64 の場合は問題なく DLL が生成されますが、ARM64 の場合は以下のようなエラーが出るケースがあるようです。CRT のディレクトリ構成が微妙に異なっているのが原因のようです。
ソート関数が int と str で比較できないのが原因なので、今回は適当に数値に変換できない場合は 0 を返して選ばれないようにしました。
修正後は ARM64 向けでも問題なく Ninja でビルドが行えるようになりました。
ビルド後の DLL のヘッダーを調べると AA64
になっているので、ARM64 向けの DLL であることが分かります。エクスポート関数も一応調べましたが、問題なく定義されていました。
後は実際に ARM64 マシン上で動作するか確認するだけです。もちろん Surface Pro X を使います。
Surface Pro X で実際に試す
PDFium ではテストコードもビルド出来るのでそれを使っても良いのですが、それだとつまらないので .NET 5 Preview 6 で追加された Win Forms の ARM64 版で試しました。
サンプル自体は以下のようなコードを書きました。GDI サポートを使えば HDC に対して直接レンダリング出来るようですが、GDI も今更感あるので Bitmap
に対してレンダリングします。
public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { var dialog = new OpenFileDialog(); if (dialog.ShowDialog() != DialogResult.OK) { return; } var filePath = dialog.FileName; NativeMethods.FPDF_InitLibrary(); var document = NativeMethods.FPDF_LoadDocument(filePath, null); var page = NativeMethods.FPDF_LoadPage(document, 0); var width = (int)NativeMethods.FPDF_GetPageWidth(page); var height = (int)NativeMethods.FPDF_GetPageHeight(page); var bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb); var bitmapData = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); var pdfBitmap = NativeMethods.FPDFBitmap_CreateEx(width, height, 2, bitmapData.Scan0, bitmapData.Stride); NativeMethods.FPDFBitmap_FillRect(pdfBitmap, 0, 0, width, height, 0xffffffff); NativeMethods.FPDF_RenderPageBitmap(pdfBitmap, page, 0, 0, width, height, 0, 0); NativeMethods.FPDFBitmap_Destroy(pdfBitmap); bitmap.UnlockBits(bitmapData); NativeMethods.FPDF_ClosePage(page); NativeMethods.FPDF_CloseDocument(document); NativeMethods.FPDF_DestroyLibrary(); pictureBox1.Image = bitmap; } }
ARM64 向けビルドは以下のコマンドを使って Self-contained 形式でビルドしました。
これまでもコンソールアプリの場合は RID に win-arm64
を指定できましたが、Preview 6 では Win Forms に対応したのでビルドが通ります。
dotnet publish -c Release -o ./publish -r win-arm64
WPF の ARM64 サポートもひっそり入っているのを期待しましたが、ビルドエラーになりました。
ビルドしたファイルを Surface Pro X にコピーして実行すると、ちゃんと 64bit で動作しているのが確認できます。PDF 自体もちゃんとレンダリングされて表示できています。
既に Chromium Edge が ARM64 に対応しているからか、ビルドから実行まで問題なく行えました。
割と簡単に PDF のレンダリングまで行えましたが、これを実際に PDF Viewer まで仕上げるのはちょっと面倒な感じです。時間を見つけつつ WPF で書いていこうかなという気持ちです。
*1:x64 / ARM64 だと特に意味はないと分かってはいる