Debugging Unity Native Plugins

Debugging Unity Native Plugins

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

  1. Is unity detecting and loading the dll?
  2. Is the dll exposing the functions?
  3. Is the correct assembly being build – windows x86_64
  4. 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.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *