-
Notifications
You must be signed in to change notification settings - Fork 588
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/autodowncast4 #554
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package org.bytedeco.javacpp.annotation; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER }) | ||
public @interface Downcast { | ||
String base() default ""; | ||
|
||
boolean classcache() default false; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,6 +93,8 @@ | |
import org.bytedeco.javacpp.annotation.ValueSetter; | ||
import org.bytedeco.javacpp.annotation.Virtual; | ||
import sun.misc.Unsafe; | ||
import java.lang.invoke.MethodHandleInfo; | ||
import org.bytedeco.javacpp.annotation.Downcast; | ||
|
||
/** | ||
* The Generator is where all the C++ source code that we need gets generated. | ||
|
@@ -181,6 +183,124 @@ static enum LongEnum { LONG; long value; } | |
Map<Method,MethodInformation> annotationCache; | ||
boolean mayThrowExceptions, usesAdapters, passesStrings, accessesEnums; | ||
|
||
private void outputDowncastFunDeclaration() { | ||
out.println(""); // a forward declaration is necessary because generated JNI methods will need to call the downcast function | ||
out.println("extern \"C++\" template<class BASE> jobject JavaCPP_downcast(JNIEnv* env, BASE* obj);"); | ||
out.println(""); | ||
} | ||
|
||
private void outputDowncastFunDefinition() { | ||
out.println(""); | ||
out.println("#define JAVACPP_DEBUG_DCAST false"); | ||
out.println(""); | ||
out.println("#include <typeindex>"); | ||
out.println("#include <typeinfo>"); | ||
out.println("#include <ostream>"); | ||
out.println("#include <iostream>"); | ||
out.println("#include <unordered_map>"); | ||
out.println("#include <utility>"); | ||
out.println(""); | ||
out.println("typedef struct constructor {"); | ||
out.println(" jclass clazz;"); | ||
out.println(" jmethodID ctor_mid;"); | ||
out.println("} constructor;"); | ||
out.println(""); | ||
out.println("typedef std::unordered_map<std::type_index, constructor> JavaCPP_JavaCPP_downcast_cache_t;"); | ||
out.println(""); | ||
out.println("JavaCPP_JavaCPP_downcast_cache_t JavaCPP_downcast_cache;"); | ||
out.println(""); | ||
out.println("extern \"C++\" template<class BASE> jobject JavaCPP_downcast(JNIEnv* env, BASE* obj) {"); | ||
out.println(" std::type_index id = typeid(*obj);"); | ||
out.println(" JavaCPP_JavaCPP_downcast_cache_t::iterator ctor = JavaCPP_downcast_cache.find(id);"); | ||
out.println(" if (ctor != JavaCPP_downcast_cache.end()) {"); | ||
out.println(" jobject ret = (jobject)env->NewObject(ctor->second.clazz, ctor->second.ctor_mid, NULL);"); | ||
out.println(" if (ret == NULL || env->ExceptionCheck()) {"); | ||
out.println(" std::cerr << \"Error calling Pointer constructor \" << std::endl;"); | ||
out.println(" return NULL;"); | ||
out.println(" }"); | ||
out.println(" if (JAVACPP_DEBUG_DCAST) std::cerr << \"Success! \" << std::endl;"); | ||
out.println(" return ret;"); | ||
out.println(" }"); | ||
out.println(" else"); | ||
out.println(" return NULL;"); | ||
out.println("}"); | ||
out.println(""); | ||
out.println("void JavaCPP_dcast_preload(JNIEnv* env, std::string class_name, std::type_index type_id) {"); | ||
out.println(" if (JAVACPP_DEBUG_DCAST) std::cerr << \"Try to find class \" << class_name << std::endl;"); | ||
out.println(""); | ||
out.println(" jclass lclazz = env->FindClass(class_name.c_str());"); | ||
out.println(" if (lclazz == NULL || env->ExceptionCheck()) {"); | ||
out.println(" std::cerr << \"Error loading class \" << class_name.c_str() << std::endl;"); | ||
out.println(" return;"); | ||
out.println(" }"); | ||
out.println(""); | ||
out.println(" jclass gclazz = (jclass)env->NewGlobalRef(lclazz);"); | ||
out.println(" if (gclazz == NULL || env->ExceptionCheck()) {"); | ||
out.println(" std::cerr << \"Error creating class \" << class_name << std::endl;"); | ||
out.println(" return;"); | ||
out.println(" }"); | ||
out.println(" "); | ||
out.println(" if (JAVACPP_DEBUG_DCAST) std::cerr << \"Try to call constructor for class \" << class_name << std::endl;"); | ||
out.println(" jmethodID ctor_mid = env->GetMethodID(gclazz, \"<init>\", \"(Lorg/bytedeco/javacpp/Pointer;)V\");"); | ||
out.println(" if (ctor_mid == NULL || env->ExceptionCheck()) {"); | ||
out.println(" std::cerr << \"Error getting Pointer constructor \" << class_name << std::endl;"); | ||
out.println(" return;"); | ||
out.println(" }"); | ||
out.println(" JavaCPP_downcast_cache[type_id] = constructor{gclazz,ctor_mid};"); | ||
out.println(" }"); | ||
} | ||
|
||
void outputDowncastCache(LinkedHashSet<Class> allClasses) { | ||
LinkedHashSet<String> baseClasses = new LinkedHashSet<String>(); | ||
// Scan to find out Downcast base classes | ||
for (Class<?> cls : allClasses) { | ||
// if super class is InfoMapper then this is likely a target class else skip this | ||
if (!InfoMapper.class.isAssignableFrom(cls.getSuperclass())) | ||
continue; | ||
// check all methods to see if they declare a Downcast baseclass via annotation | ||
for (Method m : cls.getMethods()) { | ||
Downcast d = m.getAnnotation(Downcast.class); | ||
if (d != null && d.base().length() > 0) | ||
baseClasses.add(d.base()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I understand why the annotation is on the methods. Why can't it be on the base class itself, and that's it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, it should be on the base class. I'm all for it. But I could not figure out how to do that, everything I tried caused some or other problem in parsing or the generated Java code. So I could do with some help here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could not find any examples (my bad I'm sure) how to apply
and added
but this results in error:
because the generated code looks like:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does that because it's missing a name for the class, like this works: infoMap.put(new Info("Foo").annotations("@Opaque").pointerTypes("Foo")); It's probably a bug, but since it's not a big issue, I've never looked into it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. That works for the simple test case. I tried to add it to OCC Standard_Transient and I got errors like this:
for generated code like this:
I guess I could not process the "Standard_Transient.hxx" and just keep it opaque totally, not sure if this is going to leave some essential functionality not accessible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, we might need to add a new Info field for that... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been thinking about this, and this is somewhat similar to the situation with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or, if you prefer, we could add a new field like |
||
} | ||
} | ||
|
||
if (!baseClasses.isEmpty()) | ||
outputDowncastFunDefinition(); | ||
|
||
out.println("static void JavaCPP_dcast_preload_classes(JNIEnv* env) {"); | ||
// Create/output runtime cache initialisation code for all target classes that are derived from any of the baseclasses | ||
for (Class c : allClasses) { | ||
// if super class is InfoMapper then this is likely a target class else skip this | ||
if (!InfoMapper.class.isAssignableFrom(c.getSuperclass())) | ||
continue; | ||
|
||
for (Class<?> c2 : c.getClasses()) { | ||
if (c2.isInterface()) | ||
continue; | ||
// follow up the super class chain to see if this is derived from a baseclass | ||
Class<?> sc = c2; | ||
while (sc != null) { | ||
if (baseClasses.contains(sc.getSimpleName())) | ||
break; | ||
sc = sc.getSuperclass(); | ||
} | ||
if (sc == null) { | ||
continue; | ||
} | ||
|
||
String canonName = c2.getCanonicalName(); | ||
int i = canonName.lastIndexOf("."); | ||
String javaName = (canonName.substring(0, i) + "$" + canonName.substring(i + 1)).replaceAll("\\.", "/"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dynamize/dynamic is ok, I think your suggestion is good. I will see if I have some time to work on this during the next two week.s |
||
Name na = c2.getAnnotation(Name.class); | ||
String cppTypeName = na != null ? na.value()[0] : c2.getSimpleName(); | ||
out.println(" JavaCPP_dcast_preload(env, \"" + javaName + "\", typeid(::" + cppTypeName + "));"); | ||
} | ||
} | ||
out.println(" }"); | ||
|
||
} | ||
|
||
public boolean generate(String sourceFilename, String jniConfigFilename, String reflectConfigFilename, String headerFilename, | ||
String loadSuffix, String baseLoadSuffix, String classPath, Class<?> ... classes) throws IOException { | ||
try { | ||
|
@@ -1646,6 +1766,7 @@ boolean classes(boolean handleExceptions, boolean defineAdapters, boolean conver | |
out.println(); | ||
out.println("JNIEXPORT jint JNICALL JNI_OnLoad" + baseLoadSuffix + "(JavaVM* vm, void* reserved);"); | ||
out.println("JNIEXPORT void JNICALL JNI_OnUnload" + baseLoadSuffix + "(JavaVM* vm, void* reserved);"); | ||
out.println("static void JavaCPP_dcast_preload_classes(JNIEnv* env);"); | ||
} | ||
out.println(); // XXX: JNI_OnLoad() should ideally be protected by some mutex | ||
out.println("JNIEXPORT jint JNICALL JNI_OnLoad" + loadSuffix + "(JavaVM* vm, void* reserved) {"); | ||
|
@@ -1691,6 +1812,8 @@ boolean classes(boolean handleExceptions, boolean defineAdapters, boolean conver | |
out.println(" env->PopLocalFrame(NULL);"); | ||
out.println(" }"); | ||
out.println(" }"); | ||
if (baseLoadSuffix != null && !baseLoadSuffix.isEmpty()) | ||
out.println(" JavaCPP_dcast_preload_classes(env);"); | ||
out.println(" }"); | ||
out.println(" JavaCPP_addressFID = JavaCPP_getFieldID(env, " + | ||
jclasses.index(Pointer.class) + ", \"address\", \"J\");"); | ||
|
@@ -1862,7 +1985,9 @@ boolean classes(boolean handleExceptions, boolean defineAdapters, boolean conver | |
out.println(" JavaCPP_vm = NULL;"); | ||
out.println("}"); | ||
out.println(); | ||
|
||
|
||
outputDowncastFunDeclaration(); | ||
|
||
boolean supportedPlatform = false; | ||
LinkedHashSet<Class> allClasses = new LinkedHashSet<Class>(); | ||
if (baseLoadSuffix == null || baseLoadSuffix.isEmpty()) { | ||
|
@@ -1889,6 +2014,9 @@ boolean classes(boolean handleExceptions, boolean defineAdapters, boolean conver | |
|
||
out.println("}"); | ||
out.println(); | ||
|
||
outputDowncastCache(allClasses); | ||
|
||
if (out2 != null) { | ||
out2.println("#ifdef __cplusplus"); | ||
out2.println("}"); | ||
|
@@ -2325,7 +2453,10 @@ String returnBefore(MethodInformation methodInfo) { | |
out.println(" jobject rarg = obj;"); | ||
} else if (methodInfo.returnType.isPrimitive()) { | ||
out.println(" " + jniTypeName(methodInfo.returnType) + " rarg = 0;"); | ||
returnPrefix = typeName[0] + " rval" + typeName[1] + " = " + cast; | ||
if (typeName.length >= 2) | ||
returnPrefix = typeName[0] + " rval" + typeName[1] + " = " + cast; | ||
else | ||
returnPrefix = typeName[0] + " rval" + " = " + cast; | ||
if ((returnBy instanceof ByPtr) || (returnBy instanceof ByPtrRef)) { | ||
returnPrefix += "*"; | ||
} | ||
|
@@ -2818,7 +2949,14 @@ void returnAfter(MethodInformation methodInfo) { | |
} | ||
} | ||
out.println( "if (rptr != NULL) {"); | ||
out.println(indent + " rarg = JavaCPP_createPointer(env, " + jclasses.index(methodInfo.returnType) + | ||
|
||
Downcast a = (Downcast) methodInfo.method.getAnnotation(Downcast.class); | ||
if (a != null) | ||
out.println(indent + " rarg = JavaCPP_downcast<" + a.base() + ">(env, rptr);"); | ||
else | ||
out.println(indent + " rarg = JavaCPP_createPointer(env, " + jclasses.index(methodInfo.returnType) + (methodInfo.parameterTypes.length > 0 && methodInfo.parameterTypes[0] == Class.class ? ", arg0);" : ");")); | ||
|
||
out.println(indent + " rarg = JavaCPP_createPointer(env, " + jclasses.index(methodInfo.returnType) + | ||
(methodInfo.parameterTypes.length > 0 && methodInfo.parameterTypes[0] == Class.class ? ", arg0);" : ");")); | ||
out.println(indent + " if (rarg != NULL) {"); | ||
if (needInit) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also make this work for classes written manually that are not meant to parse header files, that is to say, they do not implement InfoMapper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, how do we go about that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose if we put annotations only on the base classes we're not going to need this logic, am I right?