DEVELOPERS BLOG

Diary of a Unity 3D Newbie – Solving DllImportException Errors

If you’ve followed any of my earlier blog posts, you know that I tend to explore areas like NFC and Bluetooth Low Energy from a BlackBerry 10 developer’s perspective – usually in conjunction with my colleague Martin Woolley.

So you may be wondering why I’m writing about Unity 3D today. Well, if you weren’t already aware, Unity 3D is a phenomenal framework that allows you to develop amazing games on multiple platforms, including BlackBerry 10. What’s more, it contains a very flexible plugin framework that allows you to extend your game’s capabilities down into native capabilities of BlackBerry 10!

So naturally, I wondered if I could add the capability to a Unity 3D game to interact with, say, a Bluetooth Smart device. Imagine influencing gameplay through data streamed from a Heart Rate Monitor worn by the player. (Hold that thought for a bit!)

Because I already know how to interact with Bluetooth Smart devices, it’s just a case of embedding the Bluetooth functionality in a native shared library and plugging it into the Unity 3D framework. I’ll share how to do just that in future blog post.

However, today I’d like to share with you what, as a Unity 3D newbie, I had to do to get to the root-cause of a particularly opaque error I encountered when testing my plugin in and application on a BlackBerry 10 device. I kept on seeing “DllImportException” errors, which suggested that my plugin couldn’t be found by the running application.

Background on Unity 3D Plugins

Unity 3D plugins are basically shared libraries. For example, libBtHrmPlugin.so (my embryonic Bluetooth Low Energy Heart Rate Monitor Plugin) exposes a set of “C” functions something like this:

Diary of a 3D Newbie_1

Unity 3D applications typically use C# (using the cross-platform Mono Framework) or JavaScript to express game logic, and to link these external function references into your Unity 3D script you would typically, in the case of C#, use the statements like the ones below. The directive DllImport has a peculiar name because C# originated on Windows and a DLL is a Dynamic Link Library (the Windows analogue to a shared library on BlackBerry 10). Notice that the name of the library to import is “BtHrmPlugin” rather than “libBtHrmPlugin.so”. Mono will try a variety of permutations of names as it tries to locate the underlying library and this gives an element of platform independence to naming of the plugin libraries.

Diary of a 3D Newbie_2

So, all is looking good and we run the application on the BlackBerry 10 device:

Diary of a 3D Newbie_3

Whoops! Bang! Something went wrong and we see a DllImportException when we look at the log file:

Diary of a 3D Newbie_4

How annoying! So, how do you deal with this issue? Here’s what I’ve found to be a logical set of steps that I’ll outline below:

  1. Ensure that the plugin is added to the correct path in the Unity 3D project.
  2. Switch on Mono debugging via the bar-descriptor.xml file to understand what actual file names Mono is attempting to use to locate the shared library.
  3. Switch on QNX dynamic library logging to understand how the underlying dlopen() service is searching for the shared library.

Ensure that the plugin is in the correct path in the Unity 3D project

This may seem obvious but it caught me out. Your plugin (libBtHrmPlugin.so in my case) needs to go here in your Unity 3D project’s Assets folder. It won’t be included in the project’s “.bar” file as an asset unless it goes here:

Diary of a 3D Newbie_5

As a sanity check, you can check the “.bar” file, which you can find here:

Diary of a 3D Newbie_6

Open the “.bar” file with a tool like “7-zip” and there it is in the “nativelib” folder. So, we verified that the plugin has been included in the “.bar” file as an asset and it will be installed onto the device as party of the application.

Diary of a 3D Newbie_7

Check to see where Mono is looking for the file on the device

When Unity 3D builds a project for BlackBerry 10, it assembles all the project assets in a “StagingArea” folder. Here is where it’s located:

Diary of a 3D Newbie_8

We’re interested in the “bar-descriptor.xml” file that is created by Unity 3D. The part of this file that is of interest to us is the following:

Diary of a 3D Newbie_9

Here, the environment variable LD_LIBRARY_PATH, which determines the search location for shared libraries at run-time for BlackBerry 10 applications, is correctly set to include the “native/lib” path. If we add the following line to the bar-descriptor.xml”, this will switch on additional logging when Mono tries to locate the shared library at run-time:

Diary of a 3D Newbie_10

Then we need to rebuild the “.bar” file using the command line something like this, using “BlackBerry-nativepackager.bat”, since Unity 3D would just re-create the “bar-descriptor.xml” file if you used the Unity 3D IDE to rebuild the project. Notice we were careful to delete the old “.bar” file since we didn’t want it included in the new one.

Diary of a 3D Newbie_11

If we install this on the device, again using, say, the “blackberry-deploy.bat” command line tool, we can look at the log once again. This time we see something like the following:

Diary of a 3D Newbie_12

What we see now is that Mono is reporting on the various permutations of names that it is attempting to use from the base name of “BtHrmPlugin” to locate the underlying shared library file. Perhaps this is able to shed some light on why we’re seeing a problem? If not, then maybe the next step will help.

Switch on QNX dynamic library logging to see what QNX thinks is happening

We follow exactly the same process as in the previous section but add two additional statements, which are described in the QNX documentation for “dlopen()”, to the “bar-descriptor.xml” file, thus:

Diary of a 3D Newbie_13

When we look at the log file this time, we see a lot of information regarding how the search for the unresolved external references is taking place. We see that the “libBtHrmPlugin.so” shared library is being located correctly in the “native/lib” folder in the application.

Diary of a 3D Newbie_14

If we were to look in more detail, we’d see that the issue was not that the shared library was not found, but rather, there were unresolved external references in the shared library itself, which give rise to the “DllImportException” being reported. The real problem was back in the plugin NDK project itself:

Diary of a 3D Newbie_15

The reference to the external library “libbtapi.so” (Bluetooth API library) was missing from the project’s link step! Remember that we’re building a shared library, and missing external references are quite legitimate at build-time since they would be resolved at run-time, but we still need to identify the external library to the linker when the project is built.

Adding this fixed the problem:

Diary of a 3D Newbie_16

Summary

I hope that my own experience has given you some insight into how to logically track down this sort of issue in any Unity 3D plugin you may write, and how it could be used in a more general context to find the root cause of unresolved references in your application.

I’ll be following up this post with one on the Unity 3D Bluetooth Low Energy Heart Rate Monitor plugin I’m currently writing, so watch this space!

Additional Resources

  • If you want to know more about Bluetooth Low Energy, then look here at the index of material produced by John Murray and Martin Woolley.

About jcmurray2012