aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2023-06-29 03:50:12 +0200
committerSven Gothel <[email protected]>2023-06-29 03:50:12 +0200
commit6591f1fef419841660311bbb554aeda7b267c9a7 (patch)
treeaa5dba474e78f83628e1cfa7a700797c3f848594
parent1d66739e09899cb90888c8fe34aba339511aa656 (diff)
GlueGen JavaCallback: 1st Working Draft: Changed 'JavaCallbackDef' config, added code generation incl. native to Java dispatch and resource management
Tested via Test4JavaCallback.java (using test2.[hc]). Please read the GlueGen_Mapping.md as well as Test4JavaCallback.java . +++ Some implementation details: JavaConfiguration maps JavaCallbackDef to JavaCallback set-function and maintains a list. JavaCallbackDef itself holds all configured details. JavaConfiguration also maps JavaCallbackInfo to JavaCallback set-function. JavaCallbackInfo itself holds all compile time information, as produced by JavaEmitter.beginFunctions(..). This extends JavaCallbackDef and avoid repetetive computation for the callback-function-type and its MethodBinding, parameter indices for the callback interface and userParam, etc. CMethodBindingEmitter: Native callback to Java dispatch - The JavaCallback setter function creates a native 'UserParam' struct instance, which holds the callback-interface-jobject, its callback-jmethodID and the userParam-jobject for invocation of the actual JavaCallback interface method. - To produce the C-Type -> JNI-Type conversion, An internal CMethodBindingEmitter instance for the native-callback function binding is created inside the CMethodBindingEmitter of the callback setter method. It is being used to map the types to JNI within the generated native callback function, passed to the actual JavaCallback method. JavaMethodBindingEmitter: Native callback to Java dispatch - The JavaCallbacl setter passes the callback-interface-object, the userParam-object and the callback-method-signature (to have the native method retrieve the jmethodID). - It receives the native pointer of the native `UserParam` struct instance, which gets mapped to the userParam-object. (*TODO: Refine ownership + release*).
-rw-r--r--doc/GlueGen_Mapping.html231
-rw-r--r--doc/GlueGen_Mapping.md84
-rwxr-xr-xmake/scripts/runtest.sh1
-rw-r--r--src/java/com/jogamp/gluegen/CMethodBindingEmitter.java188
-rw-r--r--src/java/com/jogamp/gluegen/JavaConfiguration.java139
-rw-r--r--src/java/com/jogamp/gluegen/JavaEmitter.java100
-rw-r--r--src/java/com/jogamp/gluegen/JavaMethodBindingEmitter.java66
-rw-r--r--src/junit/com/jogamp/gluegen/test/junit/generation/BaseClass.java2
-rw-r--r--src/junit/com/jogamp/gluegen/test/junit/generation/Test4JavaCallback.java143
-rw-r--r--src/junit/com/jogamp/gluegen/test/junit/generation/test2.c67
-rw-r--r--src/junit/com/jogamp/gluegen/test/junit/generation/test2.cfg36
-rw-r--r--src/junit/com/jogamp/gluegen/test/junit/generation/test2.h23
12 files changed, 863 insertions, 217 deletions
diff --git a/doc/GlueGen_Mapping.html b/doc/GlueGen_Mapping.html
index 9739082..02ec43f 100644
--- a/doc/GlueGen_Mapping.html
+++ b/doc/GlueGen_Mapping.html
@@ -426,11 +426,18 @@
<li><a href="#alignment-for-compound-data">Alignment for Compound
Data</a></li>
</ul></li>
+ <li><a href="#oo-style-api-interface-mapping">OO-Style API Interface
+ Mapping</a>
+ <ul>
+ <li><a href="#oo-style-mapping-settings">OO-Style Mapping
+ Settings</a></li>
+ <li><a href="#oo-style-example">OO-Style Example</a></li>
+ </ul></li>
<li><a href="#struct-mapping">Struct Mapping</a>
<ul>
+ <li><a href="#struct-mapping-notes">Struct Mapping Notes</a></li>
<li><a href="#gluegen-struct-settings">GlueGen Struct
Settings</a></li>
- <li><a href="#struct-mapping-notes">Struct Mapping Notes</a></li>
<li><a href="#struct-setter-pseudo-code">Struct Setter
Pseudo-Code</a></li>
<li><a href="#struct-java-signature-table">Struct Java Signature
@@ -441,8 +448,18 @@
Support</a></li>
<li><a href="#struct-function-pointer-support">Struct Function-Pointer
Support</a></li>
+ </ul></li>
<li><a href="#java-callback-from-native-c-api-support">Java Callback
- from Native C-API Support</a></li>
+ from Native C-API Support</a>
+ <ul>
+ <li><a href="#javacallback-constraints">JavaCallback
+ Constraints</a></li>
+ </ul></li>
+ <li><a href="#misc-configurations">Misc Configurations</a>
+ <ul>
+ <li><a
+ href="#libraryonload-librarybasename-for-jni_onload-"><code>LibraryOnLoad &lt;LibraryBasename&gt;</code>
+ for <code>JNI_OnLoad*(..)</code> ...</a></li>
</ul></li>
<li><a href="#platform-header-files">Platform Header Files</a></li>
<li><a href="#pre-defined-macros">Pre-Defined Macros</a></li>
@@ -483,11 +500,30 @@ capable of representing all C types to represent the APIs for which it
generates interfaces. It has the ability to perform significant
transformations on the IR before glue code emission.</p>
<p>GlueGen can produce native foreign function bindings to Java as well
-as map native data structures to be fully accessible from Java including
-potential calls to embedded function pointer.</p>
-<p>GlueGen is also capable to bind even low-level APIs such as the Java
-Native Interface (JNI) and the AWT Native Interface (JAWT) back up to
-the Java programming language.</p>
+as <a href="#struct-mapping">map native data structures</a> to be fully
+accessible from Java including potential calls to <a
+href="#struct-function-pointer-support">embedded function
+pointer</a>.</p>
+<p>GlueGen supports <a
+href="#java-callback-from-native-c-api-support">registering Java
+callback methods</a> to receive asynchronous and off-thread native
+toolkit events, where a generated native callback function dispatches
+the events to Java.</p>
+<p>GlueGen also supports <a
+href="#oo-style-api-interface-mapping">producing an OO-Style API
+mapping</a> like <a href="../../jogl/doc/uml/html/index.html">JOGL's
+incremental OpenGL Profile API levels</a>.</p>
+<p>GlueGen is capable to bind low-level APIs such as the Java Native
+Interface (JNI) and the AWT Native Interface (JAWT) back up to the Java
+programming language.</p>
+<p>Further, GlueGen supports generating <code>JNI_OnLoad(..)</code> for
+dynamic and <code>JNI_OnLoad_&lt;LibraryBasename&gt;(..)</code> for
+static libraries via <a
+href="#libraryonload-librarybasename-for-jni_onload-"><code>LibraryOnLoad Bindingtest2</code></a>,
+which also provides <code>JVMUtil_GetJNIEnv(..)</code> to resolve the
+<code>JNIEnv*</code> as used by <a
+href="#java-callback-from-native-c-api-support">Java callback
+methods</a>.</p>
<p>GlueGen utilizes <a
href="https://jogamp.org/cgit/jcpp.git/about/">JCPP</a>, migrated C
preprocessor written in Java.</p>
@@ -930,6 +966,68 @@ Window(mingw/mingw64)</h4>
+armv7l-eabi<br />
- MacOsX-32bit-gcc4<br />
∗ Windows</p>
+<h2 id="oo-style-api-interface-mapping">OO-Style API Interface
+Mapping</h2>
+<p>GlueGen supports producing an OO-Style API mapping like <a
+href="../../jogl/doc/uml/html/index.html">JOGL's incremental OpenGL
+Profile API levels</a>.</p>
+<h3 id="oo-style-mapping-settings">OO-Style Mapping Settings</h3>
+<ul>
+<li><p><code>ExtendedInterfaceSymbolsIgnore ../build-temp/gensrc/classes/com/jogamp/opengl/GL.java</code></p>
+<p>Ignore all extended interface symbols from named Java source
+file.</p>
+<p>The named Java source file is parsed and a list of its symbols
+extracted, allowing GlueGen to ignore these in the generated interface
+(here GLES3).</p>
+<p>This complements <code>Extends</code> setting, see below.</p></li>
+<li><p><code>Extends GLES3 GLES2</code></p>
+<p>The generated interface GLES3 extends interface GLES2.</p>
+<p>This complements <code>ExtendedInterfaceSymbolsIgnore</code> setting,
+see above.</p></li>
+<li><p><code>Implements GLES3Impl GLES3</code></p>
+<p>The generated implementation GLES3Impl implements interface
+GLES3.</p></li>
+</ul>
+<h3 id="oo-style-example">OO-Style Example</h3>
+<p>Example snippet from JOGL's GLES3 interface config
+<code>gl-if-es3.cfg</code></p>
+<pre><code>...
+
+ExtendedInterfaceSymbolsIgnore ../build-temp/gensrc/classes/com/jogamp/opengl/GL.java
+ExtendedInterfaceSymbolsIgnore ../build-temp/gensrc/classes/com/jogamp/opengl/GL2ES2.java
+ExtendedInterfaceSymbolsIgnore ../build-temp/gensrc/classes/com/jogamp/opengl/GLES2.java
+ExtendedInterfaceSymbolsIgnore ../build-temp/gensrc/classes/com/jogamp/opengl/GL2ES3.java
+ExtendedInterfaceSymbolsIgnore ../build-temp/gensrc/classes/com/jogamp/opengl/GL3ES3.java
+ExtendedInterfaceSymbolsIgnore ../build-temp/gensrc/classes/com/jogamp/opengl/GL4ES3.java
+ExtendedInterfaceSymbolsIgnore ../src/jogl/classes/com/jogamp/opengl/GLBase.java
+
+Package com.jogamp.opengl
+Style InterfaceOnly
+JavaClass GLES3
+Extends GLES3 GLES2
+Extends GLES3 GL4ES3
+...</code></pre>
+<p>Example snippet from JOGL's GLES3Impl implementation
+<code>gl-es3-impl.cfg</code></p>
+<pre><code>...
+ExtendedInterfaceSymbolsIgnore ../build-temp/gensrc/classes/com/jogamp/opengl/GL.java
+ExtendedInterfaceSymbolsIgnore ../build-temp/gensrc/classes/com/jogamp/opengl/GL2ES2.java
+ExtendedInterfaceSymbolsIgnore ../build-temp/gensrc/classes/com/jogamp/opengl/GLES2.java
+ExtendedInterfaceSymbolsIgnore ../build-temp/gensrc/classes/com/jogamp/opengl/GL2ES3.java
+ExtendedInterfaceSymbolsIgnore ../build-temp/gensrc/classes/com/jogamp/opengl/GL3ES3.java
+ExtendedInterfaceSymbolsIgnore ../build-temp/gensrc/classes/com/jogamp/opengl/GL4ES3.java
+ExtendedInterfaceSymbolsIgnore ../build-temp/gensrc/classes/com/jogamp/opengl/GLES3.java
+ExtendedInterfaceSymbolsIgnore ../src/jogl/classes/com/jogamp/opengl/GLBase.java
+
+Style ImplOnly
+ImplPackage jogamp.opengl.es3
+ImplJavaClass GLES3Impl
+Implements GLES3Impl GLES2
+Implements GLES3Impl GLES3
+...</code></pre>
+<p>Above produces the GLES3 interface and its implementation as visible
+in JOGL's UML document <a
+href="../../jogl/doc/uml/html/index.html">about OpenGL Profiles</a>.</p>
<h2 id="struct-mapping">Struct Mapping</h2>
<p>A <em>Struct</em> is a C compound type declaration, which can be
mapped to a Java class.</p>
@@ -1826,43 +1924,104 @@ StructPackage T2_InitializeOptions com.jogamp.gluegen.test.junit.generation</cod
/** Interface to C language function: &lt;br&gt; &lt;code&gt;int32_t CustomFuncB1(T2_UserData * pUserData)&lt;/code&gt;&lt;br&gt; */
public final int CustomFuncB1(T2_UserData pUserData) { .. } </code></pre>
-<h3 id="java-callback-from-native-c-api-support">Java Callback from
-Native C-API Support</h3>
-<p>GlueGen supports registering Java callback methods to native C-API
-functions in the form:</p>
-<pre><code>typedef int32_t ( * T_CallbackFunc)(size_t id, const char* msg, void* userParam);
-
-void AddMessageCallback(T_CallbackFunc func, void* userParam);
-void RemoveMessageCallback(T_CallbackFunc func, void* userParam);
-void InjectMessageCallback(size_t id, const char* msg);</code></pre>
+<h2 id="java-callback-from-native-c-api-support">Java Callback from
+Native C-API Support</h2>
+<p>GlueGen supports registering Java callback methods to receive
+asynchronous and off-thread native toolkit events, where a generated
+native callback function dispatches the events to Java.</p>
+<p>C-API header snippet:</p>
+<pre><code>typedef void ( * T2_CallbackFunc01)(size_t id, const char* msg, void* usrParam);
+
+/** Sets the given `cbFunc` and associated `usrParam` as the callback. Passing NULL for `func` _and_ same `usrParam` removes the callback and its associated resources. */
+void MessageCallback01(T2_CallbackFunc01 cbFunc, void* usrParam);
+
+void InjectMessageCallback01(size_t id, const char* msg);</code></pre>
<p>and the following GlueGen configuration</p>
-<pre><code>ArgumentIsString T2_CallbackFunc 1
-ArgumentIsString InjectMessageCallback 1
-
-# Define a JavaCallback, enacted on a function-pointer argument `T2_CallbackFunc` and a user-param `void*` for Java Object mapping
-JavaCallbackDef T2_CallbackFunc 2</code></pre>
+<pre><code># JavaCallback requires `JNI_OnLoad*(..)` and `JVMUtil_GetJNIEnv(..)`
+LibraryOnLoad Bindingtest2
+
+ArgumentIsString T2_CallbackFunc01 1
+ArgumentIsString InjectMessageCallback01 1
+
+# Define a JavaCallback.
+# Set JavaCallback via function `MessageCallback01` if `T2_CallbackFunc01` argument is non-null, otherwise removes the callback and associated resources.
+# It uses `usrParam` as the resource-key to map to the hidden native-usrParam object,
+# hence a matching &#39;usrParam&#39; must be passed for setting and removal of the callback.
+#
+# It uses the function-pointer argument `T2_CallbackFunc01` as the callback function type
+# and marks `T2_CallbackFunc01`s 3rd argument (index 2) as the mandatory user-param for Java Object mapping.
+#
+# Note: An explicit `isMessageCallback01Mapped(Object usrParam)` is being created to explicitly query whether `usrParam` maps to the associated resources.
+JavaCallbackDef MessageCallback01 T2_CallbackFunc01 2</code></pre>
+<p>Note that <a
+href="#libraryonload-librarybasename-for-jni_onload-"><code>LibraryOnLoad Bindingtest2</code></a>
+must be specified in exactly one native code-unit. It provides code to
+allow the generated native callback-function to attach the current
+thread to the <code>JavaVM*</code> generating a new
+<code>JNIEnv*</code>in daemon mode - or just to retrieve the thread's
+<code>JNIEnv*</code>, if already attached to the
+<code>JavaVM*</code>.</p>
<p>This will lead to the following result</p>
<pre><code>public interface Bindingtest2 {
- /** JavaCallback interface: T2_CallbackFunc -&gt; int32_t (*T2_CallbackFunc)(size_t id, const char * msg, void * userParam) */
- public static interface T2_CallbackFunc {
- /** Interface to C language function: &lt;br&gt; &lt;code&gt;int32_t callback(size_t id, const char * msg, void * userParam)&lt;/code&gt;&lt;br&gt;Alias for: &lt;code&gt;T2_CallbackFunc&lt;/code&gt; */
- public int callback(long id, String msg, Object userParam);
+ /** JavaCallback interface: T2_CallbackFunc01 -&gt; void (*T2_CallbackFunc01)(size_t id, const char * msg, void * usrParam) */
+ public static interface T2_CallbackFunc01 {
+ /** Interface to C language function: &lt;br&gt; &lt;code&gt;void callback(size_t id, const char * msg, void * usrParam)&lt;/code&gt;&lt;br&gt;Alias for: &lt;code&gt;T2_CallbackFunc01&lt;/code&gt; */
+ public void callback(long id, String msg, Object usrParam);
}
...
+
+ /** Entry point (through function pointer) to C language function: &lt;br&gt; &lt;code&gt;void MessageCallback01(T2_CallbackFunc01 cbFunc, void * usrParam)&lt;/code&gt;&lt;br&gt; */
+ public void MessageCallback01(T2_CallbackFunc01 cbFunc, Object usrParam);
+
+ public boolean isMessageCallback01Mapped(final Object usrParam);
- /** Entry point (through function pointer) to C language function: &lt;br&gt; &lt;code&gt;void AddMessageCallback(int32_t (*func)(size_t id, const char * msg, void * userParam), void * userParam)&lt;/code&gt;&lt;br&gt; */
- public void AddMessageCallback(T2_CallbackFunc func, Object userParam);
-
- /** Entry point (through function pointer) to C language function: &lt;br&gt; &lt;code&gt;void RemoveMessageCallback(int32_t (*func)(size_t id, const char * msg, void * userParam), void * userParam)&lt;/code&gt;&lt;br&gt; */
- public void RemoveMessageCallback(T2_CallbackFunc func, Object userParam);
-
- /** Entry point (through function pointer) to C language function: &lt;br&gt; &lt;code&gt;void InjectMessageCallback(size_t id, const char * msg)&lt;/code&gt;&lt;br&gt; */
- public void InjectMessageCallback(long id, String msg);
-</code></pre>
-<p><em>TODO: Work in progress</em></p>
-<h4 id="example-1">Example</h4>
+ /** Entry point (through function pointer) to C language function: &lt;br&gt; &lt;code&gt;void InjectMessageCallback01(size_t id, const char * msg)&lt;/code&gt;&lt;br&gt; */
+ public void InjectMessageCallback01(long id, String msg);</code></pre>
+<h3 id="javacallback-constraints">JavaCallback Constraints</h3>
+<p>Please consider the following <em>currently enabled</em> constraints
+using <code>JavaCallbackDef</code></p>
+<ul>
+<li>Only one interface callback-method binding is allowed for a native
+callback function, e.g. <code>T2_CallbackFunc01</code> (see above).</li>
+<li>The native callback function can only return no-value, i.e.
+<code>void</code>, or a primitive type. Usually <code>void</code> is
+being used in toolkit APIs.</li>
+<li>The native callback function argument types must be able to be
+mapped to JNI Java types as supported for return values of all native
+functions, the same code path is being used within
+<code>CMethodBindingEmitter.emitBodyMapCToJNIType(..)</code>.</li>
+<li>To remove a JavaCallback the specified and mapped setter function,
+e.g. <code>MessageCallback01</code>, must be called with
+<code>null</code> for the callback interface but the very same
+<code>userParam</code> instance as previously called to set the
+callback.</li>
+<li>Exactly one native code-unit for the library must specify <a
+href="#libraryonload-librarybasename-for-jni_onload-"><code>LibraryOnLoad libraryBasename</code></a></li>
+<li>...</li>
+</ul>
+<p><em>TODO: Enhance documentation</em></p>
+<h2 id="misc-configurations">Misc Configurations</h2>
+<h3
+id="libraryonload-librarybasename-for-jni_onload-"><code>LibraryOnLoad &lt;LibraryBasename&gt;</code>
+for <code>JNI_OnLoad*(..)</code> ...</h3>
+<p><code>LibraryOnLoad &lt;LibraryBasename&gt;</code> generates native
+JNI code <code>JNI_OnLoad(..)</code> used for dynamic libraries,
+<code>JNI_OnLoad_&lt;LibraryBasename&gt;(..)</code> used for static
+libraries, <code>JVMUtil_GetJNIEnv(..)</code> and the instance of
+<code>JavaVM* &lt;LibraryBasename&gt;_jvmHandle</code>.</p>
+<p>The <code>JNI_OnLoad*(..)</code> methods set the
+<code>JavaVM* &lt;LibraryBasename&gt;_jvmHandle</code>, which in turn is
+utilized by <code>JVMUtil_GetJNIEnv(..)</code> to attach a new thread to
+the <code>JavaVM*</code> generating a new <code>JNIEnv*</code>in daemon
+mode - or just to retrieve the thread's <code>JNIEnv*</code>, if already
+attached to the <code>JavaVM*</code>.</p>
+<p>The <code>LibraryBasename</code> parameter is used to generate the
+<code>JNI_OnLoad_&lt;LibraryBasename&gt;(..)</code> variant for
+statically linked libraries. <code>JNI_OnLoad(..)</code>,
+<code>JNI_OnLoad_&lt;LibraryBasename&gt;(..)</code> and
+<code>JVMUtil_GetJNIEnv(..)</code></p>
<h2 id="platform-header-files">Platform Header Files</h2>
<p>GlueGen provides convenient platform headers,<br />
which can be included in your C header files for native compilation and
diff --git a/doc/GlueGen_Mapping.md b/doc/GlueGen_Mapping.md
index ee23845..738c529 100644
--- a/doc/GlueGen_Mapping.md
+++ b/doc/GlueGen_Mapping.md
@@ -31,6 +31,10 @@ GlueGen can produce native foreign function bindings to Java as well as
[map native data structures](#struct-mapping) to be fully accessible from Java including
potential calls to [embedded function pointer](#struct-function-pointer-support).
+GlueGen supports [registering Java callback methods](#java-callback-from-native-c-api-support)
+to receive asynchronous and off-thread native toolkit events,
+where a generated native callback function dispatches the events to Java.
+
GlueGen also supports [producing an OO-Style API mapping](#oo-style-api-interface-mapping) like [JOGL's incremental OpenGL Profile API levels](../../jogl/doc/uml/html/index.html).
GlueGen is capable to bind low-level APIs such as the Java Native Interface (JNI) and
@@ -752,49 +756,77 @@ and similar to `T2_CustomFuncB customFuncB1`
public final int CustomFuncB1(T2_UserData pUserData) { .. }
```
-### Java Callback from Native C-API Support
-GlueGen supports registering Java callback methods to native C-API functions in the form:
-```
-typedef int32_t ( * T_CallbackFunc)(size_t id, const char* msg, void* userParam);
+## Java Callback from Native C-API Support
+GlueGen supports registering Java callback methods
+to receive asynchronous and off-thread native toolkit events,
+where a generated native callback function dispatches the events to Java.
-void AddMessageCallback(T_CallbackFunc func, void* userParam);
-void RemoveMessageCallback(T_CallbackFunc func, void* userParam);
-void InjectMessageCallback(size_t id, const char* msg);
+C-API header snippet:
```
+typedef void ( * T2_CallbackFunc01)(size_t id, const char* msg, void* usrParam);
-and the following GlueGen configuration
+/** Sets the given `cbFunc` and associated `usrParam` as the callback. Passing NULL for `func` _and_ same `usrParam` removes the callback and its associated resources. */
+void MessageCallback01(T2_CallbackFunc01 cbFunc, void* usrParam);
+
+void InjectMessageCallback01(size_t id, const char* msg);
```
-ArgumentIsString T2_CallbackFunc 1
-ArgumentIsString InjectMessageCallback 1
-# Define a JavaCallback, enacted on a function-pointer argument `T2_CallbackFunc` and a user-param `void*` for Java Object mapping
-JavaCallbackDef T2_CallbackFunc 2
+and the following GlueGen configuration
```
+# JavaCallback requires `JNI_OnLoad*(..)` and `JVMUtil_GetJNIEnv(..)`
+LibraryOnLoad Bindingtest2
+
+ArgumentIsString T2_CallbackFunc01 1
+ArgumentIsString InjectMessageCallback01 1
+
+# Define a JavaCallback.
+# Set JavaCallback via function `MessageCallback01` if `T2_CallbackFunc01` argument is non-null, otherwise removes the callback and associated resources.
+# It uses `usrParam` as the resource-key to map to the hidden native-usrParam object,
+# hence a matching 'usrParam' must be passed for setting and removal of the callback.
+#
+# It uses the function-pointer argument `T2_CallbackFunc01` as the callback function type
+# and marks `T2_CallbackFunc01`s 3rd argument (index 2) as the mandatory user-param for Java Object mapping.
+#
+# Note: An explicit `isMessageCallback01Mapped(Object usrParam)` is being created to explicitly query whether `usrParam` maps to the associated resources.
+JavaCallbackDef MessageCallback01 T2_CallbackFunc01 2
+```
+Note that [`LibraryOnLoad Bindingtest2`](#libraryonload-librarybasename-for-jni_onload-) must be specified in exactly one native code-unit.
+It provides code to allow the generated native callback-function to attach the current thread to the `JavaVM*` generating a new `JNIEnv*`in daemon mode -
+or just to retrieve the thread's `JNIEnv*`, if already attached to the `JavaVM*`.
This will lead to the following result
```
public interface Bindingtest2 {
- /** JavaCallback interface: T2_CallbackFunc -> int32_t (*T2_CallbackFunc)(size_t id, const char * msg, void * userParam) */
- public static interface T2_CallbackFunc {
- /** Interface to C language function: <br> <code>int32_t callback(size_t id, const char * msg, void * userParam)</code><br>Alias for: <code>T2_CallbackFunc</code> */
- public int callback(long id, String msg, Object userParam);
+ /** JavaCallback interface: T2_CallbackFunc01 -> void (*T2_CallbackFunc01)(size_t id, const char * msg, void * usrParam) */
+ public static interface T2_CallbackFunc01 {
+ /** Interface to C language function: <br> <code>void callback(size_t id, const char * msg, void * usrParam)</code><br>Alias for: <code>T2_CallbackFunc01</code> */
+ public void callback(long id, String msg, Object usrParam);
}
...
+
+ /** Entry point (through function pointer) to C language function: <br> <code>void MessageCallback01(T2_CallbackFunc01 cbFunc, void * usrParam)</code><br> */
+ public void MessageCallback01(T2_CallbackFunc01 cbFunc, Object usrParam);
+
+ public boolean isMessageCallback01Mapped(final Object usrParam);
- /** Entry point (through function pointer) to C language function: <br> <code>void AddMessageCallback(int32_t (*func)(size_t id, const char * msg, void * userParam), void * userParam)</code><br> */
- public void AddMessageCallback(T2_CallbackFunc func, Object userParam);
-
- /** Entry point (through function pointer) to C language function: <br> <code>void RemoveMessageCallback(int32_t (*func)(size_t id, const char * msg, void * userParam), void * userParam)</code><br> */
- public void RemoveMessageCallback(T2_CallbackFunc func, Object userParam);
-
- /** Entry point (through function pointer) to C language function: <br> <code>void InjectMessageCallback(size_t id, const char * msg)</code><br> */
- public void InjectMessageCallback(long id, String msg);
-
+ /** Entry point (through function pointer) to C language function: <br> <code>void InjectMessageCallback01(size_t id, const char * msg)</code><br> */
+ public void InjectMessageCallback01(long id, String msg);
```
-*TODO: Work in progress*
+### JavaCallback Constraints
+Please consider the following *currently enabled* constraints using `JavaCallbackDef`
+- Only one interface callback-method binding is allowed for a native callback function, e.g. `T2_CallbackFunc01` (see above).
+- The native callback function can only return no-value, i.e. `void`, or a primitive type. Usually `void` is being used in toolkit APIs.
+- The native callback function argument types must be able to be mapped to JNI Java types as supported for return values of all native functions,
+ the same code path is being used within `CMethodBindingEmitter.emitBodyMapCToJNIType(..)`.
+- To remove a JavaCallback the specified and mapped setter function, e.g. `MessageCallback01`, must be called with `null` for the callback interface
+ but the very same `userParam` instance as previously called to set the callback.
+- Exactly one native code-unit for the library must specify [`LibraryOnLoad libraryBasename`](#libraryonload-librarybasename-for-jni_onload-)
+- ...
+
+*TODO: Enhance documentation*
## Misc Configurations
diff --git a/make/scripts/runtest.sh b/make/scripts/runtest.sh
index 40cd4c5..e20c09e 100755
--- a/make/scripts/runtest.sh
+++ b/make/scripts/runtest.sh
@@ -154,6 +154,7 @@ function onetest() {
onetest com.jogamp.gluegen.test.junit.generation.Test1p2DynamicLibraryBundle 2>&1 | tee -a $LOG
onetest com.jogamp.gluegen.test.junit.generation.Test2FuncPtr 2>&1 | tee -a $LOG
onetest com.jogamp.gluegen.test.junit.generation.Test3PtrStorage 2>&1 | tee -a $LOG
+onetest com.jogamp.gluegen.test.junit.generation.Test4JavaCallback 2>&1 | tee -a $LOG
#onetest com.jogamp.gluegen.test.junit.structgen.TestStructGen01 2>&1 | tee -a $LOG
#onetest com.jogamp.gluegen.test.junit.structgen.TestStructGen02 2>&1 | tee -a $LOG
diff --git a/src/java/com/jogamp/gluegen/CMethodBindingEmitter.java b/src/java/com/jogamp/gluegen/CMethodBindingEmitter.java
index d0e0c45..d802ee7 100644
--- a/src/java/com/jogamp/gluegen/CMethodBindingEmitter.java
+++ b/src/java/com/jogamp/gluegen/CMethodBindingEmitter.java
@@ -40,11 +40,14 @@
package com.jogamp.gluegen;
+import static java.util.logging.Level.INFO;
+
import java.io.PrintWriter;
import java.text.MessageFormat;
import java.util.List;
import com.jogamp.common.os.MachineDataInfo;
+import com.jogamp.gluegen.JavaConfiguration.JavaCallbackInfo;
import com.jogamp.gluegen.Logging.LoggerIf;
import com.jogamp.gluegen.cgram.types.ArrayType;
import com.jogamp.gluegen.cgram.types.FunctionSymbol;
@@ -113,6 +116,10 @@ public class CMethodBindingEmitter extends FunctionEmitter {
// We need this in order to compute sizes of certain types
protected MachineDataInfo machDesc;
+ private final JavaCallbackInfo javaCallback;
+ private final String jcbNativeBasename;
+ private final CMethodBindingEmitter jcbCMethodEmitter;
+
/**
* Constructs an emitter for the specified binding, and sets a default
* comment emitter that will emit the signature of the C function that is
@@ -145,6 +152,18 @@ public class CMethodBindingEmitter extends FunctionEmitter {
this.forIndirectBufferAndArrayImplementation = forIndirectBufferAndArrayImplementation;
this.machDesc = machDesc;
+ javaCallback = cfg.setFuncToJavaCallbackMap.get(binding.getName());
+ if( null != javaCallback ) {
+ jcbNativeBasename = CodeGenUtils.capitalizeString( javaCallback.setFuncName+javaCallback.simpleCbClazzName.replace("_", "") );
+ jcbCMethodEmitter = new CMethodBindingEmitter(javaCallback.cbFuncBinding,
+ unit, javaPackageName, javaClassName, isOverloadedBinding,
+ isJavaMethodStatic, forImplementingMethodCall,
+ forIndirectBufferAndArrayImplementation, machDesc, configuration);
+ } else {
+ jcbNativeBasename = null;
+ jcbCMethodEmitter = null;
+ }
+
setCommentEmitter(defaultCommentEmitter);
}
@@ -308,10 +327,98 @@ public class CMethodBindingEmitter extends FunctionEmitter {
@Override
protected void emitReturnType() {
+ if( null != javaCallback ) {
+ LOG.log(INFO, "BindCFunc.R.JavaCallback: {0}: {1}", binding.getName(), javaCallback);
+ final String userParamArgName = javaCallback.cbFuncBinding.getArgumentName(javaCallback.userParamIdx);
+ final Type cReturnType = javaCallback.cbFuncBinding.getCReturnType();
+ final JavaType jretType = javaCallback.cbFuncBinding.getJavaReturnType();
+ unit.emitln("typedef struct {");
+ unit.emitln(" jobject cbFunc;");
+ unit.emitln(" jmethodID cbMethodID;");
+ unit.emitln(" jobject userParam;");
+ unit.emitln("} T_"+jcbNativeBasename+";");
+ unit.emitln();
+ // javaCallback.cbFuncCEmitter.emitSignature();
+ unit.emit("static "+cReturnType.getCName()+" func"+jcbNativeBasename+"(");
+ // javaCallback.cbFuncCEmitter.emitArguments();
+ unit.emit(javaCallback.cbFuncBinding.getCParameterList(new StringBuilder(), null).toString());
+ unit.emitln(") {");
+ // javaCallback.cbFuncCEmitter.emitBody();
+ {
+ unit.emitln(" JNIEnv* env = JVMUtil_GetJNIEnv();");
+ unit.emitln(" if( NULL == env ) {");
+ if( !cReturnType.isVoid() ) {
+ unit.emitln(" return 0;");
+ } else {
+ unit.emitln(" return;");
+ }
+ unit.emitln(" }");
+ // javaCallback.cbFuncCEmitter.emitBodyVariableDeclarations();
+ // javaCallback.cbFuncCEmitter.emitBodyUserVariableDeclarations();
+ // javaCallback.cbFuncCEmitter.emitBodyVariablePreCallSetup();
+ jcbCMethodEmitter.emitJavaCallbackBodyCToJavaPreCall(javaCallback);
+
+ // javaCallback.cbFuncCEmitter.emitBodyCallCFunction();
+ unit.emitln(" T_"+jcbNativeBasename+"* cb = (T_"+jcbNativeBasename+"*) "+userParamArgName+";");
+ unit.emitln(" // C Params: "+javaCallback.cbFuncBinding.getCParameterList(new StringBuilder(), null).toString());
+ unit.emitln(" // J Params: "+javaCallback.cbFuncBinding.getJavaParameterList(new StringBuilder()).toString());
+ // unit.emitln(" fprintf(stderr, \"YYY Callback01 user %p, id %ld, msg %s\\n\", cb, id, msg);");
+
+ if( !cReturnType.isVoid() ) {
+ unit.emit(" "+cReturnType.getCName()+" _res = ("+cReturnType.getCName()+") ");
+ } else {
+ unit.emit(" ");
+ }
+ unit.emit("(*env)->Call" + CodeGenUtils.capitalizeString( jretType.getName() ) +"Method(env, cb->cbFunc, cb->cbMethodID, ");
+ // javaCallback.cbFuncCEmitter.emitBodyPassCArguments();
+ if( 0 < jcbCMethodEmitter.emitJavaCallbackBodyPassJavaArguments(javaCallback) ) {
+ unit.emit(", ");
+ }
+ unit.emitln("cb->userParam);");
+
+ // javaCallback.cbFuncCEmitter.emitBodyUserVariableAssignments();
+ // javaCallback.cbFuncCEmitter.emitBodyVariablePostCallCleanup();
+ // javaCallback.cbFuncCEmitter.emitBodyMapCToJNIType(-1 /* return value */, true /* addLocalVar */)
+ if( !cReturnType.isVoid() ) {
+ unit.emitln(" return _res;");
+ }
+ }
+ unit.emitln("}");
+ unit.emitln();
+ }
unit.emit("JNIEXPORT ");
unit.emit(binding.getJavaReturnType().jniTypeName());
unit.emit(" JNICALL");
}
+ /* pp */ int emitJavaCallbackBodyCToJavaPreCall(final JavaCallbackInfo jcbi) {
+ int count = 0;
+ for (int i = 0; i < binding.getNumArguments(); i++) {
+ if( i == jcbi.userParamIdx ) {
+ continue;
+ }
+ if( emitBodyMapCToJNIType(i, true /* addLocalVar */) ) {
+ ++count;
+ }
+ }
+ return count;
+ }
+ /* pp */ int emitJavaCallbackBodyPassJavaArguments(final JavaCallbackInfo jcbi) {
+ int count = 0;
+ boolean needsComma = false;
+ for (int i = 0; i < binding.getNumArguments(); i++) {
+ if( i == jcbi.userParamIdx ) {
+ continue;
+ }
+ if (needsComma) {
+ unit.emit(", ");
+ needsComma = false;
+ }
+ unit.emit( binding.getArgumentName(i) + "_jni" );
+ needsComma = true;
+ ++count;
+ }
+ return count;
+ }
@Override
protected void emitName() {
@@ -385,6 +492,15 @@ public class CMethodBindingEmitter extends FunctionEmitter {
byteOffsetArrayArgName(i));
}
}
+
+ final JavaCallbackInfo jcb = this.javaCallback;
+ if( null != jcb ) {
+ LOG.log(INFO, "BindCFunc.A.JavaCallback: {0}: {1}", binding.getName(), jcb);
+ unit.emit(", jstring jcallbackSignature, jlongArray jnativeUserParam");
+ numEmitted+=2;
+ } else {
+ LOG.log(INFO, "BindCFunc.JavaCallback: {0}: NONE", binding.getName());
+ }
return numEmitted;
}
@@ -396,6 +512,50 @@ public class CMethodBindingEmitter extends FunctionEmitter {
emitBodyVariableDeclarations();
emitBodyUserVariableDeclarations();
emitBodyVariablePreCallSetup();
+ final JavaCallbackInfo jcb = this.javaCallback;
+ if( null != jcb ) {
+ LOG.log(INFO, "BindCFunc.B.JavaCallback: {0}: {1}", binding.getName(), jcb);
+ final String cbFuncArgName = binding.getArgumentName(jcb.setFuncCBParamIdx);
+ final String userParamArgName = binding.getArgumentName(jcb.setFuncUserParamIdx);
+ final String nativeCBFuncVarName = cbFuncArgName+"_native";
+ final String nativeUserParamVarName = userParamArgName+"_native";
+ unit.emitln();
+ unit.emitln(" // JavaCallback handling");
+ unit.emitln(" "+jcb.cbFuncTypeName+" "+nativeCBFuncVarName+";");
+ unit.emitln(" T_"+jcbNativeBasename+"* "+nativeUserParamVarName+";");
+ // unit.emit(", jstring jcallbackSignature, jlongArray jnativeUserParam");
+ unit.emitln(" if( NULL == jnativeUserParam ) { (*env)->FatalError(env, \"Null jnativeUserParam in '"+jcbNativeBasename+"'\"); }");
+ unit.emitln(" const size_t jnativeUserParam_size = (*env)->GetArrayLength(env, jnativeUserParam);");
+ unit.emitln(" if( 1 > jnativeUserParam_size ) { (*env)->FatalError(env, \"nativeUserParam size < 1 in '"+jcbNativeBasename+"'\"); }");
+ unit.emitln(" if( NULL != "+cbFuncArgName+" ) {");
+ unit.emitln(" if( NULL == "+userParamArgName+" ) { (*env)->FatalError(env, \"Null "+userParamArgName+" in '"+jcbNativeBasename+"'\"); }");
+ unit.emitln(" "+nativeUserParamVarName+" = (T_"+jcbNativeBasename+"*) calloc(1, sizeof(T_"+jcbNativeBasename+"));");
+ unit.emitln(" if( NULL == "+nativeUserParamVarName+" ) { (*env)->FatalError(env, \"Can't alloc "+nativeUserParamVarName+" in '"+jcbNativeBasename+"'\"); }");
+ unit.emitln(" "+nativeUserParamVarName+"->cbFunc = (*env)->NewGlobalRef(env, "+cbFuncArgName+");");
+ unit.emitln(" if( NULL == "+nativeUserParamVarName+"->cbFunc ) { (*env)->FatalError(env, \"Failed NewGlobalRef(func) in '"+jcbNativeBasename+"'\"); }");
+ unit.emitln(" "+nativeUserParamVarName+"->userParam = (*env)->NewGlobalRef(env, "+userParamArgName+");");
+ unit.emitln(" if( NULL == "+nativeUserParamVarName+"->userParam ) { (*env)->FatalError(env, \"Failed NewGlobalRef(userParam) in '"+jcbNativeBasename+"'\"); }");
+ unit.emitln(" {");
+ unit.emitln(" jclass cbClazz = (*env)->GetObjectClass(env, "+nativeUserParamVarName+"->cbFunc);");
+ unit.emitln(" if( NULL == cbClazz ) { (*env)->FatalError(env, \"Failed GetObjectClass in '"+jcbNativeBasename+"'\"); }");
+ unit.emitln(" const char* callbackSignature = (*env)->GetStringUTFChars(env, jcallbackSignature, (jboolean*)NULL);");
+ unit.emitln(" if( NULL == callbackSignature ) { (*env)->FatalError(env, \"Failed callbackSignature in '"+jcbNativeBasename+"'\"); }");
+ unit.emitln(" "+nativeUserParamVarName+"->cbMethodID = (*env)->GetMethodID(env, cbClazz, \"callback\", callbackSignature);");
+ unit.emitln(" (*env)->ReleaseStringUTFChars(env, jcallbackSignature, callbackSignature);");
+ unit.emitln(" if( NULL == "+nativeUserParamVarName+"->cbMethodID ) { (*env)->FatalError(env, \"Failed GetMethodID in '"+jcbNativeBasename+"'\"); }");
+ unit.emitln(" }");
+ unit.emitln(" "+nativeCBFuncVarName+" = func"+jcbNativeBasename+";");
+ unit.emitln(" } else {");
+ unit.emitln(" "+nativeCBFuncVarName+" = NULL;");
+ unit.emitln(" "+nativeUserParamVarName+" = NULL;");
+ unit.emitln(" }");
+ unit.emitln(" {");
+ unit.emitln(" jlong v = (jlong) (intptr_t) "+nativeUserParamVarName+";");
+ unit.emitln(" (*env)->SetLongArrayRegion(env, jnativeUserParam, 0, (jsize)1, &v);");
+ // unit.emitln(" fprintf(stderr, \"YYY MessageCallback01 user %p -> native %p\\n\", "+userParamArgName+", "+nativeUserParamVarName+");");
+ unit.emitln(" }");
+ unit.emitln();
+ }
emitBodyCallCFunction();
emitBodyUserVariableAssignments();
emitBodyVariablePostCallCleanup();
@@ -404,6 +564,19 @@ public class CMethodBindingEmitter extends FunctionEmitter {
}
unit.emitln("}");
unit.emitln();
+ if( null != jcb ) {
+ unit.emitln("JNIEXPORT void JNICALL");
+ unit.emit(JavaEmitter.getJNIMethodNamePrefix(getJavaPackageName(), getJavaClassName()));
+ unit.emitln("_release"+getInterfaceName()+"Impl(JNIEnv *env, jobject _unused, jlong jnativeUserParam) {");
+ unit.emitln(" T_"+jcbNativeBasename+"* nativeUserParam = (T_"+jcbNativeBasename+"*) (intptr_t) jnativeUserParam;");
+ unit.emitln(" if( NULL != nativeUserParam ) {");
+ unit.emitln(" (*env)->DeleteGlobalRef(env, nativeUserParam->cbFunc);");
+ unit.emitln(" (*env)->DeleteGlobalRef(env, nativeUserParam->userParam);");
+ unit.emitln(" free(nativeUserParam);");
+ unit.emitln(" }");
+ unit.emitln("}");
+ unit.emitln();
+ }
}
protected void emitBodyVariableDeclarations() {
@@ -953,6 +1126,10 @@ public class CMethodBindingEmitter extends FunctionEmitter {
} else {
if (javaArgType.isString()) { unit.emit(STRING_CHARS_PREFIX); }
unit.emit(binding.getArgumentName(i));
+ if( null != this.javaCallback &&
+ ( i == this.javaCallback.setFuncCBParamIdx || i == this.javaCallback.setFuncUserParamIdx ) ) {
+ unit.emit("_native");
+ }
}
}
}
@@ -1212,8 +1389,15 @@ public class CMethodBindingEmitter extends FunctionEmitter {
final StringBuilder buf = new StringBuilder();
buf.append(JavaEmitter.jniMangle(getImplName()));
buf.append(getImplSuffix());
- buf.append("__");
- return getJNIMangledArgs(binding, forIndirectBufferAndArrayImplementation, buf).toString();
+ if( null == this.javaCallback ) {
+ buf.append("__");
+ getJNIMangledArgs(binding, forIndirectBufferAndArrayImplementation, buf);
+ if( null != this.javaCallback ) {
+ getJNIMangledArg(String.class, buf, false); // to account for the additional 'jstring jcallbackSignature' parameter
+ getJNIMangledArg(long[].class, buf, false); // to account for the additional 'long[] nativeUserParam' parameter
+ }
+ }
+ return buf.toString();
}
/**
diff --git a/src/java/com/jogamp/gluegen/JavaConfiguration.java b/src/java/com/jogamp/gluegen/JavaConfiguration.java
index 3faddcd..17ac547 100644
--- a/src/java/com/jogamp/gluegen/JavaConfiguration.java
+++ b/src/java/com/jogamp/gluegen/JavaConfiguration.java
@@ -149,8 +149,26 @@ public class JavaConfiguration {
* converted to String args; value is List of Integer argument indices
*/
private final Map<String, List<Integer>> argumentsAreString = new HashMap<String, List<Integer>>();
- private final Map<String, Integer> javaCallbackUserParams = new HashMap<String, Integer>();
- private final List<String> javaCallbackList = new ArrayList<String>();
+
+ /** JavaCallback configuration definition (static) */
+ public static class JavaCallbackDef {
+ final String setFuncName;
+ final String cbFuncTypeName;
+ final int userParamIdx;
+ JavaCallbackDef(final String setFuncName, final String cbFuncTypeName, final int userParamIdx) {
+ this.setFuncName = setFuncName;
+ this.cbFuncTypeName = cbFuncTypeName;
+ this.userParamIdx = userParamIdx;
+ }
+ @Override
+ public String toString() {
+ return String.format("JavaCallbackDef[set %s, cb %s, userParamIdx %d]",
+ setFuncName, cbFuncTypeName, userParamIdx);
+ }
+ }
+ private final List<JavaCallbackDef> javaCallbackList = new ArrayList<JavaCallbackDef>();
+ private final Map<String, JavaCallbackDef> javaCallbackSetFuncToDef = new HashMap<String, JavaCallbackDef>();
+
private final Set<String> extendedIntfSymbolsIgnore = new HashSet<String>();
private final Set<String> extendedIntfSymbolsOnly = new HashSet<String>();
private final Set<String> extendedImplSymbolsIgnore = new HashSet<String>();
@@ -539,30 +557,21 @@ public class JavaConfiguration {
return returnsStringOnly.contains(functionName);
}
- public List<String> getJavaCallbackList() {
+ /** Returns the list of all configured JavaCallback definitions. */
+ public List<JavaCallbackDef> getJavaCallbackList() {
return javaCallbackList;
}
- /** Returns an <code>Integer</code> index of the <code>void*</code>
- user-param argument that should be converted to <code>Object</code>s for the Java Callback. Returns null if there are no
- such hints for the given function alias symbol. */
- public boolean isJavaCallback(final AliasedSymbol symbol) {
- return -2 < javaCallbackUserParamIdx(symbol);
- }
-
- /** Returns an <code>Integer</code> index of the <code>void*</code>
- user-param argument that should be converted to <code>Object</code>s for the Java Callback. Returns -2 if there are no
- such hints for the given function alias symbol. */
- public int javaCallbackUserParamIdx(final AliasedSymbol symbol) {
+ /** Returns the configured JavaCallback definition mapped to the JavaCallback-Set-Function name. */
+ public JavaCallbackDef javaCallbackSetFuncToDef(final AliasedSymbol symbol) {
final String name = symbol.getName();
final Set<String> aliases = symbol.getAliasedNames();
- Integer res = javaCallbackUserParams.get(name);
+ JavaCallbackDef res = javaCallbackSetFuncToDef.get(name);
if( null == res ) {
- res = oneInMap(javaCallbackUserParams, aliases);
+ res = oneInMap(javaCallbackSetFuncToDef, aliases);
}
- LOG.log(INFO, getASTLocusTag(symbol), "JavaCallbackDef: {0} -> {1}", symbol, res);
- return null != res ? res.intValue() : -2;
+ return res;
}
/**
@@ -1615,15 +1624,12 @@ public class JavaConfiguration {
protected void readJavaCallbackDef(final StringTokenizer tok, final String filename, final int lineNo) {
try {
- final String name = tok.nextToken();
- final Integer idx;
- if( tok.hasMoreTokens() ) {
- idx = Integer.valueOf(tok.nextToken());
- } else {
- idx = Integer.valueOf(-2);
- }
- javaCallbackUserParams.put(name, idx);
- javaCallbackList.add(name);
+ final String setFuncName = tok.nextToken();
+ final String cbFuncTypeName = tok.nextToken();
+ final Integer userParamIdx = Integer.valueOf(tok.nextToken());
+ final JavaCallbackDef jcd = new JavaCallbackDef(setFuncName, cbFuncTypeName, userParamIdx);
+ javaCallbackList.add(jcd);
+ javaCallbackSetFuncToDef.put(setFuncName, jcd);
} catch (final NoSuchElementException e) {
throw new RuntimeException("Error parsing \"JavaCallbackDef\" command at line " + lineNo +
" in file \"" + filename + "\"", e);
@@ -2233,53 +2239,76 @@ public class JavaConfiguration {
}
/**
- * JavaCallback information, produced by {@link JavaEmitter#beginFunctions(TypeDictionary, TypeDictionary, Map)}
+ * JavaCallback compile time information, produced by {@link JavaEmitter#beginFunctions(TypeDictionary, TypeDictionary, Map)}
* from {@link Type#isFunctionPointer() function-pointer} {@link Type}s mapped to {@link JavaConfiguration#getJavaCallbackList()} names via {@link TypeDictionary} (typedef).
* @see JavaConfiguration#funcPtrTypeToJavaCallbackMap
- * @see JavaConfiguration#bindingToJavaCallbackMap
+ * @see JavaConfiguration#setFuncToJavaCallbackMap
*/
- public static class JavaCallback {
- final String funcName;
- final String simpleClazzName;
- final String fqClazzName;
- final String methodSignature;
- final FunctionType func;
+ public static class JavaCallbackInfo {
+ final String setFuncName;
+ final String cbFuncTypeName;
+ final String simpleCbClazzName;
+ final String fqCbClazzName;
+ final String cbMethodSignature;
+ final FunctionType cbFuncType;
+ final MethodBinding cbFuncBinding;
final int userParamIdx;
final Type userParamType;
final String userParamName;
-
- public JavaCallback(final String funcName, final String simpleClazzName, final String fqClazzName, final String methodSignature,
- final FunctionType func, final int userParamIdx) {
- this.funcName = funcName;
- this.simpleClazzName = simpleClazzName;
- this.fqClazzName = fqClazzName;
- this.methodSignature = methodSignature;
- this.func = func;
+ boolean setFuncProcessed;
+ int setFuncCBParamIdx;
+ int setFuncUserParamIdx;
+
+ public JavaCallbackInfo(final String setFuncName, final String cbFuncTypeName, final String simpleClazzName, final String fqClazzName, final String methodSignature,
+ final FunctionType cbFuncType, final MethodBinding cbFuncBinding, final int userParamIdx) {
+ this.setFuncName = setFuncName;
+ this.cbFuncTypeName = cbFuncTypeName;
+ this.simpleCbClazzName = simpleClazzName;
+ this.fqCbClazzName = fqClazzName;
+ this.cbMethodSignature = methodSignature;
+ this.cbFuncType = cbFuncType;
+ this.cbFuncBinding = cbFuncBinding;
int paramIdx = -2;
Type paramType = null;
String paramName = null;
- if( 0 <= userParamIdx && userParamIdx < func.getNumArguments() ) {
- final Type t = func.getArgumentType(userParamIdx);
- if( null != t && t.isPointer() && t.getTargetType().isVoid() ) {
- // OK 'void*'
+ if( 0 <= userParamIdx && userParamIdx < cbFuncType.getNumArguments() ) {
+ final Type t = cbFuncType.getArgumentType(userParamIdx);
+ if( null != t && t.isPointer() ) {
+ // OK '<something>*'
paramIdx = userParamIdx;
- paramName = func.getArgumentName(userParamIdx);
- paramType = t;
+ paramName = cbFuncType.getArgumentName(userParamIdx);
+ paramType = t.getTargetType();
}
}
this.userParamIdx = paramIdx;
this.userParamType = paramType;
this.userParamName = paramName;
+ this.setFuncProcessed = false;
+ this.setFuncCBParamIdx = -1;
+ this.setFuncUserParamIdx = -1;
+ }
+
+ public void setFuncProcessed(final int cbParamIdx, final int userParamIdx) {
+ if( !setFuncProcessed ) {
+ if( 0 <= cbParamIdx && 0 <= userParamIdx ) {
+ setFuncProcessed = true;
+ setFuncCBParamIdx = cbParamIdx;
+ setFuncUserParamIdx = userParamIdx;
+ } else {
+ setFuncCBParamIdx = -1;
+ setFuncUserParamIdx = -1;
+ }
+ }
}
@Override
public String toString() {
- return String.format("JavaCallback[%s, %s%s, userParam[idx %d, '%s', %s], %s]", funcName, fqClazzName, methodSignature,
- userParamIdx, userParamName, userParamType.getSignature(null).toString(), func.toString(funcName, false, true));
+ return String.format("JavaCallbackInfo[set %s(ok %b, cbIdx %d, upIdx %d), cb %s%s, userParam[idx %d, '%s', %s], %s]",
+ setFuncName, setFuncProcessed, setFuncCBParamIdx, setFuncUserParamIdx,
+ cbFuncTypeName, cbMethodSignature,
+ userParamIdx, userParamName, userParamType.getSignature(null).toString(), cbFuncType.toString(cbFuncTypeName, false, true));
}
}
- /** Mapped function-pointer type name to {@link JavaCallback} */
- /* pp */ final Map<String, JavaCallback> funcPtrTypeToJavaCallbackMap = new HashMap<String, JavaCallback>();
- /** Mapped binding name to {@link JavaCallback} */
- /* pp */ final Map<String, JavaCallback> bindingToJavaCallbackMap = new HashMap<String, JavaCallback>();
+ /** Mapped binding name to {@link JavaCallbackInfo} */
+ /* pp */ final Map<String, JavaCallbackInfo> setFuncToJavaCallbackMap = new HashMap<String, JavaCallbackInfo>();
}
diff --git a/src/java/com/jogamp/gluegen/JavaEmitter.java b/src/java/com/jogamp/gluegen/JavaEmitter.java
index 41c5203..7dad237 100644
--- a/src/java/com/jogamp/gluegen/JavaEmitter.java
+++ b/src/java/com/jogamp/gluegen/JavaEmitter.java
@@ -75,6 +75,9 @@ import com.jogamp.common.os.DynamicLookupHelper;
import com.jogamp.common.os.MachineDataInfo;
import com.jogamp.common.util.ArrayHashMap;
import com.jogamp.gluegen.ASTLocusTag.ASTLocusTagProvider;
+import com.jogamp.gluegen.FunctionEmitter.EmissionModifier;
+import com.jogamp.gluegen.JavaConfiguration.JavaCallbackDef;
+import com.jogamp.gluegen.JavaConfiguration.JavaCallbackInfo;
import com.jogamp.gluegen.Logging.LoggerIf;
import com.jogamp.gluegen.cgram.types.AliasedSymbol;
import com.jogamp.gluegen.cgram.types.ArrayType;
@@ -363,13 +366,13 @@ public class JavaEmitter implements GlueEmitter {
if ( ( cfg.allStatic() || cfg.emitInterface() ) && !cfg.structsOnly() ) {
javaUnit().emitln();
if( !cfg.getJavaCallbackList().isEmpty() ) {
- final List<String> javaCallbacks = cfg.getJavaCallbackList();
- for(final String javaCallback : javaCallbacks) {
- final Type funcPtr = typedefDictionary.get(javaCallback);
+ final List<JavaCallbackDef> javaCallbacks = cfg.getJavaCallbackList();
+ for(final JavaCallbackDef jcbd : javaCallbacks) {
+ final Type funcPtr = typedefDictionary.get(jcbd.cbFuncTypeName);
if( null != funcPtr && funcPtr.isFunctionPointer() ) {
- generateJavaCallbackCode(javaCallback, funcPtr.getTargetFunction());
+ generateJavaCallbackCode(jcbd, funcPtr.getTargetFunction());
} else {
- LOG.log(WARNING, "JavaCallback '{0}' function-pointer type not available", javaCallback);
+ LOG.log(WARNING, "JavaCallback '{0}' function-pointer type not available", jcbd);
}
}
}
@@ -1453,27 +1456,33 @@ public class JavaEmitter implements GlueEmitter {
}
}
- private void generateJavaCallbackCode(final String funcName, final FunctionType funcType) {
+ private void generateJavaCallbackCode(final JavaCallbackDef jcbd, final FunctionType funcType) {
final FunctionSymbol funcSym = new FunctionSymbol("callback", funcType);
- funcSym.addAliasedName(funcName);
- final int userParamIdx = cfg.javaCallbackUserParamIdx(funcSym);
- LOG.log(INFO, "JavaCallback: fSym {0}, userParam {1}", funcSym.getAliasedString(), userParamIdx);
+ funcSym.addAliasedName(jcbd.cbFuncTypeName);
+ LOG.log(INFO, "JavaCallback: fSym {0}, {1}", funcSym.getAliasedString(), jcbd);
- final String simpleClazzName = capitalizeString(funcName);
+ final String simpleClazzName = CodeGenUtils.capitalizeString(jcbd.cbFuncTypeName);
final String fqClazzName = cfg.packageName()+"."+cfg.className()+"."+simpleClazzName;
final StringBuilder methodSignature = new StringBuilder();
- javaUnit.emitln(" /** JavaCallback interface: "+funcName+" -> "+funcType.toString(funcName, false, true)+" */");
+ javaUnit.emitln(" /** JavaCallback interface: "+jcbd.cbFuncTypeName+" -> "+funcType.toString(jcbd.cbFuncTypeName, false, true)+" */");
javaUnit.emitln(" public static interface "+simpleClazzName+" {");
- generateFunctionInterfaceCode(javaUnit, funcSym, userParamIdx, methodSignature);
+ final List<MethodBinding> mbs = generateFunctionInterfaceCode(javaUnit, funcSym, jcbd.userParamIdx, methodSignature);
javaUnit.emitln(" }");
javaUnit.emitln();
-
- final JavaConfiguration.JavaCallback jcb = new JavaConfiguration.JavaCallback(funcName, simpleClazzName, fqClazzName,
- methodSignature.toString(), funcType, userParamIdx);
- cfg.funcPtrTypeToJavaCallbackMap.put(funcName, jcb);
- LOG.log(INFO, "JavaCallback: Added {0}", jcb);
+ if( 1 != mbs.size() ) {
+ throw new UnsupportedOperationException("Multiple bindings generated where only 1 is allowed for func "+funcType.toString(jcbd.cbFuncTypeName, false, true));
+ }
+ final MethodBinding mb = mbs.get(0);
+ if( !mb.getJavaReturnType().isVoid() && !mb.getJavaReturnType().isPrimitive() ) {
+ throw new UnsupportedOperationException("Non void or non-primitive callback return types not suppored. Java "+
+ mb.getJavaReturnType()+", func "+funcType.toString(jcbd.cbFuncTypeName, false, true));
+ }
+ final JavaCallbackInfo jcbi = new JavaCallbackInfo(jcbd.setFuncName, jcbd.cbFuncTypeName, simpleClazzName, fqClazzName,
+ methodSignature.toString(), funcType, mb, jcbd.userParamIdx);
+ cfg.setFuncToJavaCallbackMap.put(jcbd.setFuncName, jcbi);
+ LOG.log(INFO, "JavaCallbackInfo: Added {0}", jcbi);
}
- private void generateFunctionInterfaceCode(final JavaCodeUnit javaUnit, final FunctionSymbol funcSym, final int userParamIdx, final StringBuilder methodSignature) {
+ private List<MethodBinding> generateFunctionInterfaceCode(final JavaCodeUnit javaUnit, final FunctionSymbol funcSym, final int userParamIdx, final StringBuilder methodSignature) {
// Emit method call and associated native code
MethodBinding mb = bindFunction(funcSym, true /* forInterface */, machDescJava, null, null);
@@ -1527,6 +1536,7 @@ public class JavaEmitter implements GlueEmitter {
emitter.addModifier(JavaMethodBindingEmitter.PUBLIC);
emitter.emit();
}
+ return bindings;
}
private void generateFunctionPointerCode(final Set<MethodBinding> methodBindingSet,
@@ -2981,7 +2991,7 @@ public class JavaEmitter implements GlueEmitter {
if (cfg.emitImpl()) {
if( !cfg.getJavaCallbackList().isEmpty() && null == cfg.libraryOnLoadName() ) {
- LOG.log(WARNING, "JavaCallback used, but no 'LibraryOnLoad' basename specified for JNI_OnLoad(..). Exactly one native code-unit for the library must specify with 'LibraryOnLoad' basename");
+ LOG.log(WARNING, "JavaCallback used, but no 'LibraryOnLoad' basename specified for JNI_OnLoad(..). Exactly one native code-unit for the library must specify 'LibraryOnLoad' basename");
}
cUnit().emitHeader(cfg.libraryOnLoadName(), getImplPackageName(), cfg.implClassName(), cfg.customCCode());
}
@@ -2991,7 +3001,6 @@ public class JavaEmitter implements GlueEmitter {
" cfg.emitImpl()=" + cfg.emitImpl() + " cfg.emitInterface()=" + cfg.emitInterface(),
e);
}
-
}
/**
@@ -3061,7 +3070,8 @@ public class JavaEmitter implements GlueEmitter {
// converted from byte[] or short[] to String
final List<JavaType> javaArgumentTypes = new ArrayList<JavaType>();
final List<Integer> stringArgIndices = cfg.stringArguments(sym);
- JavaConfiguration.JavaCallback javaCallback = null;
+ final JavaCallbackInfo jcbi = cfg.setFuncToJavaCallbackMap.get( sym.getName() );
+ int jcbiSetFuncCBParamIdx=-1, jcbiSetFuncUserParamIdx=-1;
for (int i = 0; i < sym.getNumArguments(); i++) {
final Type cArgType = sym.getArgumentType(i);
@@ -3070,29 +3080,18 @@ public class JavaEmitter implements GlueEmitter {
// System.out.println("C arg type -> \"" + cArgType + "\"" );
// System.out.println(" Java -> \"" + mappedType + "\"" );
- final boolean isJavaCallbackArg;
- if( null == javaCallback ) {
- final JavaConfiguration.JavaCallback jcb = cfg.funcPtrTypeToJavaCallbackMap.get( cArgType.getName() );
- if( null != jcb && null != jcb.userParamName ) {
- isJavaCallbackArg = true;
- javaCallback = jcb;
- cfg.bindingToJavaCallbackMap.put(sym.getName(), jcb);
- LOG.log(INFO, "BindFunc.JavaCallback: {0}: {1}, {2}", sym.getName(), sym.getType().toString(sym.getName(), false, true), jcb);
- } else {
- isJavaCallbackArg = false;
- }
- } else {
- isJavaCallbackArg = false;
- }
-
- if( isJavaCallbackArg ) {
+ if( null != jcbi && jcbi.cbFuncTypeName.equals( cArgType.getName() ) &&
+ ( !jcbi.setFuncProcessed || i == jcbi.setFuncCBParamIdx ) )
+ {
// Replace JavaCallback type with generated interface name
- mappedType = JavaType.createForNamedClass( javaCallback.fqClazzName );
- } else if( null != javaCallback && null != javaCallback.userParamName &&
- javaCallback.userParamName.equals( cArgName ) &&
- cArgType.isPointer() && cArgType.getTargetType().isVoid() )
+ jcbiSetFuncCBParamIdx=i;
+ mappedType = JavaType.createForNamedClass( jcbi.fqCbClazzName );
+ } else if( null != jcbi && jcbi.userParamName.equals( cArgName ) &&
+ ( !jcbi.setFuncProcessed || i == jcbi.setFuncUserParamIdx ) &&
+ cArgType.isPointer() && jcbi.userParamType.equals( cArgType.getTargetType() ) )
{
- // Replace optional userParam argument 'void*' with Object
+ // Replace optional userParam argument '<userParamType>*' with Object
+ jcbiSetFuncUserParamIdx=i;
mappedType = JavaType.forObjectClass();
} else if (stringArgIndices != null && stringArgIndices.contains(i)) {
// Take into account any ArgumentIsString configuration directives that apply
@@ -3124,6 +3123,10 @@ public class JavaEmitter implements GlueEmitter {
javaArgumentTypes.add(mappedType);
//System.out.println("During binding of [" + sym + "], added mapping from C type: " + cArgType + " to Java type: " + mappedType);
}
+ if( null != jcbi ) {
+ jcbi.setFuncProcessed(jcbiSetFuncCBParamIdx, jcbiSetFuncUserParamIdx);
+ LOG.log(INFO, "BindFunc.JavaCallback: {0}: {1}, {2}", sym.getName(), sym.getType().toString(sym.getName(), false, true), jcbi);
+ }
final MethodBinding mb = new MethodBinding(sym, delegationImplName,
javaReturnType, javaArgumentTypes,
containingType, containingCType);
@@ -3272,19 +3275,6 @@ public class JavaEmitter implements GlueEmitter {
}
}
- /**
- * Converts first letter to upper case.
- */
- private static String capitalizeString(final String string) {
- return Character.toUpperCase(string.charAt(0)) + string.substring(1);
- }
- /**
- * Converts first letter to lower case.
- */
- private static String decapitalizeString(final String string) {
- return Character.toLowerCase(string.charAt(0)) + string.substring(1);
- }
-
private static final String optStringCharsetCode =
" private static Charset _charset = StandardCharsets.UTF_8;\n" +
"\n"+
diff --git a/src/java/com/jogamp/gluegen/JavaMethodBindingEmitter.java b/src/java/com/jogamp/gluegen/JavaMethodBindingEmitter.java
index 74e18e5..1469b4f 100644
--- a/src/java/com/jogamp/gluegen/JavaMethodBindingEmitter.java
+++ b/src/java/com/jogamp/gluegen/JavaMethodBindingEmitter.java
@@ -40,6 +40,7 @@
package com.jogamp.gluegen;
+import com.jogamp.gluegen.JavaConfiguration.JavaCallbackInfo;
import com.jogamp.gluegen.cgram.HeaderParser;
import com.jogamp.gluegen.cgram.types.AliasedSymbol;
import com.jogamp.gluegen.cgram.types.ArrayType;
@@ -94,6 +95,8 @@ public class JavaMethodBindingEmitter extends FunctionEmitter {
private String returnedArrayLengthExpression;
private boolean returnedArrayLengthExpressionOnlyForComments = false;
+ private final JavaCallbackInfo javaCallback;
+
// A suffix used to create a temporary outgoing array of Buffers to
// represent an array of compound type wrappers
private static final String COMPOUND_ARRAY_SUFFIX = "_buf_array_copy";
@@ -131,6 +134,7 @@ public class JavaMethodBindingEmitter extends FunctionEmitter {
} else {
setCommentEmitter(defaultInterfaceCommentEmitter);
}
+ javaCallback = cfg.setFuncToJavaCallbackMap.get(binding.getName());
// !forImplementingMethodCall && !isInterface
}
@@ -152,6 +156,7 @@ public class JavaMethodBindingEmitter extends FunctionEmitter {
epilogue = arg.epilogue;
returnedArrayLengthExpression = arg.returnedArrayLengthExpression;
returnedArrayLengthExpressionOnlyForComments = arg.returnedArrayLengthExpressionOnlyForComments;
+ javaCallback = arg.javaCallback;
}
public boolean isNativeMethod() { return isNativeMethod; }
@@ -411,17 +416,17 @@ public class JavaMethodBindingEmitter extends FunctionEmitter {
}
}
if( hasModifier(JavaMethodBindingEmitter.NATIVE) &&
- null != cfg.bindingToJavaCallbackMap.get(binding.getName()) ) {
+ null != javaCallback )
+ {
if (needComma) {
unit.emit(", ");
}
- unit.emit("String callbackSignature");
+ unit.emit("String callbackSignature, long[/*1*/] nativeUserParam");
++numEmitted;
}
return numEmitted;
}
-
protected String getNativeImplMethodName() {
return binding.getImplName() + ( useNIODirectOnly ? "0" : "1" );
}
@@ -468,6 +473,40 @@ public class JavaMethodBindingEmitter extends FunctionEmitter {
}
unit.emitln(" }");
}
+ if( null != javaCallback && !isPrivateNativeMethod ) {
+ unit.emitln();
+ final String userParamArgName = binding.getArgumentName(javaCallback.setFuncUserParamIdx);
+ unit.emit(" public boolean is"+getInterfaceName()+"Mapped(final Object "+userParamArgName+")");
+ if( isInterface() ) {
+ unit.emitln(";");
+ } else {
+ unit.emitln(" {");
+ unit.emitln(" return null != "+javaCallback.cbFuncTypeName+"UsrMap.get("+userParamArgName+");");
+ unit.emitln(" }");
+ unit.emitln(" private final void release"+getInterfaceName()+"(final Object "+userParamArgName+") {");
+ unit.emitln(" final "+javaCallback.cbFuncTypeName+"UserParam v = "+javaCallback.cbFuncTypeName+"UsrMap.remove("+userParamArgName+");");
+ // unit.emitln(" System.err.println(\"ZZZ Release v \"+v+\", v.nativeParam 0x\"+Long.toHexString(null!=v?v.nativeParam:0));");
+ unit.emitln(" if( null != v ) {");
+ unit.emitln(" release"+getInterfaceName()+"Impl(v.nativeParam);");
+ unit.emitln(" }");
+ unit.emitln(" }");
+ unit.emitln(" private static class "+javaCallback.cbFuncTypeName+"UserParam {");
+ unit.emitln(" @SuppressWarnings(\"unused\")");
+ unit.emitln(" final "+javaCallback.cbFuncTypeName+" func;");
+ unit.emitln(" @SuppressWarnings(\"unused\")");
+ unit.emitln(" final Object param;");
+ unit.emitln(" @SuppressWarnings(\"unused\")");
+ unit.emitln(" final long nativeParam;");
+ unit.emitln(" "+javaCallback.cbFuncTypeName+"UserParam("+javaCallback.cbFuncTypeName+" func, Object param, long nativeParam) {");
+ unit.emitln(" this.func = func;");
+ unit.emitln(" this.param = param;");
+ unit.emitln(" this.nativeParam = nativeParam;");
+ unit.emitln(" }");
+ unit.emitln(" }");
+ unit.emitln(" private final java.util.Map<Object, "+javaCallback.cbFuncTypeName+"UserParam> "+javaCallback.cbFuncTypeName+"UsrMap = new java.util.HashMap<Object, "+javaCallback.cbFuncTypeName+"UserParam>();");
+ unit.emitln(" private native void release"+getInterfaceName()+"Impl(long nativeUserParam);");
+ }
+ }
}
protected void emitPrologueOrEpilogue(final List<String> code) {
@@ -579,11 +618,14 @@ public class JavaMethodBindingEmitter extends FunctionEmitter {
protected void emitReturnVariableSetupAndCall(final MethodBinding binding) {
- unit.emit(" ");
final JavaType returnType = binding.getJavaReturnType();
boolean needsResultAssignment = false;
+ if( null != javaCallback ) {
+ unit.emitln(" final long[] nativeUserParam = { 0 };");
+ }
if (!returnType.isVoid()) {
+ unit.emit(" ");
if (returnType.isCompoundTypeWrapper() ||
returnType.isNIOBuffer()) {
unit.emitln("final ByteBuffer _res;");
@@ -612,6 +654,17 @@ public class JavaMethodBindingEmitter extends FunctionEmitter {
emitCall(binding);
unit.emitln();
+ if( null != javaCallback ) {
+ final String funcArgName = binding.getArgumentName(javaCallback.setFuncCBParamIdx);
+ final String userParamArgName = binding.getArgumentName(javaCallback.setFuncUserParamIdx);
+ // unit.emitln(" System.err.println(\"ZZZ returned nativeUserParam \"+nativeUserParam[0]);");
+ unit.emitln(" if( 0 == nativeUserParam[0] ) {");
+ unit.emitln(" release"+getInterfaceName()+"("+userParamArgName+");");
+ unit.emitln(" } else {");
+ unit.emitln(" "+javaCallback.cbFuncTypeName+"UsrMap.put("+userParamArgName+", new "+javaCallback.cbFuncTypeName+"UserParam("+funcArgName+", "+userParamArgName+", nativeUserParam[0]));");
+ unit.emitln(" }");
+ }
+
emitPostCallCleanup(binding);
emitPrologueOrEpilogue(epilogue);
if (needsResultAssignment) {
@@ -733,12 +786,11 @@ public class JavaMethodBindingEmitter extends FunctionEmitter {
needComma = true;
++numArgsEmitted;
}
- final JavaConfiguration.JavaCallback jcb = cfg.bindingToJavaCallbackMap.get(binding.getName());
- if( null != jcb ) {
+ if( null != javaCallback ) {
if (needComma) {
unit.emit(", ");
}
- unit.emit("\"" + jcb.methodSignature + "\"");
+ unit.emit("\"" + javaCallback.cbMethodSignature + "\", nativeUserParam");
++numArgsEmitted;
}
return numArgsEmitted;
diff --git a/src/junit/com/jogamp/gluegen/test/junit/generation/BaseClass.java b/src/junit/com/jogamp/gluegen/test/junit/generation/BaseClass.java
index ae47b43..5871fc0 100644
--- a/src/junit/com/jogamp/gluegen/test/junit/generation/BaseClass.java
+++ b/src/junit/com/jogamp/gluegen/test/junit/generation/BaseClass.java
@@ -1086,7 +1086,7 @@ public class BaseClass extends SingletonJunitCase {
}
}
- void assertAPTR(final long expected, final long actual) {
+ public static void assertAPTR(final long expected, final long actual) {
System.err.println("0x"+Long.toHexString(expected)+" == 0x"+Long.toHexString(actual));
if (Platform.is32Bit()) {
int exp32;
diff --git a/src/junit/com/jogamp/gluegen/test/junit/generation/Test4JavaCallback.java b/src/junit/com/jogamp/gluegen/test/junit/generation/Test4JavaCallback.java
new file mode 100644
index 0000000..1baad3f
--- /dev/null
+++ b/src/junit/com/jogamp/gluegen/test/junit/generation/Test4JavaCallback.java
@@ -0,0 +1,143 @@
+/**
+ * Copyright 2023 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+
+package com.jogamp.gluegen.test.junit.generation;
+
+import java.io.IOException;
+
+import com.jogamp.common.os.NativeLibrary;
+import com.jogamp.gluegen.test.junit.generation.Bindingtest2.T2_CallbackFunc01;
+import com.jogamp.gluegen.test.junit.generation.impl.Bindingtest2Impl;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.junit.FixMethodOrder;
+import org.junit.runners.MethodSorters;
+
+/**
+ * Test {@link Bindingtest2} with {@link T2_PointerStorage} instance and pointer pointer..
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class Test4JavaCallback extends BaseClass {
+ static NativeLibrary dynamicLookupHelper;
+
+ /**
+ * Verifies loading of the new library.
+ */
+ @BeforeClass
+ public static void chapter__TestLoadLibrary() throws Exception {
+ BindingJNILibLoader.loadBindingtest2();
+ dynamicLookupHelper = NativeLibrary.open("test2", false, false, Test2FuncPtr.class.getClassLoader(), true);
+ Assert.assertNotNull("NativeLibrary.open(test2) failed", dynamicLookupHelper);
+
+ Bindingtest2Impl.resetProcAddressTable(dynamicLookupHelper);
+ }
+
+ /**
+ * Verifies unloading of the new library.
+ */
+ @AfterClass
+ public static void chapter0XTestUnloadLibrary() throws Exception {
+ Assert.assertNotNull(dynamicLookupHelper);
+ dynamicLookupHelper.close();
+ dynamicLookupHelper = null;
+ }
+
+ /**
+ * Test Bindingtest2 with T2_CallbackFunc JavaCallback
+ */
+ @Test
+ public void chapter01() throws Exception {
+ final Bindingtest2 bt2 = new Bindingtest2Impl();
+
+ final long[] id_res = { -1 };
+ final String[] msg_res = { null };
+ final T2_CallbackFunc01 myCallback = new T2_CallbackFunc01() {
+ @Override
+ public void callback(final long id, final String msg, final Object userParam) {
+ final MyUserParam myUserParam = (MyUserParam)userParam;
+ id_res[0] = id + myUserParam.i;
+ msg_res[0] = msg;
+ myUserParam.j += id_res[0];
+ System.err.println("chapter10.myCallback: "+id+", '"+msg+"'");
+ }
+ };
+ final MyUserParam myUserParam = new MyUserParam(10);
+ Assert.assertEquals(10, myUserParam.i);
+ Assert.assertEquals( 0, myUserParam.j);
+ Assert.assertEquals(false, bt2.isMessageCallback01Mapped(myUserParam));
+
+ bt2.MessageCallback01(myCallback, myUserParam);
+ Assert.assertEquals(true, bt2.isMessageCallback01Mapped(myUserParam));
+ Assert.assertEquals(-1, id_res[0]);
+ Assert.assertEquals(null, msg_res[0]);
+ Assert.assertEquals(10, myUserParam.i);
+ Assert.assertEquals( 0, myUserParam.j);
+
+ {
+ final String msgNo1 = "My First JavaCallback message";
+ bt2.InjectMessageCallback01(404, msgNo1);
+ Assert.assertEquals(404+10, id_res[0]);
+ Assert.assertEquals(msgNo1, msg_res[0]);
+ Assert.assertEquals( 10, myUserParam.i);
+ Assert.assertEquals(404+10, myUserParam.j);
+ }
+ final String msgNo2 = "My Second JavaCallback message";
+ {
+ bt2.InjectMessageCallback01( 42, msgNo2);
+ Assert.assertEquals( 42+10, id_res[0]);
+ Assert.assertEquals( msgNo2, msg_res[0]);
+ Assert.assertEquals( 10, myUserParam.i);
+ Assert.assertEquals(42+10+404+10, myUserParam.j);
+ }
+ bt2.MessageCallback01(null, myUserParam);
+ Assert.assertEquals(false, bt2.isMessageCallback01Mapped(myUserParam));
+ {
+ final String msgNo3 = "My Third JavaCallback message";
+ bt2.InjectMessageCallback01( 21, msgNo3);
+ // No callback shall be received, hence old values
+ Assert.assertEquals( 42+10, id_res[0]);
+ Assert.assertEquals( msgNo2, msg_res[0]);
+ Assert.assertEquals( 10, myUserParam.i);
+ Assert.assertEquals(42+10+404+10, myUserParam.j);
+ }
+ }
+ private static class MyUserParam {
+ final long i;
+ long j;
+ public MyUserParam(final long i) { this.i = i; j=0; }
+ }
+
+ public static void main(final String args[]) throws IOException {
+ final String tstname = Test4JavaCallback.class.getName();
+ org.junit.runner.JUnitCore.main(tstname);
+ }
+}
diff --git a/src/junit/com/jogamp/gluegen/test/junit/generation/test2.c b/src/junit/com/jogamp/gluegen/test/junit/generation/test2.c
index e5c1fe2..9715f11 100644
--- a/src/junit/com/jogamp/gluegen/test/junit/generation/test2.c
+++ b/src/junit/com/jogamp/gluegen/test/junit/generation/test2.c
@@ -26,14 +26,41 @@ static int32_t CustomFuncB2(T2_UserData* pUserData) {
return -pUserData->balance;
}
+static int32_t StaticInt32Array[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+static T2_UndefStruct StaticUndefStructArray[] = { { 0 }, { 1 }, { 2 }, { 3 }, { 4 }, { 5 }, { 6 }, { 7 }, { 8 }, { 9 } };
+
+T2_PointerStorage * createT2PointerStorage() {
+ T2_PointerStorage * s = calloc(1, sizeof(T2_PointerStorage));
+ for(int i=0; i<10; ++i) {
+ s->int32PtrArray[i] = &StaticInt32Array[i];
+ }
+ s->undefStructPtr = &StaticUndefStructArray[0];
+ for(int i=0; i<10; ++i) {
+ s->undefStructPtrArray[i] = &StaticUndefStructArray[i];
+ }
+
+ for(int i=0; i<10; ++i) {
+ s->customFuncAVariantsArray[i] = ( i %2 == 0 ) ? CustomFuncA1 : CustomFuncA2;
+ }
+ for(int i=0; i<10; ++i) {
+ s->customFuncBVariantsArray[i] = ( i %2 == 0 ) ? CustomFuncB1 : CustomFuncB2;
+ }
+ return s;
+}
+
+void destroyT2PointerStorage(T2_PointerStorage * s) {
+ assert(NULL!=s);
+ memset(s, 0, sizeof(T2_PointerStorage));
+ free(s);
+}
+
int Initialize(T2_InitializeOptions* Options) {
Options->ProductName = calloc(100, sizeof(char));
Options->ProductVersion = calloc(100, sizeof(char));
strncpy((char*)Options->ProductName, "Product Name", 100); // yuck: nonsense-warning
strncpy((char*)Options->ProductVersion, "Product Version", 100); // yuck: nonsense-warning
Options->ApiVersion = 1;
-
- Options->Reserved1 = NULL;
+ Options->Reserved1 = (void*) 0x0000CAFFEEBEEFUL;
Options->CustomFuncA1 = CustomFuncA1;
*( (T2_CustomFuncA*) &Options->CustomFuncA2 ) = CustomFuncA2; // yuck: real yuck
Options->CustomFuncB1 = CustomFuncB1;
@@ -59,31 +86,21 @@ int Release(T2_InitializeOptions* Options) {
Options->CustomFuncB2 = NULL;
}
-static int32_t StaticInt32Array[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
-static T2_UndefStruct StaticUndefStructArray[] = { { 0 }, { 1 }, { 2 }, { 3 }, { 4 }, { 5 }, { 6 }, { 7 }, { 8 }, { 9 } };
-
-T2_PointerStorage * createT2PointerStorage() {
- T2_PointerStorage * s = calloc(1, sizeof(T2_PointerStorage));
- for(int i=0; i<10; ++i) {
- s->int32PtrArray[i] = &StaticInt32Array[i];
- }
- s->undefStructPtr = &StaticUndefStructArray[0];
- for(int i=0; i<10; ++i) {
- s->undefStructPtrArray[i] = &StaticUndefStructArray[i];
- }
+static T2_CallbackFunc01 t2_callback01 = NULL;
+static void* t2_callback01_userparam = NULL;
- for(int i=0; i<10; ++i) {
- s->customFuncAVariantsArray[i] = ( i %2 == 0 ) ? CustomFuncA1 : CustomFuncA2;
- }
- for(int i=0; i<10; ++i) {
- s->customFuncBVariantsArray[i] = ( i %2 == 0 ) ? CustomFuncB1 : CustomFuncB2;
- }
- return s;
+void MessageCallback01(T2_CallbackFunc01 func, void* userParam) {
+ t2_callback01 = func;
+ t2_callback01_userparam = userParam;
+ fprintf(stderr, "XXX MessageCallback01 func %p, user %p\n", func, userParam);
+ fflush(NULL);
}
-void destroyT2PointerStorage(T2_PointerStorage * s) {
- assert(NULL!=s);
- memset(s, 0, sizeof(T2_PointerStorage));
- free(s);
+void InjectMessageCallback01(size_t id, const char* msg) {
+ if( NULL != t2_callback01 ) {
+ fprintf(stderr, "XXX InjectMessageCallback01 func %p, user %p\n", t2_callback01, t2_callback01_userparam);
+ fflush(NULL);
+ (*t2_callback01)(id, msg, t2_callback01_userparam);
+ }
}
diff --git a/src/junit/com/jogamp/gluegen/test/junit/generation/test2.cfg b/src/junit/com/jogamp/gluegen/test/junit/generation/test2.cfg
index 77a8433..94ea0af 100644
--- a/src/junit/com/jogamp/gluegen/test/junit/generation/test2.cfg
+++ b/src/junit/com/jogamp/gluegen/test/junit/generation/test2.cfg
@@ -43,12 +43,36 @@ ReturnsStringOnly T2_InitializeOptions.ProductVersion
# ReturnedArrayLength T2_InitializeOptions.OverrideThreadAffinity 1
MaxOneElement T2_InitializeOptions.OverrideThreadAffinity
-# typedef int32_t ( * T2_CallbackFunc)(size_t id, const char* msg, void* userParam);
-# void InjectMessageCallback(size_t id, const char* msg);
-ArgumentIsString T2_CallbackFunc 1
-ArgumentIsString InjectMessageCallback 1
-# Define a JavaCallback, enacted on a function-pointer argument `T2_CallbackFunc` and a user-param `void*` for Java Object mapping
-# JavaCallbackDef T2_CallbackFunc 2
+# Begin JavaCallback.
+#
+# JavaCallback requires `JNI_OnLoad*(..)` and `JVMUtil_GetJNIEnv(..)`
+LibraryOnLoad Bindingtest2
+
+# typedef void ( * T2_CallbackFunc01)(size_t id, const char* msg, void* usrParam);
+# void MessageCallback01(T2_CallbackFunc01 cbFunc, void* usrParam);
+# void InjectMessageCallback01(size_t id, const char* msg);
+ArgumentIsString T2_CallbackFunc01 1
+ArgumentIsString InjectMessageCallback01 1
+
+# Define a JavaCallback.
+# Set JavaCallback via function `MessageCallback01` if `T2_CallbackFunc01` argument is non-null, otherwise removes the callback and associated resources.
+# It uses `usrParam` as the resource-key to map to the hidden native-usrParam object,
+# hence a matching 'usrParam' must be passed for setting and removal of the callback.
+#
+# It uses the function-pointer argument `T2_CallbackFunc01` as the callback function type
+# and marks `T2_CallbackFunc01`s 3rd argument (index 2) as the mandatory user-param for Java Object mapping.
+#
+# Note: An explicit `isMessageCallback01Mapped(Object usrParam)` is being created to explicitly query whether `usrParam` maps to the associated resources.
+JavaCallbackDef MessageCallback01 T2_CallbackFunc01 2
+
+#
+# End JavaCallback
+
+# typedef void ( * T2_CallbackFunc02)(const T2_Callback02UserType* usrParam);
+# void MessageCallback02(T2_CallbackFunc02 cbFunc, const T2_Callback02UserType* usrParam);
+# void InjectMessageCallback02(size_t id, const char* msg);
+ArgumentIsString InjectMessageCallback02 1
+JavaCallbackDef MessageCallback02 T2_CallbackFunc02 0
CustomCCode #include "test2.h"
diff --git a/src/junit/com/jogamp/gluegen/test/junit/generation/test2.h b/src/junit/com/jogamp/gluegen/test/junit/generation/test2.h
index 78bdf87..afe94a5 100644
--- a/src/junit/com/jogamp/gluegen/test/junit/generation/test2.h
+++ b/src/junit/com/jogamp/gluegen/test/junit/generation/test2.h
@@ -58,9 +58,24 @@ typedef struct {
extern int Initialize(T2_InitializeOptions* Options);
extern int Release(T2_InitializeOptions* Options);
-typedef int32_t ( * T2_CallbackFunc)(size_t id, const char* msg, void* userParam);
+//
+// T2_CallbackFunc01
+//
+typedef void ( * T2_CallbackFunc01)(size_t id, const char* msg, void* usrParam);
-void AddMessageCallback(T2_CallbackFunc func, void* userParam);
-void RemoveMessageCallback(T2_CallbackFunc func, void* userParam);
-void InjectMessageCallback(size_t id, const char* msg);
+void MessageCallback01(T2_CallbackFunc01 cbFunc, void* usrParam);
+void InjectMessageCallback01(size_t id, const char* msg);
+
+//
+// T2_CallbackFunc02
+//
+typedef struct {
+ int32_t ApiVersion;
+ void* Data;
+} T2_Callback02UserType;
+
+typedef void ( * T2_CallbackFunc02)(const T2_Callback02UserType* usrParam);
+
+void MessageCallback02(T2_CallbackFunc02 cbFunc, const T2_Callback02UserType* usrParam);
+void InjectMessageCallback02(size_t id, const char* msg);