JNA: указанная процедура не найдена

Я пытался узнать, как работает JNA, поэтому решил использовать API Spotify (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);

person nbarraille    schedule 01.03.2011    source источник


Ответы (3)


Я наконец-то нашел решение, открыв libspotify.dll с помощью Dependency Walker: компилятор добавил некоторую дополнительную информацию к имени метода (префикс подчеркивания и суффикс @4 или @8).

Мне пришлось:

  • Создайте реализацию FunctionMapper, которая переименует все мои методы в соответствии с реальными именами (доступно в Dependency Walker)
  • Создайте экземпляр моей библиотеки с экземпляром этого преобразователя на карте параметров.
person nbarraille    schedule 02.03.2011
comment
Ах. Заголовок объявляет методы с помощью _stdcall. Я привык к _cdecl. Подробнее об изменении имени читайте на странице en.wikipedia.org/wiki/Name_mangling . - person Andy Thomas; 02.03.2011
comment
JNA поставляется с StdCallFunctionMapper, который обрабатывает для вас искажение имени __stdcall. Вы также должны убедиться, что ваша библиотека реализует StdCallLibrary, что указывает JNA на использование соглашения stdcall для этой библиотеки. - person technomage; 12.11.2011

    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 «художник»?

person Andy Thomas    schedule 01.03.2011
comment
Да, это непрозрачный тип, и я не могу найти никакого метода создания объекта sp_artist. Единственные методы, которые я нашел, были в листинге №4. Как вы думаете, ошибка связана с тем, что созданный мной объект Java не соответствует объекту C sp_artist? И нет, я не получил никакой ошибки в определении художника. Спасибо! - person nbarraille; 01.03.2011
comment
@nathanb - я думаю, что это, скорее всего, приведет к мусорным данным и потенциальной перезаписи памяти на стороне C. Я взглянул на Spotify API. Как вы сказали, sp_artist намеренно непрозрачен. Экземпляр можно получить из методов, которые возвращают sp_artist* -- выполните поиск SP_LIBEXPORT(sp_artist *). На первый взгляд кажется, что первой функцией, которую вы должны вызвать, будет sp_session_create(), которая принимает непрозрачную структуру. Это может быть более легким местом для начала. Кстати, JNA предоставляет тип Pointer. - person Andy Thomas; 01.03.2011
comment
Спасибо, что нашли время проверить API. Я согласен с тем, что я должен начать, как задумано API, с создания сеанса, поэтому я изменил свой код, чтобы сделать это (я отредактировал свой первый пост). Но я все еще получил ту же ошибку. Любая идея? Спасибо! - person nbarraille; 01.03.2011
comment
Наконец нашел решение, посмотрите мой ответ. Спасибо большое за помощь. - person nbarraille; 02.03.2011

@technomage комментарий очень полезен. Вот подробности:

Интерфейс может оставаться одинаковым для всех платформ:

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);

У меня работает с JNA 4.5.1 в 32-разрядной версии Windows 7, Mac OS 10.13.2, Unbuntu Linux 16.04 64-разрядной версии. Я еще не тестировал другие платформы и не компилировал нативную библиотеку самостоятельно, так что ваш пробег может отличаться.


Вот еще подробности:

Изначально мой интерфейс выглядел так:

Foo extends Library {
    void foo();
}

и я попытался загрузить родную библиотеку следующим образом:

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

Работало на Mac и Linux, но не на 32-разрядной версии Windows 7: Ошибка при поиске функции '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 :-(

Я думал, что мне понадобится отдельный путь кода для каждой платформы, может быть, даже добавить интерфейс Library или StdCallLibrary динамически (с другим Proxy), но потом я обнаружил, что мы можем пообедать и съесть его тоже! См. выше.

Однако я не уверен, указано ли это конкретное поведение JNA или, скорее, счастливая случайность, которая может измениться со следующим выпуском JNA. Во всяком случае, это достаточно хорошо для меня.

person jcsahnwaldt Reinstate Monica    schedule 25.01.2018