diff --git a/src/main/c/.gitignore b/src/main/c/.gitignore index a7a2d38..7cf4d5f 100644 --- a/src/main/c/.gitignore +++ b/src/main/c/.gitignore @@ -26,3 +26,4 @@ m4 missing ar-lib compile +.cproject diff --git a/src/main/c/src/LibUsb.c b/src/main/c/src/LibUsb.c index bd69f8b..aa5e913 100644 --- a/src/main/c/src/LibUsb.c +++ b/src/main/c/src/LibUsb.c @@ -1272,14 +1272,14 @@ static void LIBUSB_CALL triggerPollfdAdded(int fd, short events, void *user_data { THREAD_BEGIN(env) - jclass fdcls = (*env)->FindClass(env, "java/io/FileDescriptor"); - jmethodID constructor = (*env)->GetMethodID(env, fdcls, "", "(I)V"); - jobject object = (*env)->NewObject(env, fdcls, constructor, fd); + jclass fdCls = (*env)->FindClass(env, "java/io/FileDescriptor"); + jmethodID fdConstructor = (*env)->GetMethodID(env, fdCls, "", "(I)V"); + jobject fdObject = (*env)->NewObject(env, fdCls, fdConstructor, fd); jclass cls = (*env)->FindClass(env, PACKAGE_DIR"/LibUsb"); jmethodID method = (*env)->GetStaticMethodID(env, cls, "triggerPollfdAdded", "(Ljava/io/FileDescriptor;IJ)V"); - (*env)->CallStaticVoidMethod(env, cls, method, object, (jint) events, + (*env)->CallStaticVoidMethod(env, cls, method, fdObject, (jint) events, (jlong) (intptr_t) user_data); THREAD_END @@ -1289,38 +1289,37 @@ static void LIBUSB_CALL triggerPollfdRemoved(int fd, void *user_data) { THREAD_BEGIN(env) - jclass fdcls = (*env)->FindClass(env, "java/io/FileDescriptor"); - jmethodID constructor = (*env)->GetMethodID(env, fdcls, "", "(I)V"); - jobject object = (*env)->NewObject(env, fdcls, constructor, fd); + jclass fdCls = (*env)->FindClass(env, "java/io/FileDescriptor"); + jmethodID fdConstructor = (*env)->GetMethodID(env, fdCls, "", "(I)V"); + jobject fdObject = (*env)->NewObject(env, fdCls, fdConstructor, fd); jclass cls = (*env)->FindClass(env, PACKAGE_DIR"/LibUsb"); jmethodID method = (*env)->GetStaticMethodID(env, cls, "triggerPollfdRemoved", "(Ljava/io/FileDescriptor;J)V"); - (*env)->CallStaticVoidMethod(env, cls, method, object, + (*env)->CallStaticVoidMethod(env, cls, method, fdObject, (jlong) (intptr_t) user_data); THREAD_END } /** - * void setPollfdNotifiers(Context, long) + * void setPollfdNotifiersNative(Context, long) */ -JNIEXPORT void JNICALL METHOD_NAME(LibUsb, setPollfdNotifiers) +JNIEXPORT void JNICALL METHOD_NAME(LibUsb, setPollfdNotifiersNative) ( JNIEnv *env, jclass class, jobject context, jlong context_id ) { libusb_context *ctx = unwrapContext(env, context); if (!ctx && context) return; - libusb_set_pollfd_notifiers(ctx, &triggerPollfdAdded, &triggerPollfdRemoved, (void *) (intptr_t) context_id); } /** - * void unsetPollfdNotifiers(Context) + * void unsetPollfdNotifiersNative(Context) */ -JNIEXPORT void JNICALL METHOD_NAME(LibUsb, unsetPollfdNotifiers) +JNIEXPORT void JNICALL METHOD_NAME(LibUsb, unsetPollfdNotifiersNative) ( JNIEnv *env, jclass class, jobject context ) @@ -1409,62 +1408,55 @@ JNIEXPORT jint JNICALL METHOD_NAME(LibUsb, cancelTransfer) return libusb_cancel_transfer(transfer); } -struct hotplug_data -{ - jobject callbackObject; - jmethodID callbackObjectMethod; - jobject callbackUserDataObject; -}; - -static int LIBUSB_CALL hotplugCallback(libusb_context *ctx, +static int LIBUSB_CALL hotplugCallback(libusb_context *context, libusb_device *device, libusb_hotplug_event event, void *user_data) { THREAD_BEGIN(env) - // TODO: check for return value and free memory appropriately. + jobject ctx = wrapContext(env, context); + jobject dev = wrapDevice(env, device); + + jclass cls = (*env)->FindClass(env, PACKAGE_DIR"/LibUsb"); + jmethodID method = (*env)->GetStaticMethodID(env, cls, + "hotplugCallback", "(L"PACKAGE_DIR"/Context;L"PACKAGE_DIR"/Device;IJ)I"); + int result = (*env)->CallStaticIntMethod(env, cls, method, ctx, dev, + (jint) event, (jlong) (intptr_t) user_data); THREAD_END + + return result; } /** - * int hotplugRegisterCallback(Context, int, int, short, short, byte, - * HotplugCallback, Object, HotplugCallbackHandle) + * int hotplugRegisterCallbackNative(Context, int, int, short, short, byte, + * HotplugCallbackHandle, long) */ -JNIEXPORT jint JNICALL METHOD_NAME(LibUsb, hotplugRegisterCallback) +JNIEXPORT jint JNICALL METHOD_NAME(LibUsb, hotplugRegisterCallbackNative) ( JNIEnv *env, jclass class, jobject context, jint events, jint flags, jshort vendorId, jshort productId, jbyte deviceClass, - jobject callback, jobject userData, jobject callbackHandle + jobject callbackHandle, jlong hotplugId ) { libusb_context *ctx = unwrapContext(env, context); if (!ctx && context) return 0; - NOT_NULL(env, callback, return 0); - if (callbackHandle) { + + if (callbackHandle != NULL) + { // If callbackHandle is set, the Java object must be fresh/empty. NOT_SET(env, callbackHandle, "hotplugCallbackHandleValue", return 0); } - // Allocate memory to hold the references to the callback on the C side. - struct hotplug_data *hotplugData = calloc(1, sizeof(*hotplugData)); - if (!hotplugData) - { - return LIBUSB_ERROR_NO_MEM; - } - - // Setup the needed global references to the callback objects. - hotplugData->callbackObject = NULL; - hotplugData->callbackObjectMethod = NULL; - hotplugData->callbackUserDataObject = userData; - // Register the callback. - libusb_hotplug_callback_handle handle = 0; + libusb_hotplug_callback_handle handle; int result = libusb_hotplug_register_callback(ctx, events, flags, - vendorId, productId, deviceClass, &hotplugCallback, hotplugData, &handle); + vendorId, productId, deviceClass, &hotplugCallback, + (void *) (intptr_t) hotplugId, &handle); // If callbackHandle is set and registering was successful, we set the handle // to the value we've gotten from libusb. - if (callbackHandle && (result == LIBUSB_SUCCESS)) { + if ((callbackHandle != NULL) && (result == LIBUSB_SUCCESS)) + { setHotplugCallbackHandle(env, handle, callbackHandle); } @@ -1472,22 +1464,25 @@ JNIEXPORT jint JNICALL METHOD_NAME(LibUsb, hotplugRegisterCallback) } /* - * void hotplugDeregisterCallback(Context, HotplugCallbackHandle) + * long hotplugDeregisterCallbackNative(Context, HotplugCallbackHandle) */ -JNIEXPORT void JNICALL METHOD_NAME(LibUsb, hotplugDeregisterCallback) +JNIEXPORT jlong JNICALL METHOD_NAME(LibUsb, hotplugDeregisterCallbackNative) ( JNIEnv *env, jclass class, jobject context, jobject callbackHandle ) { libusb_context *ctx = unwrapContext(env, context); - if (!ctx && context) return; - NOT_NULL(env, callbackHandle, return); + if (!ctx && context) return 0; + NOT_NULL(env, callbackHandle, return 0); libusb_hotplug_callback_handle handle = unwrapHotplugCallbackHandle(env, callbackHandle); - if (!handle) return; - + if (!handle) return 0; + // Deregister the callback. + libusb_hotplug_deregister_callback(ctx, handle); resetHotplugCallbackHandle(env, callbackHandle); + + return handle; } diff --git a/src/main/java/de/ailis/usb4java/libusb/LibUsb.java b/src/main/java/de/ailis/usb4java/libusb/LibUsb.java index de0669a..bdef85e 100644 --- a/src/main/java/de/ailis/usb4java/libusb/LibUsb.java +++ b/src/main/java/de/ailis/usb4java/libusb/LibUsb.java @@ -617,7 +617,15 @@ public final class LibUsb public static final byte HOTPLUG_MATCH_ANY = -1; /** - * pollfd listeners (to support different listeners for different contexts). + * Hotplug callbacks (to correctly manage calls and additional data). + */ + private static long globalHotplugId = 1; + + private static final ConcurrentMap> hotplugCallbacks = + new ConcurrentHashMap>(); + + /** + * Pollfd listeners (to support different listeners for different contexts). */ private static final ConcurrentMap> pollfdListeners = new ConcurrentHashMap>(); @@ -2228,7 +2236,7 @@ public final class LibUsb * User data to be passed back to callbacks (useful for passing * context information). */ - public static void setPollfdNotifiers(final Context context, + public static synchronized void setPollfdNotifiers(final Context context, final PollfdListener listener, final Object userData) { long contextId; @@ -2244,13 +2252,13 @@ public final class LibUsb if (listener == null) { - unsetPollfdNotifiers(context); + unsetPollfdNotifiersNative(context); pollfdListeners.remove(contextId); } else { - setPollfdNotifiers(context, contextId); + setPollfdNotifiersNative(context, contextId); pollfdListeners.put(contextId, new ImmutablePair(listener, userData)); @@ -2309,7 +2317,7 @@ public final class LibUsb * @param contextId * A unique identifier for the given context. */ - static native void setPollfdNotifiers(final Context context, + static native void setPollfdNotifiersNative(final Context context, final long contextId); /** @@ -2319,7 +2327,7 @@ public final class LibUsb * @param context * The context to operate on, or NULL for the default context */ - static native void unsetPollfdNotifiers(final Context context); + static native void unsetPollfdNotifiersNative(final Context context); /** * Allocate a libusb transfer without support for isochronous transfers. @@ -2545,14 +2553,39 @@ public final class LibUsb isoDescriptors[packet].length()); } + static int hotplugCallback(final Context context, final Device device, + final int event, final long hotplugId) + { + final ImmutablePair callback = hotplugCallbacks + .get(hotplugId); + + int result = 0; + + if (callback != null) + { + result = callback.left.processEvent(context, device, event, + callback.right); + } + + // If callback indicates it is finished, it will get deregistered + // automatically. As such, we have to remove it from the Java + // map, like when deregistering manually. + if (result == 1) + { + hotplugCallbacks.remove(hotplugId); + } + + return result; + } + /** * Register a hotplug callback function. * * Register a callback with the {@link Context}. The callback will fire * when a matching event occurs on a matching device. The callback is * armed until either it is deregistered with - * {@link #hotplugDeregisterCallback(Context, HotplugCallbackHandle)} - * or the supplied callback returns 1 to indicate it is finished processing + * {@link #hotplugDeregisterCallback(Context, HotplugCallbackHandle)} or the + * supplied callback returns 1 to indicate it is finished processing * events. * * @param context @@ -2577,11 +2610,37 @@ public final class LibUsb * * @return LIBUSB_SUCCESS on success LIBUSB_ERROR code on failure */ - public static native int hotplugRegisterCallback(final Context context, + public static synchronized int hotplugRegisterCallback( + final Context context, final int events, final int flags, + final short vendorId, final short productId, final byte deviceClass, + final HotplugCallback callback, final Object userData, + final HotplugCallbackHandle callbackHandle) + { + if (callback == null) + { + throw new IllegalArgumentException("callback must not be null"); + } + + final int result = hotplugRegisterCallbackNative(context, events, + flags, vendorId, productId, deviceClass, callbackHandle, + globalHotplugId); + + if (result == LibUsb.SUCCESS) + { + hotplugCallbacks.put(globalHotplugId, + new ImmutablePair(callback, userData)); + + // Increment globalHotplugId by one, like the libusb handle. + globalHotplugId++; + } + + return result; + } + + static native int hotplugRegisterCallbackNative(final Context context, final int events, final int flags, final short vendorId, final short productId, final byte deviceClass, - final HotplugCallback callback, final Object userData, - final HotplugCallbackHandle callbackHandle); + final HotplugCallbackHandle callbackHandle, final long hotplugId); /** * Deregisters a hotplug callback. @@ -2594,6 +2653,24 @@ public final class LibUsb * @param handle * the handle of the callback to deregister */ - public static native void hotplugDeregisterCallback(final Context context, + public static void hotplugDeregisterCallback(final Context context, + final HotplugCallbackHandle callbackHandle) + { + final long handle = hotplugDeregisterCallbackNative(context, + callbackHandle); + + // When a handle is assigned by a register call, its value is the same + // as the one of globalHotplugId at that moment, which is what's used + // to identify data in the hotplugCallbacks map. + // This is because globalHotplugId pretty much mirrors the behavior of + // the handle: integer starting at 1, incremented each time by one. + // Problems could arise from concurrency, but are completely avoided by + // fully serializing register calls. + // As such, we can use the handle value from callbackHandle to + // correctly remove the data from the hotplugCallbacks map. + hotplugCallbacks.remove(handle); + } + + static native long hotplugDeregisterCallbackNative(final Context context, final HotplugCallbackHandle callbackHandle); } diff --git a/src/test/java/de/ailis/usb4java/libusb/LibUSBTest.java b/src/test/java/de/ailis/usb4java/libusb/LibUSBTest.java index 286a1f6..64e39a7 100644 --- a/src/test/java/de/ailis/usb4java/libusb/LibUSBTest.java +++ b/src/test/java/de/ailis/usb4java/libusb/LibUSBTest.java @@ -1160,27 +1160,27 @@ public class LibUSBTest } /** - * Tests {@link LibUsb#setPollfdNotifiers(Context, long)} with uninitialized - * USB context. + * Tests {@link LibUsb#setPollfdNotifiersNative(Context, long)} + * with uninitialized USB context. */ @Test(expected = IllegalStateException.class) public void testSetPollfdNotifiersWithUninitializedContext() { assumeUsbTestsEnabled(); final Context context = new Context(); - LibUsb.setPollfdNotifiers(context, context.getPointer()); + LibUsb.setPollfdNotifiersNative(context, context.getPointer()); } /** - * Tests {@link LibUsb#unsetPollfdNotifiers(Context)} with uninitialized USB - * context. + * Tests {@link LibUsb#unsetPollfdNotifiersNative(Context)} with + * uninitialized USB context. */ @Test(expected = IllegalStateException.class) public void testUnsetPollfdNotifiersWithUninitializedContext() { assumeUsbTestsEnabled(); final Context context = new Context(); - LibUsb.unsetPollfdNotifiers(context); + LibUsb.unsetPollfdNotifiersNative(context); } /**