Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Native libraries in MonoDroid library projects

, 13 Jul 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
Support for native libraries in MonoDroid.

Currently, MonoDroid (Mono for Android) doesn’t support native libraries (or any other Android resource type) in library projects (bug report). Fortunately, they support normal .NET assembly resources. This led me to write a workaround for this missing feature.

The Class

Here’s the class that implements the loader.

//
// Copyright 2012 Sebastian Krysmanski
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
using System;
using System.IO;
using System.Reflection;
using Android.App;
using Android.Content;
using Android.OS;

namespace Android.Util {
  /// <summary>
  /// Dynamically loads a native library from the resources of an assembly.
  /// 
  /// <para>Important:
  /// <list type="bullet">
  /// <item><description>
  ///   The native library must be marked with "Embedded Resource" (not "AndroidNativeLibrary")
  /// </description></item>
  /// <item><description>
  ///   You can't run any native functions within the same method you load the library (e.g. in a static constructor).
  /// </description></item>
  /// <item><description>
  ///   You need to increase the assembly version number when ever the native library is changed. Changes to it can't
  ///   be detected automatically. To increase the version number automatically on each build, use 
  ///   <c>[assembly: AssemblyVersion("1.0.*")]</c> in <c>AssemblyInfo.cs</c>. If you don't increase the version number,
  ///   the native library won't be re-deployed.
  /// </description></item>
  /// </list>
  /// </para>
  /// 
  /// <para>Note: This class is thread-safe.</para>
  /// </summary>
  public class MonoDroidLibraryLoader {
    private static readonly string TAG = typeof(MonoDroidLibraryLoader).Name;
    private const string LIBS_DIR = "dynlibs";

    private static readonly object LOCK = new object();

    private readonly Assembly m_assembly;

    private readonly string m_libName;
    private readonly string m_libFileName;
    private readonly string m_libResDir;

    private readonly string m_outDir;
    private readonly string m_outFile;
    private readonly string m_deploymentInfoFile;

    private delegate Stream GetResourceStreamFunc(string resName);
    private readonly GetResourceStreamFunc GetResourceStream;

    private MonoDroidLibraryLoader(Assembly assembly, string libName, string libResDir, 
                                   GetResourceStreamFunc getResourceStreamFunc) {
      this.m_assembly = assembly;
      this.m_libName = libName;
      this.m_libFileName = Java.Lang.JavaSystem.MapLibraryName(libName);
      this.m_libResDir = libResDir;

      this.m_outDir = Application.Context.GetDir(LIBS_DIR, FileCreationMode.Private).AbsolutePath;
      this.m_outFile = this.m_outDir + "/" + this.m_libFileName;
      this.m_deploymentInfoFile = this.m_outFile + ".info";

      this.GetResourceStream = getResourceStreamFunc;
    }

    /// <summary>
    /// Loads the specified native library from the resources of an assembly by using a type for reference.
    /// </summary>
    /// <param name="libName">the platform independent name of the
    ///         library (e.g. "sqlite" for "libsqlite.so")</param>
    /// <param name="rootType">a type to which the library is relative to; must be in the same assembly as the native
    /// library</param>
    /// <param name="libResDir">the name of the sub directory where the library ABI directories are located</param>
    ///
    /// <example>For example, assume the following project structure:
    ///
    /// <code>
    /// project
    /// +- MyClass.cs
    /// +- NativeLibs
    ///    +- armeabi
    ///       +- libsqlite.so
    ///    +- armeabi-v7a
    ///       +- libsqlite.so
    /// </code>
    /// 
    /// <para>Then the file "libsqlite.so" can be loaded
    ///           by calling <c>Load("sqlite", typeof(MyClass))</c>.</para>
    /// </example>
    /// 
    /// <remarks>Loading the same library multiple times has no effect (i.e. throws no exception or breaks things 
    /// otherwise).</remarks>
    public static void Load(string libName, Type rootType, string libResDir = "NativeLibs") {
      MonoDroidLibraryLoader loader = new MonoDroidLibraryLoader(rootType.Assembly, libName, libResDir, (resName) => {
        return rootType.Assembly.GetManifestResourceStream(rootType, resName);
      });
      loader.Load();
    }

    /// <summary>
    /// Loads the specified native library from the resources of an assembly by using an assembly for reference.
    /// </summary>
    /// <param name="libName">the platform independent name
    ///           of the library (e.g. "sqlite" for "libsqlite.so")</param>
    /// <param name="assembly">the assembly containing the library as resource</param>
    /// <param name="rootNS">the root namespace of the assembly (as stated in the project properties)</param>
    /// <param name="libResDir">the name of the sub directory where the library ABI directories are located</param>
    /// 
    /// <example>For example, assume the following project structure:
    ///
    /// <code>
    /// project
    /// +- NativeLibs
    ///    +- armeabi
    ///       +- libsqlite.so
    ///    +- armeabi-v7a
    ///       +- libsqlite.so
    /// </code>
    /// 
    /// <para>Then the file "libsqlite.so" can be loaded by calling 
    /// <c>Load("sqlite", Assembly.GetExecutingAssembly(), "MyRootNS")</c>.</para>
    /// </example>
    /// 
    /// <remarks>Loading the same library multiple times has no effect (i.e. throws no exception or breaks things 
    /// otherwise).</remarks>
    public static void Load(string libName, Assembly assembly, string rootNS, string libResDir = "NativeLibs") {
      MonoDroidLibraryLoader loader = new MonoDroidLibraryLoader(assembly, libName, libResDir, (resName) => {
        return assembly.GetManifestResourceStream(rootNS + "." + resName);
      });
      loader.Load();
    }

    private static string FixDirName(string dir) {
      return dir.Replace('/', '.').Replace('-', '_');
    }

    private string GetResourceName(string cpuAbi) {
      return FixDirName(this.m_libResDir) + '.' + FixDirName(cpuAbi) + '.' + this.m_libFileName;
    }

    private string GetAssemblyVersion() {
      // NOTE: We can't use the file version as it doesn't support
      //            wildcards. We need to use the assembly version 
      //   instead. 
      //   See: http://connect.microsoft.com/VisualStudio/feedback/details/
      //              114814/assemblyfileversion-does-not-allow-wildcards
      return this.m_assembly.GetName().Version.ToString();
    }

    private bool RequireDeployment() {
      if (!File.Exists(this.m_outFile) || !File.Exists(this.m_deploymentInfoFile)) {
        // File doesn't exist. Require deployment.
        return true;
      }

      string lastDeployedVersion = File.ReadAllText(this.m_deploymentInfoFile);
      string curVersion = GetAssemblyVersion();

      return (lastDeployedVersion != curVersion);
    }

    private void WriteDeploymentInfo() {
      File.WriteAllText(this.m_deploymentInfoFile, GetAssemblyVersion());
    }

    private void Load() {
      lock (LOCK) {
        if (RequireDeployment()) {
          string nativeLibResName1 = GetResourceName(Build.CpuAbi);
          Stream nativeLibResStream = GetResourceStream(nativeLibResName1);
          if (nativeLibResStream == null) {
            // Try alternative ABI
            string nativeLibResName2 = GetResourceName(Build.CpuAbi2);
            nativeLibResStream = GetResourceStream(nativeLibResName2);
            if (nativeLibResStream == null) {
              throw new ArgumentException("Could neither find resource '" + nativeLibResName1
                                          + "' nor '" + nativeLibResName2 + "'. Do they exist?");
            }
          }

          Directory.CreateDirectory(this.m_outDir);
          using (Stream fileStream = File.OpenWrite(this.m_outFile)) {
            nativeLibResStream.CopyTo(fileStream);
          }

          WriteDeploymentInfo();

          Log.Info(TAG, this.m_libFileName + " deployed at " + this.m_outDir);
        }

        // IMPORTANT:
        //  * We need to use "Load()" instead of "LoadLibrary()" (because the latter doesn't allow full paths)
        //  * The native library we want to load must be in the internal(!) storage (as the SD card is mounted with 
        //    noexec).
        Java.Lang.JavaSystem.Load(this.m_outFile);
      }
    }
  }
}

How to use

To use this class, you first need to create a directory structure for the native libraries, like this:

Project
+- NativeLibs
   +- armeabi-v7a
   +- armeabi
   +- other ABIs you want to support

Now place the native libraries in the apropriate directories, for example:

Project
+- NativeLibs
   +- armeabi-v7a
   |  +- libsqlite.so
   +- armeabi
      +- libsqlite.so

Then select “Embedded Resource” as Build Action in the file’s properties (Hotkey: F4).

Selecting "Embedded Resource" as "Build Action"

Now use one of the Load() methods to load the library. For example, if you have a type called MyType in the project’s default namespace (specified in the project settings), you can use this call to load a library called libsqlite.so:

MonoDroidLibraryLoader.Load("sqlite", typeof(MyType));

Note: If you renamed the directory “NativeLibs” from above, you can specify it as third parameter for Load().

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sebastian Krysmanski
Software Developer University of Stuttgart
Germany Germany
I have studied Software Engineering and am currently working at the University of Stuttgart, Germany.
 
I have been programming for many years and have a background in C++, C#, Java, Python and web languages (HTML, CSS, JavaScript).
Follow on   Twitter

Comments and Discussions

 
QuestionQuestion Pinmembersaeidfarahi8-Aug-14 17:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 13 Jul 2012
Article Copyright 2012 by Sebastian Krysmanski
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid