Skip to content

Commit

Permalink
Implement an InvocationHandler to handle function resolving by ordina…
Browse files Browse the repository at this point in the history
…l value

Closes: java-native-access#638
  • Loading branch information
matthiasblaesing committed Aug 29, 2016
1 parent 5d7e4b0 commit 5f1c191
Show file tree
Hide file tree
Showing 5 changed files with 302 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ pom-jna-platform.xml.asc
# IntelliJ IDEA
.idea
*.iml
/contrib/platform/${build.generated.sources.dir}/
/contrib/platform/${build}/
22 changes: 22 additions & 0 deletions contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package com.sun.jna.platform.win32;

import com.sun.jna.LastErrorException;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
Expand Down Expand Up @@ -3353,4 +3354,25 @@ boolean GetVolumePathNamesForVolumeName(String lpszVolumeName,
* flags.
*/
int SetErrorMode(int umode);

/**
* Retrieves the address of an exported function or variable from the
* specified dynamic-link library (DLL).
*
* <p>
* This function is mapped to enable accessing function on win32 systems
* only accessible by their ordinal value.</p>
*
* <p>
* To access functions by their name, please use
* NativeLibrary#getFunction.</p>
*
* @param hmodule A handle to the DLL module that contains the function or
* variable. The LoadLibrary, LoadLibraryEx,
* LoadPackagedLibrary, or GetModuleHandle function returns
* this handle.
* @param ordinal ordinal value of the function export
* @return address of the exported function
*/
Pointer GetProcAddress(HMODULE hmodule, int ordinal) throws LastErrorException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@

package com.sun.jna.platform.win32;

import com.sun.jna.Function;
import com.sun.jna.LastErrorException;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.Kernel32Util;
import com.sun.jna.platform.win32.WinDef.HMODULE;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
* Allow binding of functions, that shall be bound by their ordinal number.
*
* <p>
* This is win32 specific.</p>
*
* <p>
* The invocation Handler is meant to be used together with the annotation. An
* interface is declared listing the methods to be bound, each method is
* annotated with the
* {@link com.sun.jna.platform.win32.OrdinalValueHelper.OrdinalValueInvocation}
* annotation. On this interface a static INTERFACE member is defined, that
* implements the interface by using the
* {@link com.sun.jna.platform.win32.OrdinalValueHelper.OrdinalValueInvocationHandler}
* </p>
*
* <p>This is documented in <a href="https://msdn.microsoft.com/de-de/library/windows/desktop/ms683212(v=vs.85).aspx">the MSDN - GetProcAddress</a></p>
*/
public abstract class OrdinalValueHelper {

public static class OrdinalValueInvocationHandler implements InvocationHandler {

private final NativeLibrary backingLibrary;
private final HMODULE hmodule;
private final int defaultCallflags;
private final String defaultEncoding;
private final Map<FunctionKey, Function> functionCache
= Collections.synchronizedMap(new HashMap<FunctionKey, Function>());

public OrdinalValueInvocationHandler(String backingLibraryName, int defaultCallflags, String defaultEncoding) {
// ensure library is loaded and hold it
this.backingLibrary = NativeLibrary.getInstance(backingLibraryName);
// get module handle needed to resolve function pointer via GetProcAddress
this.hmodule = Kernel32.INSTANCE.GetModuleHandle(backingLibraryName);
this.defaultCallflags = defaultCallflags;
this.defaultEncoding = defaultEncoding;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
OrdinalValueInvocation annotation = method.getAnnotation(OrdinalValueInvocation.class);
if (annotation == null) {
throw new UnsatisfiedLinkError("Method must be annotated with @OrdinalValueInvocation to be used with OrdinalValueInvocationHandler");
}

int ordinal = annotation.ordinalValue();
int callflags = defaultCallflags;
if (annotation.callflags() != Integer.MIN_VALUE) {
callflags = annotation.callflags();
}
String encoding = defaultEncoding;
if (!annotation.encoding().isEmpty()) {
encoding = annotation.encoding();
}
Class returnType = method.getReturnType();

FunctionKey key = new FunctionKey(ordinal, callflags, encoding, returnType);

Function function = functionCache.get(key);
if (function == null) {
try {
Pointer functionPointer = Kernel32.INSTANCE.GetProcAddress(hmodule, ordinal);
function = Function.getFunction(functionPointer, callflags, encoding);
functionCache.put(key, function);
} catch (LastErrorException ex) {
throw new UnsatisfiedLinkError(String.format(
"Could not find native method for method: %s#%s (Error: %s (%d))",
method.getDeclaringClass().getName(),
method.getName(),
Kernel32Util.formatMessage(ex.getErrorCode()),
ex.getErrorCode()
));
}
}
return function.invoke(returnType, args);
}

private static class FunctionKey {

private final int ordinal;
private final int callflags;
private final String encoding;
private final Class returnType;

public FunctionKey(int ordinal, int callflags, String encoding, Class returnType) {
this.ordinal = ordinal;
this.callflags = callflags;
this.encoding = encoding;
this.returnType = returnType;
}

@Override
public int hashCode() {
int hash = 3;
hash = 67 * hash + this.ordinal;
return hash;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final FunctionKey other = (FunctionKey) obj;
if (this.ordinal != other.ordinal) {
return false;
}
if (this.callflags != other.callflags) {
return false;
}
if ((this.encoding == null) ? (other.encoding != null) : !this.encoding.equals(other.encoding)) {
return false;
}
if (this.returnType != other.returnType && (this.returnType == null || !this.returnType.equals(other.returnType))) {
return false;
}
return true;
}

}
}

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface OrdinalValueInvocation {

/**
* Ordinal value to use to resolve function definition
*/
public int ordinalValue();

/**
* Encoding to use for this method call - overrides default encoding
* provided on library level.
*
* <p>
* The empty string causes the library defaults to be used.</p>
*/
public String encoding() default "";

/**
* Callflags to apply for the function call - overrides default
* callflags provided on library level.
*
* <p>
* A value of Integer.MIN_VALUE causes the library defaults to be
* used.</p>
*/
public int callflags() default Integer.MIN_VALUE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@

package com.sun.jna.platform.win32;

import com.sun.jna.Function;
import com.sun.jna.Native;
import com.sun.jna.platform.win32.OrdinalValueHelper.OrdinalValueInvocation;
import com.sun.jna.platform.win32.OrdinalValueHelper.OrdinalValueInvocationHandler;
import com.sun.jna.platform.win32.WinDef.DWORD;
import java.lang.reflect.Proxy;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;

public class OrdinalValueHelperTest {
/**
* Sample interface to demonstrate ordinal value based binding.
*
* From link.exe /dump /exports c:\\Windows\\System32\\kernel32.dll
*
* 746 2E9 0004FA20 GetTapeStatus
* 747 2EA 0002DB20 GetTempFileNameA
* 748 2EB 0002DB30 GetTempFileNameW
* 749 2EC 0002DB40 GetTempPathA
* 750 2ED 0002DB50 GetTempPathW
* 751 2EE 00026780 GetThreadContext
*
* Highest ordinal at time of writing was 1599, so the value for "WronglyMapped"
* should be save for some time.
*/
interface Kernel32Ordinal {
public static final Kernel32Ordinal INSTANCE = (Kernel32Ordinal) Proxy.newProxyInstance(Kernel32Ordinal.class.getClassLoader(),
new Class[] {Kernel32Ordinal.class},
new OrdinalValueInvocationHandler("kernel32", Function.ALT_CONVENTION, null)
);

@OrdinalValueInvocation(ordinalValue = 750)
void GetTempPathW(int bufferLength, char[] buffer);

@OrdinalValueInvocation(ordinalValue = 749)
void GetTempPathA(int bufferLength, byte[] buffer);

@OrdinalValueInvocation(ordinalValue = 65000)
void WronglyMapped();

void Unmapped();
}


@Test
public void testBasicInvoke() {
// Compare results of the two ordinal based calls (Ansi and Wide variants)
// with the name based call (classic).
char[] namedBuffer = new char[2048];
Kernel32.INSTANCE.GetTempPath(new DWORD(namedBuffer.length), namedBuffer);
String namedString = Native.toString(namedBuffer);
char[] ordinal750Buffer = new char[2048];
Kernel32Ordinal.INSTANCE.GetTempPathW(ordinal750Buffer.length, ordinal750Buffer);
String ordinal750String = Native.toString(ordinal750Buffer);
byte[] ordinal749Buffer = new byte[2048];
Kernel32Ordinal.INSTANCE.GetTempPathA(ordinal749Buffer.length, ordinal749Buffer);
String ordinal749String = Native.toString(ordinal749Buffer, Native.getDefaultStringEncoding());

assertArrayEquals(namedBuffer, ordinal750Buffer);
assertEquals(namedString, ordinal750String);
assertEquals(namedString, ordinal749String);
}

@Test(expected = UnsatisfiedLinkError.class)
public void testWronglyMapped() {
Kernel32Ordinal.INSTANCE.WronglyMapped();
}

@Test(expected = UnsatisfiedLinkError.class)
public void testUnmapped() {
Kernel32Ordinal.INSTANCE.Unmapped();
}
}
25 changes: 23 additions & 2 deletions src/com/sun/jna/Function.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public static Function getFunction(String libraryName, String functionName, int
* @param p Native function pointer
*/
public static Function getFunction(Pointer p) {
return getFunction(p, 0);
return getFunction(p, 0, null);
}

/**
Expand All @@ -159,7 +159,28 @@ public static Function getFunction(Pointer p) {
* Function <a href="#callflags">call flags</a>
*/
public static Function getFunction(Pointer p, int callFlags) {
return new Function(p, callFlags, null);
return getFunction(p, callFlags, null);
}

/**
* Obtain a <code>Function</code> representing a native
* function pointer. In general, this function should be used by dynamic
* languages; Java code should allow JNA to bind to a specific Callback
* interface instead by defining a return type or Structure field type.
*
* <p>The allocated instance represents a pointer to the native
* function pointer.
*
* @param p
* Native function pointer
* @param callFlags
* Function <a href="#callflags">call flags</a>
* @param encoding
* Encoding to use for conversion between Java and native
* strings.
*/
public static Function getFunction(Pointer p, int callFlags, String encoding) {
return new Function(p, callFlags, encoding);
}

// Keep a reference to the NativeLibrary so it does not get garbage
Expand Down

0 comments on commit 5f1c191

Please sign in to comment.