/* Copyright (c) 2007-2013 Timothy Wall, All Rights Reserved * * The contents of this file is dual-licensed under 2 * alternative Open Source/Free licenses: LGPL 2.1 or later and * Apache License 2.0. (starting with JNA version 4.0.0). * * You can freely decide which license you want to apply to * the project. * * You may obtain a copy of the LGPL License at: * * http://www.gnu.org/licenses/licenses.html * * A copy is also included in the downloadable source code package * containing JNA, in file "LGPL2.1". * * You may obtain a copy of the Apache License at: * * http://www.apache.org/licenses/ * * A copy is also included in the downloadable source code package * containing JNA, in file "AL2.0". */ package com.sun.jna; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import com.sun.jna.Callback.UncaughtExceptionHandler; import com.sun.jna.CallbacksTest.TestLibrary.CbCallback; import com.sun.jna.ptr.IntByReference; import com.sun.jna.ptr.PointerByReference; import com.sun.jna.win32.W32APIOptions; import junit.framework.TestCase; /** Exercise callback-related functionality. * * @author twall@users.sf.net */ //@SuppressWarnings("unused") public class CallbacksTest extends TestCase implements Paths { // On OSX, on Oracle JVM 1.8+, pthread cleanup thinks the native thread is // not attached, and the JVM never unmaps the defunct native thread. In // order to avoid this situation causing tests to time out, we need to // explicitly detach the native thread after our Java code is done with it. // Also reproducible on Ubuntu 6 (x86-64), Java 6 private static final boolean THREAD_DETACH_BUG = Platform.isMac() || (Platform.isLinux() && Platform.is64Bit()); private static final String UNICODE = "[\u0444]"; private static final double DOUBLE_MAGIC = -118.625d; private static final float FLOAT_MAGIC = -118.625f; private static final int THREAD_TIMEOUT = 5000; protected void waitFor(Thread thread) { long start = System.currentTimeMillis(); while (thread.isAlive()) { try { Thread.sleep(10); } catch(InterruptedException e) { } if (System.currentTimeMillis() - start > THREAD_TIMEOUT) { fail("Timed out waiting for native thread " + thread + " to detach and terminate"); } } } public static class SmallTestStructure extends Structure { public static final List<String> FIELDS = createFieldsOrder("value"); public double value; public static int allocations = 0; @Override protected void allocateMemory(int size) { super.allocateMemory(size); ++allocations; } public SmallTestStructure() { } public SmallTestStructure(Pointer p) { super(p); read(); } @Override protected List<String> getFieldOrder() { return FIELDS; } } public static class TestStructure extends Structure { public static class ByValue extends TestStructure implements Structure.ByValue { } public static interface TestCallback extends Callback { TestStructure.ByValue callback(TestStructure.ByValue s); } public static final List<String> FIELDS = createFieldsOrder("c", "s", "i", "j", "inner"); public byte c; public short s; public int i; public long j; public SmallTestStructure inner; @Override protected List<String> getFieldOrder() { return FIELDS; } } public static interface TestLibrary extends Library { interface NoMethodCallback extends Callback { } interface CustomMethodCallback extends Callback { void invoke(); } interface TooManyMethodsCallback extends Callback { void invoke(); void invoke2(); } interface MultipleMethodsCallback extends Callback { void invoke(); void callback(); } interface VoidCallback extends Callback { void callback(); } void callVoidCallback(VoidCallback c); void callVoidCallbackThreaded(VoidCallback c, int count, int ms, String name); interface VoidCallbackCustom extends Callback { void customMethodName(); } abstract class VoidCallbackCustomAbstract implements VoidCallbackCustom { @Override public void customMethodName() { } } class VoidCallbackCustomDerived extends VoidCallbackCustomAbstract { } void callVoidCallback(VoidCallbackCustom c); interface BooleanCallback extends Callback { boolean callback(boolean arg, boolean arg2); } boolean callBooleanCallback(BooleanCallback c, boolean arg, boolean arg2); interface ByteCallback extends Callback { byte callback(byte arg, byte arg2); } byte callInt8Callback(ByteCallback c, byte arg, byte arg2); interface ShortCallback extends Callback { short callback(short arg, short arg2); } short callInt16Callback(ShortCallback c, short arg, short arg2); interface Int32Callback extends Callback { int callback(int arg, int arg2); } int callInt32Callback(Int32Callback c, int arg, int arg2); interface NativeLongCallback extends Callback { NativeLong callback(NativeLong arg, NativeLong arg2); } NativeLong callNativeLongCallback(NativeLongCallback c, NativeLong arg, NativeLong arg2); interface Int64Callback extends Callback { long callback(long arg, long arg2); } long callInt64Callback(Int64Callback c, long arg, long arg2); interface FloatCallback extends Callback { float callback(float arg, float arg2); } float callFloatCallback(FloatCallback c, float arg, float arg2); interface DoubleCallback extends Callback { double callback(double arg, double arg2); } double callDoubleCallback(DoubleCallback c, double arg, double arg2); interface StructureCallback extends Callback { SmallTestStructure callback(SmallTestStructure arg); } SmallTestStructure callStructureCallback(StructureCallback c, SmallTestStructure arg); interface StringCallback extends Callback { String callback(String arg, String arg2); } String callStringCallback(StringCallback c, String arg, String arg2); interface WideStringCallback extends Callback { WString callback(WString arg, WString arg2); } WString callWideStringCallback(WideStringCallback c, WString arg, WString arg2); interface CopyArgToByReference extends Callback { int callback(int arg, IntByReference result); } interface StringArrayCallback extends Callback { String[] callback(String[] arg); } Pointer callStringArrayCallback(StringArrayCallback c, String[] arg); int callCallbackWithByReferenceArgument(CopyArgToByReference cb, int arg, IntByReference result); TestStructure.ByValue callCallbackWithStructByValue(TestStructure.TestCallback callback, TestStructure.ByValue cbstruct); interface CbCallback extends Callback { CbCallback callback(CbCallback arg); } CbCallback callCallbackWithCallback(CbCallback cb); interface Int32CallbackX extends Callback { public int callback(int arg); } Int32CallbackX returnCallback(); Int32CallbackX returnCallbackArgument(Int32CallbackX cb); interface CustomCallback extends Callback { Custom callback(Custom arg1, Custom arg2); } int callInt32Callback(CustomCallback cb, int arg1, int arg2); class CbStruct extends Structure { public Callback cb; @Override protected List<String> getFieldOrder() { return Arrays.asList(new String[] { "cb" }); } } void callCallbackInStruct(CbStruct cbstruct); // Union (by value) class TestUnion extends Union implements Structure.ByValue { public String f1; public int f2; } interface UnionCallback extends Callback { TestUnion invoke(TestUnion arg); } TestUnion testUnionByValueCallbackArgument(UnionCallback cb, TestUnion arg); } TestLibrary lib; @Override protected void setUp() { lib = Native.loadLibrary("testlib", TestLibrary.class); } @Override protected void tearDown() { lib = null; } public static class Custom implements NativeMapped { private int value; public Custom() { } public Custom(int value) { this.value = value; } @Override public Object fromNative(Object nativeValue, FromNativeContext context) { return new Custom(((Integer)nativeValue).intValue()); } @Override public Class<?> nativeType() { return Integer.class; } @Override public Object toNative() { return Integer.valueOf(value); } @Override public boolean equals(Object o) { return o instanceof Custom && ((Custom)o).value == value; } } public void testLookupNullCallback() { assertNull("NULL pointer should result in null callback", CallbackReference.getCallback(null, null)); try { CallbackReference.getCallback(TestLibrary.VoidCallback.class, new Pointer(0)); fail("Null pointer lookup should fail"); } catch(NullPointerException e) { // expected } } public void testLookupNonCallbackClass() { try { CallbackReference.getCallback(String.class, new Pointer(0)); fail("Request for non-Callback class should fail"); } catch(IllegalArgumentException e) { } } public void testThrowOnMultiplyMappedCallback() { try { Pointer p = new Pointer(getName().hashCode()); CallbackReference.getCallback(TestLibrary.VoidCallback.class, p); CallbackReference.getCallback(TestLibrary.ByteCallback.class, p); fail("Multiply-mapped callback should fail"); } catch(IllegalStateException e) { } } public void testNoMethodCallback() { try { CallbackReference.getCallback(TestLibrary.NoMethodCallback.class, new Pointer(getName().hashCode())); fail("Callback with no callback method should fail"); } catch(IllegalArgumentException e) { } } public void testCustomMethodCallback() { CallbackReference.getCallback(TestLibrary.CustomMethodCallback.class, new Pointer(getName().hashCode())); } public void testTooManyMethodsCallback() { try { CallbackReference.getCallback(TestLibrary.TooManyMethodsCallback.class, new Pointer(getName().hashCode())); fail("Callback lookup with too many methods should fail"); } catch(IllegalArgumentException e) { } } public void testMultipleMethodsCallback() { CallbackReference.getCallback(TestLibrary.MultipleMethodsCallback.class, new Pointer(getName().hashCode())); } public void testNativeFunctionPointerStringValue() { Callback cb = CallbackReference.getCallback(TestLibrary.VoidCallback.class, new Pointer(getName().hashCode())); Class<?> cls = CallbackReference.findCallbackClass(cb.getClass()); assertTrue("toString should include Java Callback type: " + cb + " (" + cls + ")", cb.toString().indexOf(cls.getName()) != -1); } public void testLookupSameCallback() { Callback cb = CallbackReference.getCallback(TestLibrary.VoidCallback.class, new Pointer(getName().hashCode())); Callback cb2 = CallbackReference.getCallback(TestLibrary.VoidCallback.class, new Pointer(getName().hashCode())); assertEquals("Callback lookups for same pointer should return same Callback object", cb, cb2); } // Allow direct tests to override protected Map<Callback, CallbackReference> callbackCache() { return CallbackReference.callbackMap; } // Fails on OpenJDK(linux/ppc), probably finalize not run public void testGCCallbackOnFinalize() throws Exception { final boolean[] called = { false }; TestLibrary.VoidCallback cb = new TestLibrary.VoidCallback() { @Override public void callback() { called[0] = true; } }; lib.callVoidCallback(cb); assertTrue("Callback not called", called[0]); Map<Callback, CallbackReference> refs = new WeakHashMap<Callback, CallbackReference>(callbackCache()); assertTrue("Callback not cached", refs.containsKey(cb)); CallbackReference ref = refs.get(cb); refs = callbackCache(); Pointer cbstruct = ref.cbstruct; cb = null; System.gc(); for (int i = 0; i < 100 && (ref.get() != null || refs.containsValue(ref)); ++i) { Thread.sleep(10); // Give the GC a chance to run System.gc(); } assertNull("Callback not GC'd", ref.get()); assertFalse("Callback still in map", refs.containsValue(ref)); ref = null; System.gc(); for (int i = 0; i < 100 && (cbstruct.peer != 0 || refs.size() > 0); ++i) { // Flush weak hash map refs.size(); try { Thread.sleep(10); // Give the GC a chance to run System.gc(); } finally {} } assertEquals("Callback trampoline not freed", 0, cbstruct.peer); } public void testFindCallbackInterface() { TestLibrary.Int32Callback cb = new TestLibrary.Int32Callback() { @Override public int callback(int arg, int arg2) { return arg + arg2; } }; assertEquals("Wrong callback interface", TestLibrary.Int32Callback.class, CallbackReference.findCallbackClass(cb.getClass())); } public void testCallVoidCallback() { final boolean[] called = { false }; TestLibrary.VoidCallback cb = new TestLibrary.VoidCallback() { @Override public void callback() { called[0] = true; } }; lib.callVoidCallback(cb); assertTrue("Callback not called", called[0]); } public void testCallInt32Callback() { final int MAGIC = 0x11111111; final boolean[] called = { false }; TestLibrary.Int32Callback cb = new TestLibrary.Int32Callback() { @Override public int callback(int arg, int arg2) { called[0] = true; return arg + arg2; } }; final int EXPECTED = MAGIC*3; int value = lib.callInt32Callback(cb, MAGIC, MAGIC*2); assertTrue("Callback not called", called[0]); assertEquals("Wrong callback value", Integer.toHexString(EXPECTED), Integer.toHexString(value)); value = lib.callInt32Callback(cb, -1, -2); assertEquals("Wrong callback return", -3, value); } public void testCallInt64Callback() { final long MAGIC = 0x1111111111111111L; final boolean[] called = { false }; TestLibrary.Int64Callback cb = new TestLibrary.Int64Callback() { @Override public long callback(long arg, long arg2) { called[0] = true; return arg + arg2; } }; final long EXPECTED = MAGIC*3; long value = lib.callInt64Callback(cb, MAGIC, MAGIC*2); assertTrue("Callback not called", called[0]); assertEquals("Wrong callback value", Long.toHexString(EXPECTED), Long.toHexString(value)); value = lib.callInt64Callback(cb, -1, -2); assertEquals("Wrong callback return", -3, value); } public void testCallFloatCallback() { final boolean[] called = { false }; final float[] args = { 0, 0 }; TestLibrary.FloatCallback cb = new TestLibrary.FloatCallback() { @Override public float callback(float arg, float arg2) { called[0] = true; args[0] = arg; args[1] = arg2; return arg + arg2; } }; final float EXPECTED = FLOAT_MAGIC*3; float value = lib.callFloatCallback(cb, FLOAT_MAGIC, FLOAT_MAGIC*2); assertTrue("Callback not called", called[0]); assertEquals("Wrong first argument", FLOAT_MAGIC, args[0], 0); assertEquals("Wrong second argument", FLOAT_MAGIC*2, args[1], 0); assertEquals("Wrong callback value", EXPECTED, value, 0); value = lib.callFloatCallback(cb, -1f, -2f); assertEquals("Wrong callback return", -3f, value, 0); } public void testCallDoubleCallback() { final boolean[] called = { false }; final double[] args = { 0, 0 }; TestLibrary.DoubleCallback cb = new TestLibrary.DoubleCallback() { @Override public double callback(double arg, double arg2) { called[0] = true; args[0] = arg; args[1] = arg2; return arg + arg2; } }; final double EXPECTED = DOUBLE_MAGIC*3; double value = lib.callDoubleCallback(cb, DOUBLE_MAGIC, DOUBLE_MAGIC*2); assertTrue("Callback not called", called[0]); assertEquals("Wrong first argument", DOUBLE_MAGIC, args[0], 0); assertEquals("Wrong second argument", DOUBLE_MAGIC*2, args[1], 0); assertEquals("Wrong callback value", EXPECTED, value, 0); value = lib.callDoubleCallback(cb, -1d, -2d); assertEquals("Wrong callback return", -3d, value, 0); } public void testCallStructureCallback() { final boolean[] called = {false}; final Pointer[] cbarg = { null }; final SmallTestStructure s = new SmallTestStructure(); final double MAGIC = 118.625; TestLibrary.StructureCallback cb = new TestLibrary.StructureCallback() { @Override public SmallTestStructure callback(SmallTestStructure arg) { called[0] = true; cbarg[0] = arg.getPointer(); arg.value = MAGIC; return arg; } }; SmallTestStructure.allocations = 0; SmallTestStructure value = lib.callStructureCallback(cb, s); assertTrue("Callback not called", called[0]); assertEquals("Wrong argument passed to callback", s.getPointer(), cbarg[0]); assertEquals("Structure argument not synched on callback return", MAGIC, s.value, 0d); assertEquals("Wrong structure return", s.getPointer(), value.getPointer()); assertEquals("Structure return not synched", MAGIC, value.value, 0d); // All structures involved should be created from pointers, with no // memory allocation at all. assertEquals("No structure memory should be allocated", 0, SmallTestStructure.allocations); } public void testCallStructureArrayCallback() { final SmallTestStructure s = new SmallTestStructure(); final SmallTestStructure[] array = (SmallTestStructure[])s.toArray(2); final double MAGIC = 118.625; TestLibrary.StructureCallback cb = new TestLibrary.StructureCallback() { @Override public SmallTestStructure callback(SmallTestStructure arg) { SmallTestStructure[] array = (SmallTestStructure[])arg.toArray(2); array[0].value = MAGIC; array[1].value = MAGIC*2; return arg; } }; SmallTestStructure value = lib.callStructureCallback(cb, s); assertEquals("Structure array element 0 not synched on callback return", MAGIC, array[0].value, 0d); assertEquals("Structure array element 1 not synched on callback return", MAGIC*2, array[1].value, 0d); } public void testCallBooleanCallback() { final boolean[] called = {false}; final boolean[] cbargs = { false, false }; TestLibrary.BooleanCallback cb = new TestLibrary.BooleanCallback() { @Override public boolean callback(boolean arg, boolean arg2) { called[0] = true; cbargs[0] = arg; cbargs[1] = arg2; return arg && arg2; } }; boolean value = lib.callBooleanCallback(cb, true, false); assertTrue("Callback not called", called[0]); assertEquals("Wrong first callback argument", true, cbargs[0]); assertEquals("Wrong second callback argument", false, cbargs[1]); assertFalse("Wrong boolean return", value); } public void testCallInt8Callback() { final boolean[] called = {false}; final byte[] cbargs = { 0, 0 }; TestLibrary.ByteCallback cb = new TestLibrary.ByteCallback() { @Override public byte callback(byte arg, byte arg2) { called[0] = true; cbargs[0] = arg; cbargs[1] = arg2; return (byte)(arg + arg2); } }; final byte MAGIC = 0x11; byte value = lib.callInt8Callback(cb, MAGIC, (byte)(MAGIC*2)); assertTrue("Callback not called", called[0]); assertEquals("Wrong first callback argument", Integer.toHexString(MAGIC), Integer.toHexString(cbargs[0])); assertEquals("Wrong second callback argument", Integer.toHexString(MAGIC*2), Integer.toHexString(cbargs[1])); assertEquals("Wrong byte return", Integer.toHexString(MAGIC*3), Integer.toHexString(value)); value = lib.callInt8Callback(cb, (byte)-1, (byte)-2); assertEquals("Wrong byte return (hi bit)", (byte)-3, value); } public void testCallInt16Callback() { final boolean[] called = {false}; final short[] cbargs = { 0, 0 }; TestLibrary.ShortCallback cb = new TestLibrary.ShortCallback() { @Override public short callback(short arg, short arg2) { called[0] = true; cbargs[0] = arg; cbargs[1] = arg2; return (short)(arg + arg2); } }; final short MAGIC = 0x1111; short value = lib.callInt16Callback(cb, MAGIC, (short)(MAGIC*2)); assertTrue("Callback not called", called[0]); assertEquals("Wrong first callback argument", Integer.toHexString(MAGIC), Integer.toHexString(cbargs[0])); assertEquals("Wrong second callback argument", Integer.toHexString(MAGIC*2), Integer.toHexString(cbargs[1])); assertEquals("Wrong short return", Integer.toHexString(MAGIC*3), Integer.toHexString(value)); value = lib.callInt16Callback(cb, (short)-1, (short)-2); assertEquals("Wrong short return (hi bit)", (short)-3, value); } public void testCallNativeLongCallback() { final boolean[] called = {false}; final NativeLong[] cbargs = { null, null}; TestLibrary.NativeLongCallback cb = new TestLibrary.NativeLongCallback() { @Override public NativeLong callback(NativeLong arg, NativeLong arg2) { called[0] = true; cbargs[0] = arg; cbargs[1] = arg2; return new NativeLong(arg.intValue() + arg2.intValue()); } }; NativeLong value = lib.callNativeLongCallback(cb, new NativeLong(1), new NativeLong(2)); assertTrue("Callback not called", called[0]); assertEquals("Wrong first callback argument", new NativeLong(1), cbargs[0]); assertEquals("Wrong second callback argument", new NativeLong(2), cbargs[1]); assertEquals("Wrong boolean return", new NativeLong(3), value); } public void testCallNativeMappedCallback() { final boolean[] called = {false}; final Custom[] cbargs = { null, null}; TestLibrary.CustomCallback cb = new TestLibrary.CustomCallback() { @Override public Custom callback(Custom arg, Custom arg2) { called[0] = true; cbargs[0] = arg; cbargs[1] = arg2; return new Custom(arg.value + arg2.value); } }; int value = lib.callInt32Callback(cb, 1, 2); assertTrue("Callback not called", called[0]); assertEquals("Wrong first callback argument", new Custom(1), cbargs[0]); assertEquals("Wrong second callback argument", new Custom(2), cbargs[1]); assertEquals("Wrong NativeMapped return", 3, value); } public void testCallStringCallback() { final boolean[] called = {false}; final String[] cbargs = { null, null }; TestLibrary.StringCallback cb = new TestLibrary.StringCallback() { @Override public String callback(String arg, String arg2) { called[0] = true; cbargs[0] = arg; cbargs[1] = arg2; return arg + arg2; } }; Charset charset = Charset.forName(Native.getDefaultStringEncoding()); final String VALUE = "value" + charset.decode(charset.encode(UNICODE)); final String VALUE2 = getName() + charset.decode(charset.encode(UNICODE)); String value = lib.callStringCallback(cb, VALUE, VALUE2); assertTrue("Callback not called", called[0]); assertEquals("Wrong String callback argument 0", VALUE, cbargs[0]); assertEquals("Wrong String callback argument 1", VALUE2, cbargs[1]); assertEquals("Wrong String return", VALUE + VALUE2, value); } public void testStringCallbackMemoryReclamation() throws InterruptedException { TestLibrary.StringCallback cb = new TestLibrary.StringCallback() { @Override public String callback(String arg, String arg2) { return arg + arg2; } }; // A little internal groping Map<?, ?> m = CallbackReference.allocations; m.clear(); Charset charset = Charset.forName(Native.getDefaultStringEncoding()); String arg = getName() + "1" + charset.decode(charset.encode(UNICODE)); String arg2 = getName() + "2" + charset.decode(charset.encode(UNICODE)); String value = lib.callStringCallback(cb, arg, arg2); WeakReference<Object> ref = new WeakReference<Object>(value); arg = null; value = null; System.gc(); for (int i = 0; i < 100 && (ref.get() != null || m.size() > 0); ++i) { try { Thread.sleep(10); // Give the GC a chance to run System.gc(); } finally {} } assertNull("NativeString reference not GC'd", ref.get()); assertEquals("NativeString reference still held: " + m.values(), 0, m.size()); } public void testCallWideStringCallback() { final boolean[] called = {false}; final WString[] cbargs = { null, null }; TestLibrary.WideStringCallback cb = new TestLibrary.WideStringCallback() { @Override public WString callback(WString arg, WString arg2) { called[0] = true; cbargs[0] = arg; cbargs[1] = arg2; return new WString(arg.toString() + arg2.toString()); } }; final WString VALUE = new WString("magic" + UNICODE); final WString VALUE2 = new WString(getName() + UNICODE); WString value = lib.callWideStringCallback(cb, VALUE, VALUE2); assertTrue("Callback not called", called[0]); assertEquals("Wrong first callback argument", VALUE, cbargs[0]); assertEquals("Wrong second callback argument", VALUE2, cbargs[1]); assertEquals("Wrong wide string return", new WString(VALUE.toString() + VALUE2.toString()), value); } public void testCallStringArrayCallback() { final boolean[] called = {false}; final String[][] cbargs = { null }; TestLibrary.StringArrayCallback cb = new TestLibrary.StringArrayCallback() { @Override public String[] callback(String[] arg) { called[0] = true; cbargs[0] = arg; return arg; } }; Charset charset = Charset.forName(Native.getDefaultStringEncoding()); final String VALUE = "value" + charset.decode(charset.encode(UNICODE)); final String[] VALUE_ARRAY = { VALUE, null }; Pointer value = lib.callStringArrayCallback(cb, VALUE_ARRAY); assertTrue("Callback not called", called[0]); assertEquals("String[] array should not be modified", VALUE, VALUE_ARRAY[0]); assertEquals("Terminating null should be removed from incoming arg", VALUE_ARRAY.length-1, cbargs[0].length); assertEquals("String[] argument index 0 mismatch", VALUE_ARRAY[0], cbargs[0][0]); String[] result = value.getStringArray(0); assertEquals("Wrong String[] return", VALUE_ARRAY[0], result[0]); assertEquals("Terminating null should be removed from return value", VALUE_ARRAY.length-1, result.length); } public void testCallCallbackWithByReferenceArgument() { final boolean[] called = {false}; TestLibrary.CopyArgToByReference cb = new TestLibrary.CopyArgToByReference() { @Override public int callback(int arg, IntByReference result) { called[0] = true; result.setValue(arg); return result.getValue(); } }; final int VALUE = 0; IntByReference ref = new IntByReference(~VALUE); int value = lib.callCallbackWithByReferenceArgument(cb, VALUE, ref); assertEquals("Wrong value returned", VALUE, value); assertEquals("Wrong value in by reference memory", VALUE, ref.getValue()); } public void testCallCallbackWithStructByValue() throws Exception { final boolean[] called = { false }; final TestStructure.ByValue[] arg = { null }; final TestStructure.ByValue s = new TestStructure.ByValue(); TestStructure.TestCallback cb = new TestStructure.TestCallback() { @Override public TestStructure.ByValue callback(TestStructure.ByValue s) { // Copy the argument value for later comparison called[0] = true; return arg[0] = s; } }; s.c = (byte)0x11; s.s = 0x2222; s.i = 0x33333333; s.j = 0x4444444444444444L; s.inner.value = 5; TestStructure result = lib.callCallbackWithStructByValue(cb, s); assertTrue("Callback not called", called[0]); assertTrue("ByValue argument should own its own memory, instead was " + arg[0].getPointer(), arg[0].getPointer() instanceof Memory); assertTrue("ByValue result should own its own memory, instead was " + result.getPointer(), result.getPointer() instanceof Memory); if (!s.dataEquals(arg[0], true)) { System.out.println("Mismatch: " + s); System.out.println(" versus: " + arg[0]); } assertTrue("Wrong value for callback argument", s.dataEquals(arg[0], true)); if (!s.dataEquals(result, true)) { System.out.println("Mismatch: " + s); System.out.println(" versus: " + result); } assertTrue("Wrong value for callback result", s.dataEquals(result, true)); } public void testUnionByValueCallbackArgument() throws Exception{ TestLibrary.TestUnion arg = new TestLibrary.TestUnion(); arg.setType(String.class); Charset charset = Charset.forName(arg.getStringEncoding()); final String VALUE = getName() + charset.decode(charset.encode(UNICODE)); arg.f1 = VALUE; final boolean[] called = { false }; final TestLibrary.TestUnion[] cbvalue = { null }; TestLibrary.TestUnion result = lib.testUnionByValueCallbackArgument(new TestLibrary.UnionCallback() { @Override public TestLibrary.TestUnion invoke(TestLibrary.TestUnion v) { called[0] = true; v.setType(String.class); v.read(); cbvalue[0] = v; return v; } }, arg); assertTrue("Callback not called", called[0]); assertTrue("ByValue argument should have its own allocated memory, instead was " + cbvalue[0].getPointer(), cbvalue[0].getPointer() instanceof Memory); assertEquals("Wrong value for callback argument", VALUE, cbvalue[0].f1); assertEquals("Wrong value for callback result", VALUE, result.getTypedValue(String.class)); } public void testCallCallbackWithCallbackArgumentAndResult() { TestLibrary.CbCallback cb = new TestLibrary.CbCallback() { @Override public CbCallback callback(CbCallback arg) { return arg; } }; TestLibrary.CbCallback cb2 = lib.callCallbackWithCallback(cb); assertEquals("Callback reference should be reused", cb, cb2); } public void testDefaultCallbackExceptionHandler() { final RuntimeException ERROR = new RuntimeException(getName()); PrintStream ps = System.err; ByteArrayOutputStream s = new ByteArrayOutputStream(); System.setErr(new PrintStream(s)); try { TestLibrary.CbCallback cb = new TestLibrary.CbCallback() { @Override public CbCallback callback(CbCallback arg) { throw ERROR; } }; TestLibrary.CbCallback cb2 = lib.callCallbackWithCallback(cb); String output = s.toString(); assertTrue("Default handler not called", output.length() > 0); } finally { System.setErr(ps); } } // Most Callbacks are wrapped in DefaultCallbackProxy, which catches their // exceptions. public void testCallbackExceptionHandler() { final RuntimeException ERROR = new RuntimeException(getName()); final Throwable CAUGHT[] = { null }; final Callback CALLBACK[] = { null }; UncaughtExceptionHandler old = Native.getCallbackExceptionHandler(); UncaughtExceptionHandler handler = new UncaughtExceptionHandler() { @Override public void uncaughtException(Callback cb, Throwable e) { CALLBACK[0] = cb; CAUGHT[0] = e; } }; Native.setCallbackExceptionHandler(handler); try { TestLibrary.CbCallback cb = new TestLibrary.CbCallback() { @Override public CbCallback callback(CbCallback arg) { throw ERROR; } }; TestLibrary.CbCallback cb2 = lib.callCallbackWithCallback(cb); assertNotNull("Exception handler not called", CALLBACK[0]); assertEquals("Wrong callback argument to handler", cb, CALLBACK[0]); assertEquals("Wrong exception passed to handler", ERROR, CAUGHT[0]); } finally { Native.setCallbackExceptionHandler(old); } } // CallbackProxy is called directly from native. public void testCallbackExceptionHandlerWithCallbackProxy() throws Throwable { final RuntimeException ERROR = new RuntimeException(getName()); final Throwable CAUGHT[] = { null }; final Callback CALLBACK[] = { null }; UncaughtExceptionHandler old = Native.getCallbackExceptionHandler(); UncaughtExceptionHandler handler = new UncaughtExceptionHandler() { @Override public void uncaughtException(Callback cb, Throwable e) { CALLBACK[0] = cb; CAUGHT[0] = e; } }; Native.setCallbackExceptionHandler(handler); try { class TestProxy implements CallbackProxy, TestLibrary.CbCallback { @Override public CbCallback callback(CbCallback arg) { throw new Error("Should never be called"); } @Override public Object callback(Object[] args) { throw ERROR; } @Override public Class<?>[] getParameterTypes() { return new Class[] { CbCallback.class }; } @Override public Class<?> getReturnType() { return CbCallback.class; } }; TestLibrary.CbCallback cb = new TestProxy(); TestLibrary.CbCallback cb2 = lib.callCallbackWithCallback(cb); assertNotNull("Exception handler not called", CALLBACK[0]); assertEquals("Wrong callback argument to handler", cb, CALLBACK[0]); assertEquals("Wrong exception passed to handler", ERROR, CAUGHT[0]); } finally { Native.setCallbackExceptionHandler(old); } } public void testResetCallbackExceptionHandler() { Native.setCallbackExceptionHandler(null); assertNotNull("Should not be able to set callback EH null", Native.getCallbackExceptionHandler()); } public void testInvokeCallback() { TestLibrary.Int32CallbackX cb = lib.returnCallback(); assertNotNull("Callback should not be null", cb); assertEquals("Callback should be callable", 1, cb.callback(1)); TestLibrary.Int32CallbackX cb2 = new TestLibrary.Int32CallbackX() { @Override public int callback(int arg) { return 0; } }; assertSame("Java callback should be looked up", cb2, lib.returnCallbackArgument(cb2)); assertSame("Existing native function wrapper should be reused", cb, lib.returnCallbackArgument(cb)); } public void testCallCallbackInStructure() { final boolean[] flag = {false}; final TestLibrary.CbStruct s = new TestLibrary.CbStruct(); s.cb = new Callback() { public void callback() { flag[0] = true; } }; lib.callCallbackInStruct(s); assertTrue("Callback not invoked", flag[0]); } public void testCustomCallbackMethodName() { final boolean[] called = {false}; TestLibrary.VoidCallbackCustom cb = new TestLibrary.VoidCallbackCustom() { @Override public void customMethodName() { called[0] = true; } @Override public String toString() { return "Some debug output"; } }; lib.callVoidCallback(cb); assertTrue("Callback with custom method name not called", called[0]); } public void testDisallowDetachFromJVMThread() { final boolean[] called = {false}; final boolean[] exceptionThrown = {true}; TestLibrary.VoidCallback cb = new TestLibrary.VoidCallback() { @Override public void callback() { called[0] = true; try { Native.detach(true); } catch(IllegalStateException e) { } } }; lib.callVoidCallback(cb); assertTrue("Callback not called", called[0]); assertTrue("Native.detach(true) should throw IllegalStateException when called from JVM thread", exceptionThrown[0]); } public void testCustomCallbackVariedInheritance() { final boolean[] called = {false}; TestLibrary.VoidCallbackCustom cb = new TestLibrary.VoidCallbackCustomDerived(); lib.callVoidCallback(cb); } protected static class CallbackTypeMapper extends DefaultTypeMapper { public int fromNativeConversions = 0; public int toNativeConversions = 0; public void clear() { fromNativeConversions = 0; toNativeConversions = 0; } { // Convert java doubles into native integers and back TypeConverter converter = new TypeConverter() { @Override public Object fromNative(Object value, FromNativeContext context) { ++fromNativeConversions; return Double.valueOf(((Integer)value).intValue()); } @Override public Class<?> nativeType() { return Integer.class; } @Override public Object toNative(Object value, ToNativeContext ctx) { ++toNativeConversions; return Integer.valueOf(((Double)value).intValue()); } }; addTypeConverter(double.class, converter); converter = new TypeConverter() { @Override public Object fromNative(Object value, FromNativeContext context) { ++fromNativeConversions; return Float.valueOf(((Long)value).intValue()); } @Override public Class<?> nativeType() { return Long.class; } @Override public Object toNative(Object value, ToNativeContext ctx) { ++toNativeConversions; return Long.valueOf(((Float)value).longValue()); } }; addTypeConverter(float.class, converter); converter = new TypeConverter() { @Override public Object fromNative(Object value, FromNativeContext context) { ++fromNativeConversions; if (value == null) { return null; } if (value instanceof Pointer) { return ((Pointer)value).getWideString(0); } return value.toString(); } @Override public Class<?> nativeType() { return WString.class; } @Override public Object toNative(Object value, ToNativeContext ctx) { ++toNativeConversions; return new WString(value.toString()); } }; addTypeConverter(String.class, converter); } } public static interface CallbackTestLibrary extends Library { final CallbackTypeMapper _MAPPER = new CallbackTypeMapper(); final Map<String, ?> _OPTIONS = Collections.singletonMap(Library.OPTION_TYPE_MAPPER, _MAPPER); interface DoubleCallback extends Callback { double callback(double arg, double arg2); } double callInt32Callback(DoubleCallback c, double arg, double arg2); interface FloatCallback extends Callback { float callback(float arg, float arg2); } float callInt64Callback(FloatCallback c, float arg, float arg2); interface WStringCallback extends Callback { String callback(String arg, String arg2); } String callWideStringCallback(WStringCallback c, String arg, String arg2); } protected CallbackTestLibrary loadCallbackTestLibrary() { return Native.loadLibrary("testlib", CallbackTestLibrary.class, CallbackTestLibrary._OPTIONS); } /** This test is here instead of NativeTest in order to facilitate running the exact same test on a direct-mapped library without the tests interfering with one another due to persistent/cached state in library loading. */ public void testCallbackUsesTypeMapper() throws Exception { CallbackTestLibrary lib = loadCallbackTestLibrary(); CallbackTestLibrary._MAPPER.clear(); final double[] ARGS = new double[2]; CallbackTestLibrary.DoubleCallback cb = new CallbackTestLibrary.DoubleCallback() { @Override public double callback(double arg, double arg2) { ARGS[0] = arg; ARGS[1] = arg2; return arg + arg2; } }; assertEquals("Wrong type mapper for callback class", CallbackTestLibrary._MAPPER, Native.getTypeMapper(CallbackTestLibrary.DoubleCallback.class)); assertEquals("Wrong type mapper for callback object", CallbackTestLibrary._MAPPER, Native.getTypeMapper(cb.getClass())); double result = lib.callInt32Callback(cb, -1, -2); assertEquals("Wrong first callback argument", -1, ARGS[0], 0); assertEquals("Wrong second callback argument", -2, ARGS[1], 0); assertEquals("Incorrect result of callback invocation", -3, result, 0); // Once per argument, then again for return value (convert native int->Java double) assertEquals("Type mapper not called for arguments", 3, CallbackTestLibrary._MAPPER.fromNativeConversions); // Once per argument, then again for return value (convert Java double->native int) assertEquals("Type mapper not called for result", 3, CallbackTestLibrary._MAPPER.toNativeConversions); } public void testTypeMapperWithWideStrings() throws Exception { CallbackTestLibrary lib = loadCallbackTestLibrary(); CallbackTestLibrary._MAPPER.clear(); final String[] ARGS = new String[2]; CallbackTestLibrary.WStringCallback cb = new CallbackTestLibrary.WStringCallback() { @Override public String callback(String arg, String arg2) { ARGS[0] = arg; ARGS[1] = arg2; return arg + arg2; } }; assertEquals("Wrong type mapper for callback class", CallbackTestLibrary._MAPPER, Native.getTypeMapper(CallbackTestLibrary.WStringCallback.class)); assertEquals("Wrong type mapper for callback object", CallbackTestLibrary._MAPPER, Native.getTypeMapper(cb.getClass())); final String[] EXPECTED = { "magic" + UNICODE, getName() + UNICODE }; String result = lib.callWideStringCallback(cb, EXPECTED[0], EXPECTED[1]); assertEquals("Wrong first callback argument", EXPECTED[0], ARGS[0]); assertEquals("Wrong second callback argument", EXPECTED[1], ARGS[1]); assertEquals("Incorrect result of callback invocation", EXPECTED[0] + EXPECTED[1], result); // Once per argument, then again for return value (convert const wchar_t*->Java String) assertEquals("Type mapper not called for arguments", 3, CallbackTestLibrary._MAPPER.fromNativeConversions); // Once per argument, then again for return value (convert Java String->const wchar_t*) assertEquals("Type mapper not called for result", 3, CallbackTestLibrary._MAPPER.toNativeConversions); } public void testCallbackUsesTypeMapperWithDifferentReturnTypeSize() throws Exception { CallbackTestLibrary lib = loadCallbackTestLibrary(); final float[] ARGS = new float[2]; CallbackTestLibrary.FloatCallback cb = new CallbackTestLibrary.FloatCallback() { @Override public float callback(float arg, float arg2) { ARGS[0] = arg; ARGS[1] = arg2; return arg + arg2; } }; assertEquals("Wrong type mapper for callback class", CallbackTestLibrary._MAPPER, Native.getTypeMapper(CallbackTestLibrary.FloatCallback.class)); assertEquals("Wrong type mapper for callback object", CallbackTestLibrary._MAPPER, Native.getTypeMapper(cb.getClass())); float result = lib.callInt64Callback(cb, -1, -2); assertEquals("Wrong first callback argument", -1, ARGS[0], 0); assertEquals("Wrong second callback argument", -2, ARGS[1], 0); assertEquals("Incorrect result of callback invocation", -3, result, 0); } protected void callThreadedCallback(TestLibrary.VoidCallback cb, CallbackThreadInitializer cti, int repeat, int sleepms, int[] called) throws Exception { callThreadedCallback(cb, cti, repeat, sleepms, called, repeat); } protected void callThreadedCallback(TestLibrary.VoidCallback cb, CallbackThreadInitializer cti, int repeat, int sleepms, int[] called, int returnAfter) throws Exception { if (cti != null) { Native.setCallbackThreadInitializer(cb, cti); } lib.callVoidCallbackThreaded(cb, repeat, sleepms, getName()); long start = System.currentTimeMillis(); while (called[0] < returnAfter) { Thread.sleep(10); if (System.currentTimeMillis() - start > THREAD_TIMEOUT) { fail("Timed out waiting for callback, invoked " + called[0] + " times so far"); } } } public void testCallbackThreadDefaults() throws Exception { final int[] called = {0}; final boolean[] daemon = {false}; final String[] name = { null }; final ThreadGroup[] group = { null }; final Thread[] t = { null }; ThreadGroup testGroup = new ThreadGroup(getName() + UNICODE); TestLibrary.VoidCallback cb = new TestLibrary.VoidCallback() { @Override public void callback() { Thread thread = Thread.currentThread(); daemon[0] = thread.isDaemon(); name[0] = thread.getName(); group[0] = thread.getThreadGroup(); t[0] = thread; ++called[0]; } }; callThreadedCallback(cb, null, 1, 100, called); assertFalse("Callback thread default should not be attached as daemon", daemon[0]); // thread name and group are not defined } public void testCustomizeCallbackThread() throws Exception { final int[] called = {0}; final boolean[] daemon = {false}; final String[] name = { null }; final ThreadGroup[] group = { null }; final Thread[] t = { null }; // Ensure unicode is properly handled final String tname = "NAME: " + getName() + UNICODE; final boolean[] alive = {false}; ThreadGroup testGroup = new ThreadGroup("Thread group for " + getName()); CallbackThreadInitializer init = new CallbackThreadInitializer(true, false, tname, testGroup); TestLibrary.VoidCallback cb = new TestLibrary.VoidCallback() { @Override public void callback() { Thread thread = Thread.currentThread(); daemon[0] = thread.isDaemon(); name[0] = thread.getName(); group[0] = thread.getThreadGroup(); t[0] = thread; if (thread.isAlive()) { // NOTE: older phoneME incorrectly reports thread "alive" status alive[0] = true; } ++called[0]; if (THREAD_DETACH_BUG && called[0] == 2) { Native.detach(true); } } }; callThreadedCallback(cb, init, 2, 2000, called, 1); assertTrue("Callback thread not attached as daemon", daemon[0]); assertEquals("Callback thread name not applied", tname, name[0]); assertEquals("Callback thread group not applied", testGroup, group[0]); // NOTE: older phoneME incorrectly reports thread "alive" status if (!alive[0]) { throw new Error("VM incorrectly reports Thread.isAlive() == false within callback"); } assertTrue("Thread should still be alive", t[0].isAlive()); long start = System.currentTimeMillis(); while (called[0] < 2) { Thread.sleep(10); if (System.currentTimeMillis() - start > THREAD_TIMEOUT) { fail("Timed out waiting for second callback invocation, which indicates detach"); } } waitFor(t[0]); } // Detach preference is indicated by the initializer. Thread is attached // as daemon to avoid VM having to wait for it. public void testCallbackThreadPersistence() throws Exception { final int[] called = {0}; final Set<Thread> threads = new HashSet<Thread>(); final int COUNT = 5; CallbackThreadInitializer init = new CallbackThreadInitializer(true, false) { @Override public String getName(Callback cb) { return "Test thread (native) for " + CallbacksTest.this.getName() + " (call count: " + called[0] + ")"; } }; TestLibrary.VoidCallback cb = new TestLibrary.VoidCallback() { @Override public void callback() { threads.add(Thread.currentThread()); ++called[0]; if (THREAD_DETACH_BUG && called[0] == COUNT) { Native.detach(true); } } }; callThreadedCallback(cb, init, COUNT, 100, called); assertEquals("Multiple callbacks on a given native thread should use the same Thread mapping: " + threads, 1, threads.size()); waitFor(threads.iterator().next()); } // Thread object is never GC'd on linux-amd64 and darwin-amd64 (w/openjdk7) public void testCleanupUndetachedThreadOnThreadExit() throws Exception { final Set<Reference<Thread>> threads = new HashSet<Reference<Thread>>(); final int[] called = { 0 }; TestLibrary.VoidCallback cb = new TestLibrary.VoidCallback() { @Override public void callback() { threads.add(new WeakReference<Thread>(Thread.currentThread())); if (++called[0] == 1) { Thread.currentThread().setName(getName() + " (Thread to be cleaned up)"); } Native.detach(false); } }; // Always attach as daemon to ensure tests will exit CallbackThreadInitializer asDaemon = new CallbackThreadInitializer(true) { @Override public String getName(Callback cb) { return "Test thread (native) for " + CallbacksTest.this.getName(); } }; callThreadedCallback(cb, asDaemon, 2, 100, called); // Wait for it to start up long start = System.currentTimeMillis(); while (threads.size() == 0 && called[0] == 0) { Thread.sleep(10L); if (System.currentTimeMillis() - start > THREAD_TIMEOUT) { fail("Timed out waiting for thread to detach and terminate"); } } start = System.currentTimeMillis(); Reference<Thread> ref = threads.iterator().next(); while (ref.get() != null) { System.gc(); Thread.sleep(100); Thread[] remaining = new Thread[Thread.activeCount()]; Thread.enumerate(remaining); if (System.currentTimeMillis() - start > THREAD_TIMEOUT) { Thread t = ref.get(); Pointer terminationFlag = Native.getTerminationFlag(t); assertNotNull("Native thread termination flag is missing", terminationFlag); if (terminationFlag.getInt(0) == 0) { fail("Timed out waiting for native attached thread to be GC'd: " + t + " alive: " + t.isAlive() + " daemon: " + t.isDaemon() + "\n" + Arrays.asList(remaining)); } System.err.println("Warning: JVM did not GC Thread mapping after native thread terminated"); break; } } } // Callback indicates detach preference (instead of // CallbackThreadInitializer); thread is non-daemon (default), // but callback explicitly detaches it on final invocation. public void testCallbackIndicatedThreadDetach() throws Exception { final int[] called = {0}; final Set<Thread> threads = new HashSet<Thread>(); final int COUNT = 5; TestLibrary.VoidCallback cb = new TestLibrary.VoidCallback() { @Override public void callback() { threads.add(Thread.currentThread()); // detach on final invocation int count = called[0] + 1; if (count == 1) { Thread.currentThread().setName("Native thread for " + getName()); Native.detach(false); } else if (count == COUNT) { Native.detach(true); } called[0] = count; } }; callThreadedCallback(cb, null, COUNT, 100, called); assertEquals("Multiple callbacks in the same native thread should use the same Thread mapping: " + threads, 1, threads.size()); waitFor(threads.iterator().next()); } public void testDLLCallback() throws Exception { if (!Platform.HAS_DLL_CALLBACKS) { return; } final boolean[] called = { false }; class TestCallback implements TestLibrary.VoidCallback, com.sun.jna.win32.DLLCallback { @Override public void callback() { called[0] = true; } } TestCallback cb = new TestCallback(); lib.callVoidCallback(cb); assertTrue("Callback not called", called[0]); // Check module information Pointer fp = CallbackReference.getFunctionPointer(cb); NativeLibrary kernel32 = NativeLibrary.getInstance("kernel32", W32APIOptions.DEFAULT_OPTIONS); Function f = kernel32.getFunction("GetModuleHandleExW"); final int GET_MODULE_HANDLE_FROM_ADDRESS = 0x4; PointerByReference pref = new PointerByReference(); int result = f.invokeInt(new Object[] { Integer.valueOf(GET_MODULE_HANDLE_FROM_ADDRESS), fp, pref }); assertTrue("GetModuleHandleEx(fptr) failed: " + Native.getLastError(), result != 0); f = kernel32.getFunction("GetModuleFileNameW"); char[] buf = new char[1024]; result = f.invokeInt(new Object[] { pref.getValue(), buf, buf.length }); assertTrue("GetModuleFileName(fptr) failed: " + Native.getLastError(), result != 0); f = kernel32.getFunction("GetModuleHandleW"); Pointer handle = f.invokePointer(new Object[] { Native.jnidispatchPath != null ? Native.jnidispatchPath : "jnidispatch" }); assertNotNull("GetModuleHandle(\"jnidispatch\") failed: " + Native.getLastError(), handle); assertEquals("Wrong module HANDLE for DLL function pointer", handle, pref.getValue()); // Check slot re-use Map<Callback, CallbackReference> refs = new WeakHashMap<Callback, CallbackReference>(callbackCache()); assertTrue("Callback not cached", refs.containsKey(cb)); CallbackReference ref = refs.get(cb); refs = callbackCache(); Pointer cbstruct = ref.cbstruct; Pointer first_fptr = cbstruct.getPointer(0); cb = null; System.gc(); for (int i = 0; i < 100 && (ref.get() != null || refs.containsValue(ref)); ++i) { Thread.sleep(10); // Give the GC a chance to run System.gc(); } assertNull("Callback not GC'd", ref.get()); assertFalse("Callback still in map", refs.containsValue(ref)); ref = null; System.gc(); for (int i = 0; i < 100 && (cbstruct.peer != 0 || refs.size() > 0); ++i) { // Flush weak hash map refs.size(); Thread.sleep(10); // Give the GC a chance to run System.gc(); } assertEquals("Callback trampoline not freed", 0, cbstruct.peer); // Next allocation should be at same place called[0] = false; cb = new TestCallback(); lib.callVoidCallback(cb); ref = refs.get(cb); cbstruct = ref.cbstruct; assertTrue("Callback not called", called[0]); assertEquals("Same (in-DLL) address should be re-used for DLL callbacks after callback is GCd", first_fptr, cbstruct.getPointer(0)); } public void testThrowOutOfMemoryWhenDLLCallbacksExhausted() throws Exception { if (!Platform.HAS_DLL_CALLBACKS) { return; } final boolean[] called = { false }; class TestCallback implements TestLibrary.VoidCallback, com.sun.jna.win32.DLLCallback { @Override public void callback() { called[0] = true; } } // Exceeding allocations should result in OOM error try { for (int i=0;i <= TestCallback.DLL_FPTRS;i++) { lib.callVoidCallback(new TestCallback()); } fail("Expected out of memory error when all DLL callbacks used"); } catch(OutOfMemoryError e) { } } public interface TaggedCallingConventionTestLibrary extends Library, AltCallingConvention { interface TestCallbackTagged extends Callback, AltCallingConvention { void invoke(); } } public void testCallingConventionFromInterface() { TaggedCallingConventionTestLibrary lib = Native.loadLibrary("testlib", TaggedCallingConventionTestLibrary.class); TaggedCallingConventionTestLibrary.TestCallbackTagged cb = new TaggedCallingConventionTestLibrary.TestCallbackTagged() { @Override public void invoke() { } }; try { Pointer p = CallbackReference.getFunctionPointer(cb); CallbackReference ref = CallbackReference.callbackMap.get(cb); assertNotNull("CallbackReference not found", ref); assertEquals("Tag-based calling convention not applied", Function.ALT_CONVENTION, ref.callingConvention); } catch (IllegalArgumentException e) { // Alt convention not supported } } public interface OptionCallingConventionTestLibrary extends Library { interface TestCallback extends Callback { void invoke(); } } public void testCallingConventionFromOptions() { OptionCallingConventionTestLibrary lib = Native.loadLibrary("testlib", OptionCallingConventionTestLibrary.class, Collections.singletonMap(Library.OPTION_CALLING_CONVENTION, Function.ALT_CONVENTION)); assertNotNull("Library not loaded", lib); OptionCallingConventionTestLibrary.TestCallback cb = new OptionCallingConventionTestLibrary.TestCallback() { @Override public void invoke() { } }; try { Pointer p = CallbackReference.getFunctionPointer(cb); assertNotNull("No function pointer", p); CallbackReference ref = CallbackReference.callbackMap.get(cb); assertNotNull("CallbackReference not found", ref); assertEquals("Option-based calling convention not applied", Function.ALT_CONVENTION, ref.callingConvention); } catch(IllegalArgumentException e) { // Alt convention not supported } } public static void main(java.lang.String[] argList) { junit.textui.TestRunner.run(CallbacksTest.class); } }