diff --git a/jni/jffi/Library.c b/jni/jffi/Library.c
index c1dcd86d..ddc66ec4 100644
--- a/jni/jffi/Library.c
+++ b/jni/jffi/Library.c
@@ -64,6 +64,7 @@ enum { RTLD_LAZY=1, RTLD_NOW, RTLD_GLOBAL, RTLD_LOCAL };
const char *e = dlerror(); snprintf(buf, size, "%s", e ? e : "unknown"); \
} while(0)
# define dl_sym(handle, name) dlsym(handle, name)
+# define dl_vsym(handle, name, version) dlvsym(handle, name, version)
# define dl_close(handle) dlclose(handle)
#ifndef RTLD_LOCAL
# define RTLD_LOCAL 8
@@ -154,6 +155,28 @@ Java_com_kenai_jffi_Foreign_dlsym(JNIEnv* env, jclass cls, jlong handle, jstring
return p2j(addr);
}
+JNIEXPORT jlong JNICALL
+Java_com_kenai_jffi_Foreign_dlvsym(JNIEnv* env, jclass cls, jlong handle, jstring jsymbol, jstring jversion)
+{
+ char sym[1024];
+ char version[1024];
+ void* addr;
+
+ getMultibyteString(env, sym, jsymbol, sizeof(sym));
+ getMultibyteString(env, version, jversion, sizeof(version));
+#ifndef _WIN32
+ dlerror(); // clear any errors
+#endif
+ addr = dl_vsym(j2p(handle), sym, version);
+ if (addr == NULL) {
+ char errbuf[1024] = { 0 };
+ dl_error(errbuf, sizeof(errbuf) - 1);
+ throwException(env, UnsatisfiedLink, "%s", errbuf);
+ }
+
+ return p2j(addr);
+}
+
/*
* Class: com_kenai_jffi_Foreign
* Method: dlerror
diff --git a/libtest/GNUmakefile b/libtest/GNUmakefile
index f16ceb70..9f43f6ed 100644
--- a/libtest/GNUmakefile
+++ b/libtest/GNUmakefile
@@ -47,7 +47,7 @@ JFLAGS = -fno-omit-frame-pointer -fno-strict-aliasing
OFLAGS = -O2 $(JFLAGS)
WFLAGS = -W -Werror -Wall -Wno-unused -Wno-unused-parameter -Wno-parentheses
PICFLAGS = -fPIC
-SOFLAGS = -shared -Wl,-O1
+SOFLAGS = -shared -Wl,-O1 -Wl,--version-script,$(SRC_DIR)/libtest.map
LDFLAGS += $(SOFLAGS)
IFLAGS = -I"$(BUILD_DIR)"
@@ -127,17 +127,17 @@ ifeq ($(OS), solaris)
CC = gcc
CFLAGS += -std=c99
LD = /usr/ccs/bin/ld
- SOFLAGS = -shared -static-libgcc
+ SOFLAGS = -shared -static-libgcc -Wl,--version-script,$(SRC_DIR)/libtest.map
endif
ifeq ($(OS), aix)
LIBEXT = a
- SOFLAGS = -shared -static-libgcc
+ SOFLAGS = -shared -static-libgcc -Wl,--version-script,$(SRC_DIR)/libtest.map
PICFLAGS += -pthread
endif
ifneq ($(findstring bsd, $(OS)),)
- SOFLAGS = -shared -static-libgcc
+ SOFLAGS = -shared -static-libgcc -Wl,--version-script,$(SRC_DIR)/libtest.map
CFLAGS += -pthread
LDFLAGS += -pthread
endif
diff --git a/libtest/VersionedSymbols.c b/libtest/VersionedSymbols.c
new file mode 100644
index 00000000..57ad9c5c
--- /dev/null
+++ b/libtest/VersionedSymbols.c
@@ -0,0 +1,14 @@
+int
+old_answer(void)
+{
+ return 41;
+}
+
+int
+new_answer(void)
+{
+ return 42;
+}
+
+__asm__(".symver old_answer,answer@VERS_1.0");
+__asm__(".symver new_answer,answer@@VERS_1.1");
diff --git a/libtest/libtest.map b/libtest/libtest.map
new file mode 100644
index 00000000..a4d74572
--- /dev/null
+++ b/libtest/libtest.map
@@ -0,0 +1,2 @@
+VERS_1.0 {};
+VERS_1.1 {};
diff --git a/src/main/java/com/kenai/jffi/Foreign.java b/src/main/java/com/kenai/jffi/Foreign.java
index d3606479..07aaab39 100644
--- a/src/main/java/com/kenai/jffi/Foreign.java
+++ b/src/main/java/com/kenai/jffi/Foreign.java
@@ -290,6 +290,16 @@ static boolean isMemoryProtectionEnabled() {
*/
static native long dlsym(long handle, String name);
+ /**
+ * Locates the memory address of the specified version of a dynamic library symbol.
+ *
+ * @param handle A dynamic library handle obtained from {@link #dlopen}
+ * @param name The name of the symbol.
+ * @param version The version of the symbol.
+ * @return The address where the symbol in loaded in memory.
+ */
+ static native long dlvsym(long handle, String name, String version);
+
/**
* Gets the last error raised by {@link #dlopen} or {@link #dlsym}
*
diff --git a/src/main/java/com/kenai/jffi/Library.java b/src/main/java/com/kenai/jffi/Library.java
index 9ce61fd9..34a0ff3f 100644
--- a/src/main/java/com/kenai/jffi/Library.java
+++ b/src/main/java/com/kenai/jffi/Library.java
@@ -182,6 +182,23 @@ public final long getSymbolAddress(String name) {
}
}
+ /**
+ * Gets the address of a symbol within the Library.
+ *
+ * @param name The name of the symbol to locate.
+ * @param version The version of the symbol to locate.
+ * @return The address of the symbol within the current address space.
+ */
+ public final long getSymbolAddressWithVersion(String name, String version) {
+ try {
+ return foreign.dlvsym(handle, name, version);
+
+ } catch (UnsatisfiedLinkError ex) {
+ lastError.set(foreign.dlerror());
+ return 0;
+ }
+ }
+
/**
* Gets the current error string from dlopen/LoadLibrary.
*
diff --git a/src/test/java/com/kenai/jffi/UnitHelper.java b/src/test/java/com/kenai/jffi/UnitHelper.java
index 87460ab1..e8f9a346 100644
--- a/src/test/java/com/kenai/jffi/UnitHelper.java
+++ b/src/test/java/com/kenai/jffi/UnitHelper.java
@@ -112,6 +112,13 @@ public static Address findSymbol(String name) {
}
return new Address(address);
}
+ public static Address findSymbolWithVersion(String name, String version) {
+ final long address = LibraryHolder.libtest.getSymbolAddressWithVersion(name, version);
+ if (address == 0L) {
+ throw new UnsatisfiedLinkError("Could not locate symbol '" + name + "' at version '" + version + "'");
+ }
+ return new Address(address);
+ }
private static final class NativeInvocationHandler implements InvocationHandler {
private final ConcurrentMap invokers
= new ConcurrentHashMap();
diff --git a/src/test/java/com/kenai/jffi/VersionTest.java b/src/test/java/com/kenai/jffi/VersionTest.java
new file mode 100644
index 00000000..f9a46679
--- /dev/null
+++ b/src/test/java/com/kenai/jffi/VersionTest.java
@@ -0,0 +1,44 @@
+package com.kenai.jffi;
+
+import org.junit.Test;
+
+import com.kenai.jffi.InvokerTest.HeapInvoker;
+import com.kenai.jffi.InvokerTest.NativeInvoker;
+import com.kenai.jffi.UnitHelper.Address;
+
+import static org.junit.Assert.assertEquals;
+
+public class VersionTest {
+
+ public VersionTest() {
+ }
+
+ @Test public void old_answer() {
+ Invoker invoker = new NativeInvoker();
+
+ Address sym = UnitHelper.findSymbolWithVersion("answer", "VERS_1.0");
+ Function function = new Function(sym.address, Type.SINT);
+ CallContext ctx = new CallContext(Type.SINT);
+
+ long res = invoker.invokeN0(ctx, function.getFunctionAddress());
+
+ assertEquals(41, res);
+ }
+
+ @Test public void new_answer() {
+ Invoker invoker = new HeapInvoker();
+
+ Address sym = UnitHelper.findSymbolWithVersion("answer", "VERS_1.1");
+ Function function = new Function(sym.address, Type.SINT);
+ CallContext ctx = new CallContext(Type.SINT);
+
+ long res = invoker.invokeN0(ctx, function.getFunctionAddress());
+
+ assertEquals(42, res);
+ }
+
+ @Test(expected = UnsatisfiedLinkError.class)
+ public void future_answer() {
+ UnitHelper.findSymbolWithVersion("answer", "VERS_1.2");
+ }
+}