LoginSignup
1
0

More than 1 year has passed since last update.

UnityでDllNotFoundExceptionをデバッグする

Posted at

背景

Unityで、特にNative Pluginを使った or に依存した開発中に、DllNotFoundException が発生することがあります。
Exception の名前からすれば、ライブラリが見つからない or 存在しない場合に発生しそうですが(ドキュメントにもそう書いてある)、実際には依存ライブラリが見つからない場合や、一部のシンボルが解決できない場合にも発生します。
エラーメッセージから原因の切り分けができればよいのですが、大抵の場合は以下のようにこれ以上の情報がありません。

2021/12/09 13:14:12.173 13325 13432 Error Unity DllNotFoundException: mediapipe_jni
2021/12/09 13:14:12.173 13325 13432 Error Unity at (wrapper managed-to-native) Mediapipe.UnsafeNativeMethods.glog_FLAGS_logtostderr(bool)
...

さて、ライブラリのパスが解決できない場合は公式ドキュメントを読めば良いとして、その他の原因を特定するためには、ライブラリの中身を調べる必要があります。
通常、lddobjdump などを使えば良いことが多いのですが、

  • Android上だと使えない or 面倒
  • 再現環境が手元にないと厳しい
    • 例えば、GitHubのissue等でこの手の質問が来て、解決の必要がある場合。相手にどうにか原因の一端を特定してもらう必要がある

という問題があり、効率的に原因を特定する方法が欲しくなったのでメモを残しておきます。
ただし、今のところ、Windowsで有効なエラーメッセージを得る方法は見つけていません。

コード

using System;
using System.Runtime.InteropServices;

using UnityEngine;

/// <summary>
///   <see cref="DllNotFoundException" />の原因のデバッグ用のコード。
///   ライブラリをロードし、そのエラーコードやエラーメッセージを読む。
/// </summary>
/// <remarks>
///   Linux, macOS, Windows, Android, iOSのみを考慮。
///   ただし、iOSは静的リンクしている想定なので、何もしない。
/// </remarks>
public class NativeLibraryTester
{
  /// <summary>
  ///   ライブラリをロードする。
  /// </summary>
  /// <remarks>
  ///   Androidの場合は、通常 <see cref="LoadLibrary" /> を使うほうが良い。
  /// </remarks>
  /// <param name="path">
  ///   ライブラリのパス
  ///     UnityEditorの場合: プロジェクトのルートからの相対パスでOK
  ///     Standaloneの場合: `{Application.dataPath}/Plugins/*.{so,dylib,dll}`
  ///     Androidの場合: 絶対パス
  /// </param>
  public static void Load(string path)
  {
#if UNITY_IOS && !UNITY_EDITOR
    // iOS
    // 静的リンクを想定してスルー
#elif UNITY_ANDROID && !UNITY_EDITOR
    // Android
    // NOTE: Linux, macOSと同じくdlopenしても良いけど
    using (var system = new AndroidJavaClass("java.lang.System"))
    {
      system.CallStatic("load", path);
    }
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
    // Windows
    // ただし、まともなエラーメッセージは出ない
    var handle = LoadLibraryW(path);

    if (handle != IntPtr.Zero)
    {
      // Success
      if (!FreeLibrary(handle))
      {
        Debug.LogError($"Failed to unload {path}: {Marshal.GetLastWin32Error()}");
      }
    }
    else
    {
      // Error
      // cf. https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
      var errorCode = Marshal.GetLastWin32Error();
      Debug.LogError($"Failed to load {path}: {errorCode}");

      if (errorCode == 126)
      {
        // ERROR_MOD_NOT_FOUND
        Debug.LogError(@"Check missing dependencies using [Dependencies](https://github.com/lucasg/Dependencies).
If all the required libraries exist, open the plugin inspector for dependent libraries and check `Load on startup`.
");
      }
    }
#else
    // Linux, macOS
    var handle = dlopen(path, 2);

    if (handle != IntPtr.Zero)
    {
      var result = dlclose(handle);

      if (result != 0)
      {
        Debug.LogError($"Failed to unload {path}");
      }
    }
    else
    {
      Debug.LogError($"Failed to load {path}: {Marshal.GetLastWin32Error()}");
      var error = Marshal.PtrToStringAnsi(dlerror());
      // TODO: release memory

      if (error != null)
      {
        Debug.LogError(error);
      }
    }
#endif
  }

  /// <summary>
  ///   ライブラリをロードする。
  /// </summary>
  /// <param name="name">
  ///   ライブラリ名 (e.g. libfoo.so -> foo)
  /// </param>
  public static void LoadLibrary(string name)
  {
#if UNITY_ANDROID && !UNITY_EDITOR
    using (var system = new AndroidJavaClass("java.lang.System"))
    {
      system.CallStatic("loadLibrary", name);
    }
#else
    throw new NotSupportedException("Androidのみ対応");
#endif
  }

  [DllImport("dl", SetLastError = true, ExactSpelling = true)]
  private static extern IntPtr dlopen(string name, int flags);

  [DllImport("dl", ExactSpelling = true)]
  private static extern IntPtr dlerror();

  [DllImport("dl", ExactSpelling = true)]
  private static extern int dlclose(IntPtr handle);

  [DllImport("kernel32", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
  private static extern IntPtr LoadLibraryW(string path);

  [DllImport("kernel32", SetLastError = true)]
  [return: MarshalAs(UnmanagedType.Bool)]
  private static extern bool FreeLibrary(IntPtr handle);
}

適当なスクリプトから、以下のようにライブラリをロードすると、DllNotFoundExceptionよりはまともなエラーメッセージが表示されます。

NativeLibraryTester.Load("/path/to/library");

ちなみに

一般的な方法なのか分かりませんが、 Plugin InspectorLoad on startup にチェックを入れて Apply すると、実行時のメッセージよりもなぜか詳しいメッセージが出ることがある(最初から出して)ので、UnityEditorで開発中は、こちらも有力な方法な気がします。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0