When developing some native plugins to use in my unity project I frequently run into generic errors coming from the dll that I am not experienced in debugging. I have one such an example and wanted to record it for my future self if I run into these kind of issues again.
Background
I haven’t integrated the unity pipelines yet, or figured the best workflow to build for both windows and android, so currently I have both android and windows mingled in with each other, with a common CMakeLists and a few subdirectories for windows and android. The directory structure from inside src\main looks like this
| AndroidManifest.xml
|
+---assets
| example.pdf
|
+---cpp
| | CMakeLists.txt
| | main.cpp
| |
| +---android
| | AndroidLogger.cpp
| | CMakeLists.txt
| |
| +---include
| | AndroidLogger.h
| | fpdfview.h
| | fpdf_text.h
| | fpdf_transformpage.h
| | Logger.h
| | WindowsLogger.h
| |
| \---windows
| CMakeLists.txt
| WindowsLogger.cpp
|
+---libs
| +---android
| | +---arm64-v8a
| | | libbase_allocator_partition_allocator_src_partition_alloc_allocator_base.cr.so
| | | libbase_allocator_partition_allocator_src_partition_alloc_allocator_core.cr.so
| | | libbase_allocator_partition_allocator_src_partition_alloc_allocator_shim.cr.so
| | | libc++_chrome.so
| | | libchrome_zlib.cr.so
| | | libicuuc.cr.so
| | | libpdfium.cr.so
| | | libthird_party_abseil-cpp_absl.cr.so
| | |
| | \---x86_64
| | libbase_allocator_partition_allocator_src_partition_alloc_allocator_base.cr.so
| | libbase_allocator_partition_allocator_src_partition_alloc_allocator_core.cr.so
| | libbase_allocator_partition_allocator_src_partition_alloc_allocator_shim.cr.so
| | libc++_chrome.so
| | libchrome_zlib.cr.so
| | libicuuc.cr.so
| | libpdfium.cr.so
| | libthird_party_abseil-cpp_absl.cr.so
| |
| \---windows
| pdfium.dll
| pdfium.dll.lib
|
\---res
+---mipmap-hdpi
| ic_launcher.webp
| ic_launcher_round.webp
|
+---mipmap-mdpi
| ic_launcher.webp
| ic_launcher_round.webp
|
+---mipmap-xhdpi
| ic_launcher.webp
| ic_launcher_round.webp
|
+---mipmap-xxhdpi
| ic_launcher.webp
| ic_launcher_round.webp
|
+---raw
| example.pdf
|
\---values
strings.xml
So I am using gradle to build for android (which uses a cmake plugin) and cmake directly to build for windows, it at least allows me to share some cpp code between the two and keep platform specific stuff separate, no idea if this will work well going forward.
Running gradle
./gradlew :emu-pdfium-plugin-shared:build info
Results in a successful android build that works in unity.
Running cmake for the windows build
cmake --build C:\Users\ryanm\AndroidStudioProjects\EmuPdfiumPlugin\emupdfiumplugin\winbuild -j 6
Results in a successful windows build with the binaries, but even though it uses the same native cpp code as the android build it has difficulty finding the entrypoint when running in unity
2024-08-17 09:45:00.1059 INFO (HandleOpenBook:65) Received opening Book signal
EntryPointNotFoundException: GetPageCount assembly:<unknown assembly> type:<unknown type> member:(null)
...
at UnityEngine.InputSystem.Utilities.DelegateHelpers.InvokeCallbacksSafe[TValue] (UnityEngine.InputSystem.Utilities.CallbackArray`1[System.Action`1[TValue]]& callbacks, TValue argument, System.String callbackName, System.Object context) [0x00027] in .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\Utilities\DelegateHelpers.cs:46
And this is what I need to learn to debug better.
Debugging!
My first initial thoughts are
- Is unity detecting and loading the dll?
- Is the dll exposing the functions?
- Is the correct assembly being build – windows x86_64
- Is unity targetting the correct platform
I think the best place to start is to check the dll is built for the correct platform, as I used to build it with clion which handled a lot of the configuration, now I have switched to manually calling cmake.
To do this I will use dumpbin and get some header info
dumpbin /headers .\emu_pdfium_native_plugin.dll
Microsoft (R) COFF/PE Dumper Version 14.35.32215.0
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file .\emu_pdfium_native_plugin.dll
PE signature found
File Type: DLL
FILE HEADER VALUES
8664 machine (x64)
8 number of sections
66C0616B time date stamp Sat Aug 17 09:38:03 2024
0 file pointer to symbol table
0 number of symbols
F0 size of optional header
2022 characteristics
Executable
Application can handle large (>2GB) addresses
DLL
...
The line 8664 machine (x64) tells us it is build for x86_64 which is what we want.
Next I want to make sure the functions are exported correctly, again we use dumpbin
dumpbin /exports .\emu_pdfium_native_plugin.dll
Microsoft (R) COFF/PE Dumper Version 14.35.32215.0Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file .\emu_pdfium_native_plugin.dll
File Type: DLL
Summary
1000 .00cfg
1000 .data
2000 .idata
2000 .pdata
8000 .rdata
1000 .reloc
1000 .rsrc
11000 .text
Ah so our functions are not being exports, I decided to run the same command on the old dll that was working, this is the dll created by clion.
Microsoft (R) COFF/PE Dumper Version 14.35.32215.0
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file .\emu_pdfium_native_plugin.dll
File Type: DLL
Section contains the following exports for emu_pdfium_native_plugin.dll
00000000 characteristics
66C06433 time date stamp Sat Aug 17 09:49:55 2024
0.00 version
1 ordinal base
10 number of functions
10 number of names
ordinal hint RVA name
1 0 00001695 DoNothing
2 1 0000169C DoSomething
3 2 00001ED9 FreeBitmap
4 3 000018B0 GetPageCount
5 4 00001908 GetRenderedImage
6 5 0000178D LoadPDF
7 6 00001819 LoadPDFFromMemory
8 7 000016A7 LoadPdfium
9 8 0000153D SetLogCallback
10 9 00001705 onLibraryUnload
Summary
1000 .CRT
1000 .bss
3000 .data
1000 .debug_abbrev
1000 .debug_aranges
2000 .debug_frame
19000 .debug_info
2000 .debug_line
1000 .debug_line_str
1000 .debug_rnglists
1000 .debug_str
1000 .edata
2000 .idata
D000 .pdata
11000 .rdata
2000 .reloc
BC000 .text
1000 .tls
11000 .xdata
It looks like in our old dll all the exported functions are available. At this point I decided to review CMakeLists.txt and compare with the old one.
I didn’t notice anything that stood out in CMakesLists.txt, but I did notice in our cpp code that the working code had this expression on each of the functions we wanted to export
__declspec(dllexport)
Solution
So if I understand this, it is a microsoft-specific extension that tells the linker to export a symbol (in this case our function) to the DLL. Android doesn’t have this requirement, but I added it in our platform-independent cpp, however android doesn’t support this so to make it work for both windows and android we needed to do this.
#ifdef PLATFORM_WINDOWS
#define EXPORT_API __declspec(dllexport)
#else
#define EXPORT_API
#endif
...
EXPORT_API bool LoadPDF(const char *filePath)
{
...
}
Conclusion
The DLL is now picked up by unity, and the functions are exposed. I was able to successfully call my native code from the c# code and it ran as expected.
Overall this was a good exercise to record how I debugged this, firstly it made me think it through a bit more as I had to put it into words here, this avoided the trial and error approach I will sometimes do when I am frustrated or not thinking clearly. It is also useful if something similar happens in the future I have some reference to previous appraoches and successes.

Leave a Reply