forked from java-native-access/jna
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement an InvocationHandler to handle function resolving by ordina…
…l value Closes: java-native-access#638
- Loading branch information
1 parent
5d7e4b0
commit 5f1c191
Showing
5 changed files
with
302 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
178 changes: 178 additions & 0 deletions
178
contrib/platform/src/com/sun/jna/platform/win32/OrdinalValueHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
77 changes: 77 additions & 0 deletions
77
contrib/platform/test/com/sun/jna/platform/win32/OrdinalValueHelperTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters