【问题标题】:JNA: The specified procedure could not be foundJNA:找不到指定的程序
【发布时间】:2011-03-01 14:48:07
【问题描述】:

我试图了解 JNA 的工作原理,因此我决定使用 spotify API (libspotify 0.0.7)。我设法正确加载了我的 dll,但看起来我的代码没有找到 API 中定义的任何方法。

这是我的代码:

我的主文件:

public class Test{
    private static final int SPOTIFY_API_VERSION = 7;
private static final char[] APP_KEY = { /* MY APP KEY HERE */ };

    static{
        System.loadLibrary("libspotify");
    }

    public static void main(String[] args){
    JLibspotify libs = JLibspotify.INSTANCE;

    sp_session mySession = new sp_session();
    sp_session_config cfg = new sp_session_config();
    cfg.api_version = SPOTIFY_API_VERSION;
    cfg.cache_location = "tmp";
    cfg.settings_location = "tmp";
    cfg.application_key = APP_KEY;
    cfg.application_key_size = APP_KEY.length;
    cfg.user_agent = "spshell";
    cfg.callbacks = null;

    libs.sp_session_create(cfg, mySession);
}
}

我的图书馆界面:

public interface JLibspotify extends Library {  
    JLibspotify INSTANCE = (JLibspotify)Native.loadLibrary("libspotify", JLibspotify.class);

    // Methods definitions
    sp_error sp_session_create(sp_session_config config, sp_session sess);
}

我的 sp_session 对象(不透明的 C 结构)

public class sp_session extends PointerType{
    public sp_session(Pointer address) {
        super(address);
    }
    public sp_session() {
        super();
    }
}

我的 sp_session_config 对象

public class sp_session_config extends Structure{
    public int api_version; // The version of the Spotify API your application is compiled with.
    public String cache_location;
    public String settings_location;
    public char[] application_key}; // Your application key.
    public int application_key_size; // The size of the application key in bytes
    public String user_agent;
    public sp_session_callbacks callbacks; // Delivery callbacks for session events. NULL if not interested in any callbacks
    public Pointer userdata; // User supplied data for your application
    public boolean compress_playlists;
    public boolean dont_save_metadata_for_playlists;
    public boolean initially_unload_playlists;
}

我的 sp_error 枚举

public enum sp_error {
    SP_ERROR_OK, 
    SP_ERROR_BAD_API_VERSION, 
    SP_ERROR_API_INITIALIZATION_FAILED, 
    SP_ERROR_TRACK_NOT_PLAYABLE, 
    SP_ERROR_RESOURCE_NOT_LOADED, 
    SP_ERROR_BAD_APPLICATION_KEY, 
    SP_ERROR_BAD_USERNAME_OR_PASSWORD, 
    SP_ERROR_USER_BANNED, 
    SP_ERROR_UNABLE_TO_CONTACT_SERVER, 
    SP_ERROR_CLIENT_TOO_OLD, 
    SP_ERROR_OTHER_PERMANENT, 
    SP_ERROR_BAD_USER_AGENT, 
    SP_ERROR_MISSING_CALLBACK, 
    SP_ERROR_INVALID_INDATA, 
    SP_ERROR_INDEX_OUT_OF_RANGE, 
    SP_ERROR_USER_NEEDS_PREMIUM, 
    SP_ERROR_OTHER_TRANSIENT, 
    SP_ERROR_IS_LOADING, 
    SP_ERROR_NO_STREAM_AVAILABLE, 
    SP_ERROR_PERMISSION_DENIED, 
    SP_ERROR_INBOX_IS_FULL, 
    SP_ERROR_NO_CACHE, 
    SP_ERROR_NO_SUCH_USER
}

我的异常堆栈跟踪

Exception in thread "main" java.lang.UnsatisfiedLinkError: Error looking up function 'sp_session_create': The specified procedure could not be found.

at com.sun.jna.Function.<init>(Function.java:129)
at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:250)
at com.sun.jna.Library$Handler.invoke(Library.java:191)
at $Proxy0.sp_session_create(Unknown Source)
at com.nbarraille.jspotify.main.Test.main(Test.java:49)

我尝试运行的方法的 C++ 声明

/**
 * Initialize a session. The session returned will be initialized, but you will need
 * to log in before you can perform any other operation
 *
 * Here is a snippet from \c spshell.c:
 * @dontinclude spshell.c
 * @skip config.api_version
 * @until }
 *
 * @param[in]   config    The configuration to use for the session
 * @param[out]  sess      If successful, a new session - otherwise NULL
 *
 * @return                One of the following errors, from ::sp_error
 *                        SP_ERROR_OK
 *                        SP_ERROR_BAD_API_VERSION
 *                        SP_ERROR_BAD_USER_AGENT
 *                        SP_ERROR_BAD_APPLICATION_KEY
 *                        SP_ERROR_API_INITIALIZATION_FAILED
 */
SP_LIBEXPORT(sp_error) sp_session_create(const sp_session_config *config, sp_session **sess);

【问题讨论】:

    标签: java api java-native-interface jna


    【解决方案1】:

    终于通过使用 Dependency Walker 打开 libspotify.dll 找到了解决方案: 编译器在方法名称中添加了一些额外信息(下划线前缀和@4 或@8 后缀)。

    我不得不:

    • 创建一个 FunctionMapper 的实现,根据真实名称重命名我的所有方法(在 Dependency Walker 中可用)
    • 在选项映射中使用此映射器的实例来实例化我的库。

    【讨论】:

    • 啊。标头使用 _stdcall 声明方法。我习惯了_cdecl。在en.wikipedia.org/wiki/Name_mangling 有更多关于名称修改的信息。
    • JNA 带有一个 StdCallFunctionMapper,可以为您处理 __stdcall 名称修改。您还应该确保您的库实现 StdCallLibrary,这指示 JNA 使用该库的 stdcall 约定。
    【解决方案2】:
        By the way, I don't have access to the definition of the sp_artist structure in C, I just reconstructed it based on the methods offered by the API, could it be the problem?
    

    如果您无权访问它,JNA 也无权访问它。如果它是不透明类型,请查找创建、修改和删除它的函数。

    另外,前面的语句,Java 变量“artist”的五行定义是否有错误?

    【讨论】:

    • 是的,它是一个不透明的类型,我找不到sp_artist对象的任何创建方法。我发现的唯一方法是清单 #4 中的方法。您是否认为错误是因为我创建的 Java 对象与 C sp_artist 对象不匹配?不,我对艺术家的定义没有任何错误。谢谢!
    • @nathanb - 我认为这更有可能导致 C 端的垃圾数据和潜在的内存覆盖。我看了一眼 Spotify API。正如你所说, sp_artist 是故意不透明的。实例可从返回 sp_artist* 的方法中获得——搜索“SP_LIBEXPORT(sp_artist *)”。乍一看,您要调用的第一个函数似乎是 sp_session_create(),它采用非透明结构。这可能是一个更容易开始的地方。顺便说一句,JNA 提供了一个指针类型。
    • 感谢您抽出宝贵时间查看 API。我同意我应该按照 API 的意图通过创建会话来开始,所以我更改了我的代码来这样做(我编辑了我的第一篇文章)。但我仍然遇到同样的错误。任何想法?谢谢!
    • 终于找到了解决办法,看看我的回答。非常感谢您的帮助。
    【解决方案3】:

    @technomagecomment 很有帮助。以下是详细信息:

    界面可以在所有平台上保持不变:

    Foo extends Library {
        void foo();
    }
    

    只需添加StdCallLibrary.FUNCTION_MAPPER 函数映射器:

    Map<String, ?> options = Collections.singletonMap(
        Library.OPTION_FUNCTION_MAPPER,
        StdCallLibrary.FUNCTION_MAPPER
    );
    Foo proxy = Native.loadLibrary("foo", Foo.class, options);
    

    适用于我在 Windows 7 32 位、Mac OS 10.13.2、Unbuntu Linux 16.04 64 位上的 JNA 4.5.1。我还没有测试过其他平台,也没有自己编译原生库,所以你的里程可能会有所不同。


    这里有更多细节:

    最初,我的界面是这样的:

    Foo extends Library {
        void foo();
    }
    

    我尝试像这样加载本机库:

    Native.loadLibrary("foo", Foo.class);
    

    适用于 Mac 和 Linux,但不适用于 Windows 7 32 位:查找函数“foo”时出错:找不到指定的过程。

    所以我改变了我的界面:

    Foo extends StdCallLibrary {
        void foo();
    }
    

    我尝试使用 stdcall 特定的函数映射器加载库:

    Map<String, ?> options = Collections.singletonMap(
        Library.OPTION_FUNCTION_MAPPER,
        StdCallLibrary.FUNCTION_MAPPER
    );
    Foo proxy = Native.loadLibrary("foo", Foo.class, options);
    

    现在它可以在 32 位的 Windows 7 上运行,但不能在 Mac 或 Linux 上运行:无法识别的调用约定:63 :-(

    我以为每个平台都需要不同的代码路径,甚至可以动态添加LibraryStdCallLibrary 接口(加上另一个Proxy),但后来我发现我们可以吃午饭了它也!见上文。

    我不确定这种特殊行为是由 JNA 指定的,还是可能会随着 JNA 的下一个版本而改变的幸运事故。不管怎样,对我来说已经足够了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-24
      • 1970-01-01
      • 2020-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多