有没有办法使用 JNI 动态添加本机钩子?我的意思是,我想重写类(或新类)中的一些方法,以便重写调用我的本机代码,而无需为此编写任何 Java 代码。
如果您指的是
native
方法,也许注册可能是您的答案。
比如我这里注册的是native方法
JNIEXPORT jint JNICALL addOne(JNIEnv *env, jclass obj, jint a) {
return a + 1;
}
...
...
static JNINativeMethod methods[] = {
{"addOne", "(I)I", (void *)&addOne}
};
...
...
(*env)->RegisterNatives(env, cls_Main,
methods, sizeof(methods)/sizeof(methods[0]));
将被分配到班级
public class Main {
public static native int addOne(int a);
...
...
}
完整示例:https://github.com/mkowsiak/jnicookbook/tree/master/recipes/recipeNo052
这在典型的 JVM 上是可能的,但需要在运行时从预编译的字节码定义代理类。不幸的是,下面的方法在Android上不起作用,因为Dalvik使用自己的字节码格式并且
DefineClass
没有实现。在这种情况下,您必须使用一些用 Java 编写并编译为 DEX 的最少支持代码来构建包。
举个例子,假设您想创建一个 AWT
Frame
,但在本机代码中处理窗口事件。这需要实现一个抽象的 WindowListener
接口并使用 Frame
将其传递给 addWindowListener
。
首先,实现一个代理类,将事件转发到本机实现:
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
class NativeWindowListener implements WindowListener
{
public native void windowActivated(WindowEvent e);
public native void windowClosed(WindowEvent e);
public native void windowClosing(WindowEvent e);
public native void windowDeactivated(WindowEvent e);
public native void windowDeiconified(WindowEvent e);
public native void windowIconified(WindowEvent e);
public native void windowOpened(WindowEvent e);
}
接下来,将该类编译为字节码:
$ javac NativeWindowListener.java
使用Linux命令
xxd
或类似工具将NativeWindowListener.class
二进制转换为C数组:
$ xxd -i NativeWindowListener.class
将生成的 C 数组嵌入到本机代码中,并使用
DefineClass
在运行时从字节码定义该类:
const unsigned char NativeWindowListener_class[] = { /* bytecode */ };
const jclass listener_class = jni->DefineClass(
"NativeWindowListener", NULL,
reinterpret_cast<const jbyte*>(NativeWindowListener_class),
sizeof(NativeWindowListener_class));
一旦定义了代理类,您就可以使用
RegisterNatives
为 Java 存根提供本机实现:
// Native event handlers
void JNICALL NativeWindowListener_windowActivated(JNIEnv* jni, jobject self, jobject event);
// etc.
#define WINDOW_EVENT_LISTENER(name) \
{ \
const_cast<char*>(#name), const_cast<char*>("(Ljava/awt/event/WindowEvent;)V"), \
reinterpret_cast<void*>(&NativeWindowListener_##name) \
}
const JNINativeMethod methods[] = {
WINDOW_EVENT_LISTENER(windowActivated),
WINDOW_EVENT_LISTENER(windowClosed),
WINDOW_EVENT_LISTENER(windowClosing),
WINDOW_EVENT_LISTENER(windowDeactivated),
WINDOW_EVENT_LISTENER(windowDeiconified),
WINDOW_EVENT_LISTENER(windowIconified),
WINDOW_EVENT_LISTENER(windowOpened),
};
jni->RegisterNatives(listener_class, methods, sizeof(methods) / sizeof(methods[0]));
最后,您可以构造侦听器并将其传递给
Frame
:
// Construct the listener
const jmethodID listener_ctor = jni->GetMethodID(listener_class, "<init>", "()V");
const jobject listener = jni->NewObject(listener_class, listener_ctor);
// Add listener to the frame
const jclass frame_class = jni->FindClass("java/awt/Frame");
const jmethodID frame_ctor = jni->GetMethodID(frame_class, "<init>", "()V");
const jmethodID frame_addwindowlistener = jni->GetMethodID(frame_class, "addWindowListener", "(Ljava/awt/event/WindowListener;)V");
const jobject frame = jni->NewObject(frame_class, frame_ctor);
jni->CallObjectMethod(frame, frame_addwindowlistener, listener);
如果要存储指向某些本机上下文的指针,可以向侦听器代理添加一个
long
字段,并使用 GetFieldID
和 Get/SetLongField
JNI 函数访问它。
[1] https://github.com/android/ndk/wiki/JNI#unsupported-featuresbackwards-compatibility