I was trying to learn how JNA works, so I decided to use the spotify API (libspotify 0.0.7). I managed to load my dll correctly, but then it looks like my code is not finding any of the method defined in the API.
Here is my code:
My main file:
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);
}
}
My Library interface:
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);
}
My sp_session Object (opaque C struct)
public class sp_session extends PointerType{
public sp_session(Pointer address) {
super(address);
}
public sp_session() {
super();
}
}
My sp_session_config object
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;
}
My sp_error enum
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
}
My Exception Stack Trace
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)
The C++ declaration of the method I'm trying to run
/**
* 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);
I finally found the solution by opening the libspotify.dll with Dependency Walker: The compiler added some extra information to the method name (a underscore prefix and a @4 or @8 suffix).
I had to:
- Create an implementation of FunctionMapper that renamed all my methods according to the real names (available in Dependency Walker)
- Instantiate my Library with an instance of this mapper in the options map.
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?
If you don't have access to it, neither does JNA. If it's an opaque type, look for functions to create, modify and delete it.
Also, did you get an error on the preceding statement, the five-line definition of the Java variable "artist"?
@technomage's comment is very helpful. Here are the details:
The interface can remain the same for all platforms:
Foo extends Library {
void foo();
}
Just add the StdCallLibrary.FUNCTION_MAPPER
function mapper:
Map<String, ?> options = Collections.singletonMap(
Library.OPTION_FUNCTION_MAPPER,
StdCallLibrary.FUNCTION_MAPPER
);
Foo proxy = Native.loadLibrary("foo", Foo.class, options);
Works for me with JNA 4.5.1 on Windows 7 32-bit, Mac OS 10.13.2, Unbuntu Linux 16.04 64-bit. I haven't tested other platforms yet, and I didn't compile the native library myself, so your mileage may vary.
Here are even more details:
Initially, my interface looked like this:
Foo extends Library {
void foo();
}
and I tried to load the native library like this:
Native.loadLibrary("foo", Foo.class);
Worked on Mac and Linux, but not on Windows 7 32-bit: Error looking up function 'foo': The specified procedure could not be found.
So I changed my interface:
Foo extends StdCallLibrary {
void foo();
}
and I tried to load the library with the stdcall specific function mapper:
Map<String, ?> options = Collections.singletonMap(
Library.OPTION_FUNCTION_MAPPER,
StdCallLibrary.FUNCTION_MAPPER
);
Foo proxy = Native.loadLibrary("foo", Foo.class, options);
Now it worked on Windows 7 32-bit, but not on Mac or Linux: Unrecognized calling convention: 63 :-(
I thought I'd need a different code path for each platform, maybe even add the Library
or StdCallLibrary
interface dynamically (with another Proxy
), but then I found that we can have our lunch and eat it too! See above.
I'm not sure though if this particular behavior is specified by JNA or rather a lucky accident that may change with the next release of JNA. Anyway, it's good enough for me.
来源:https://stackoverflow.com/questions/5156055/jna-the-specified-procedure-could-not-be-found