diff options
18 files changed, 1999 insertions, 862 deletions
@@ -31,7 +31,7 @@ GlueGen can produce native foreign function bindings to Java™ as well as [map native data structures](doc/GlueGen_Mapping.md#struct-mapping) to be fully accessible from Java™ including potential calls to [embedded function pointer](doc/GlueGen_Mapping.md#struct-function-pointer-support). -GlueGen supports [registering Java™ callback methods](doc/GlueGen_Mapping.md#java-callback-from-native-c-api-support) +GlueGen supports [registering Java™ callback methods](doc/GlueGen_Mapping.md#java-callback) to receive asynchronous and off-thread native toolkit events, where a generated native callback function dispatches the events to Java™. diff --git a/doc/GlueGen_Mapping.html b/doc/GlueGen_Mapping.html index 706eda4..d1b5b04 100644 --- a/doc/GlueGen_Mapping.html +++ b/doc/GlueGen_Mapping.html @@ -449,19 +449,28 @@ <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><a href="#java-callback">Java Callback</a> <ul> - <li><a href="#required-libraryonload">Required - <em>LibraryOnLoad</em></a></li> + <li><a href="#implementation-details">Implementation Details</a></li> + <li><a href="#javacallback-userparam-mapping"><em>JavaCallback</em> + <em>UserParam</em> Mapping</a></li> <li><a href="#javacallback-configuration"><em>JavaCallback</em> Configuration</a></li> + <li><a + href="#javacallback-generated-interfaces-classes-and-methods"><em>JavaCallback</em> + Generated Interfaces, Classes and Methods</a></li> <li><a href="#javacallback-notes"><em>JavaCallback</em> Notes</a></li> <li><a href="#javacallback-example-1">JavaCallback Example 1</a></li> <li><a href="#javacallback-example-2a-default-keyclass">JavaCallback Example 2a (Default <em>KeyClass</em>)</a></li> <li><a href="#javacallback-example-2b-custom-keyclass">JavaCallback Example 2b (Custom <em>KeyClass</em>)</a></li> + <li><a + href="#javacallback-example-11a-homogeneous-struct-type">JavaCallback + Example 11a (<em>Homogeneous Struct Type</em>)</a></li> + <li><a + href="#javacallback-example-11b-heterogeneous-pointerstruct-type">JavaCallback + Example 11b (<em>Heterogeneous Pointer/Struct Type</em>)</a></li> </ul></li> <li><a href="#misc-configurations">Misc Configurations</a> <ul> @@ -531,11 +540,10 @@ 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 supports <a href="#java-callback">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 @@ -1076,7 +1084,7 @@ variable</li> function pointer, see <a href="#struct-function-pointer-support">Struct Function-Pointer Support</a> below.</li> <li><em>Java Callback from Native Code</em>, see <a -href="#java-callback-from-native-c-api-support">section below</a></li> +href="#java-callback">section below</a></li> </ul> <p>A field may be a direct aggregation, i.e. instance, within the struct including an array or a reference to a single element or array via a @@ -1947,24 +1955,79 @@ StructPackage T2_InitializeOptions com.jogamp.gluegen.test.junit.generation</cod /** Interface to C language function: <br> <code>int32_t CustomFuncB1(T2_UserData * pUserData)</code><br> */ public final int CustomFuncB1(T2_UserData pUserData) { .. } </code></pre> -<h2 id="java-callback-from-native-c-api-support">Java Callback from -Native C-API Support</h2> +<h2 id="java-callback">Java Callback</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> -<h3 id="required-libraryonload">Required <em>LibraryOnLoad</em></h3> -<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> +<h3 id="implementation-details">Implementation Details</h3> +<p>Implementation generates a static Java callback dispatcher for each +defined <code>SetCallbackFunction</code>, which gets invoked by the +generated native static counterpart with all arguments required.</p> +<p>The <em>static callback</em> utilizes its own synchronization for +thread-safety and fetches the required data set stored at +<code>SetCallbackFunction</code> to dispatch the call to the users' +<code>CallbackFunction</code>.<br /> +In case the callback has been removed already, the <em>static +callback</em> simply bails out quietly.</p> +<p>The native code does not create, release or manage heap memory and +therefore is considered safe.</p> +<h3 id="javacallback-userparam-mapping"><em>JavaCallback</em> +<em>UserParam</em> Mapping</h3> +<p>Usually the same <code>UserParam</code> type is used in both items +(or hooks), <code>SetCallbackFunctionName</code> and +<code>CallbackFunctionType</code>, which we call a homogeneous +<code>UserParam</code> mapping.</p> +<p>Even in a homogeneous <code>UserParam</code> mapping, handling of the +<code>UserParam</code> value might differ in the native binding +code.</p> +<p>To specify a non homogeneous <code>UserParam</code> mapping, i.e. +heterogeneous <code>UserParam</code> mapping, the <code>UserParam</code> +index of the <code>SetCallbackFunction</code> must be <a +href="#javacallback-configuration">set in the configuration</a>.</p> +<p>The following mappings are supported.</p> +<h4 id="pure-java-object-user-type-default">Pure Java Object User Type +(default)</h4> +<p>A pure Java <em>Object type</em> is used for both, +<code>SetCallbackFunctionName</code> and +<code>CallbackFunctionType</code>.</p> +<p>It's a homogeneous <code>UserParam</code> mapping, where the native +side receives a simple unique ID and shall not dereference the +<em>pointer</em>.</p> +<p>The static Java callback dispatcher fetches the Java +<code>UserParam</code> <em>Object</em> from the key-mapped data +value.</p> +<h4 id="struct-type-user-param-homogeneous">Struct Type User Param +(Homogeneous)</h4> +<p>A <a href="#struct-mapping">GlueGen generated <em>Struct +type</em></a> is used for both, <code>SetCallbackFunctionName</code> and +<code>CallbackFunctionType</code>.</p> +<p>It's a homogeneous <code>UserParam</code> mapping, where the native +side receives the actual native struct address.</p> +<p>The static Java callback dispatcher dereferences the received native +struct address (<em>long</em>), i.e. rebuilding the <em>struct +Object</em> to be passed to the users' +<code>CallbackFunction</code>.</p> +<h4 id="struct-type-user-param-heterogeneous">Struct Type User Param +(Heterogeneous)</h4> +<p>An anonymous pointer (<em>long</em>) for +<code>SetCallbackFunctionName</code> and a <a +href="#struct-mapping">GlueGen generated <em>struct type</em></a> for +<code>CallbackFunctionType</code> is being used.</p> +<p>It's a heterogeneous <code>UserParam</code> mapping, where the +toolkit is expected to place the given anonymous pointer inside the +defined <em>struct type</em> passed to the +<code>CallbackFunction</code>.</p> +<p>The <code>SetCallback-UserParamIndex</code> for the different +parameter-type is <a href="#javacallback-configuration">set in the +configuration</a>.</p> +<p>The static Java callback dispatcher dereferences the received native +struct address (<em>long</em>), i.e. rebuilding the <em>struct +Object</em> to be passed to the users' +<code>CallbackFunction</code>.</p> <h3 id="javacallback-configuration"><em>JavaCallback</em> Configuration</h3> <p>Configuration directives are as follows:</p> -<pre><code>JavaCallbackDef <SetCallbackFunctionName> <CallbackFunctionType> <CallbackFunction-UserParamIndex> [<SetCallback-KeyClassName>] +<pre><code>JavaCallbackDef <SetCallbackFunctionName> [<SetCallback-UserParamIndex>] <CallbackFunctionType> <CallbackFunction-UserParamIndex> [<SetCallback-KeyClassName>] JavaCallbackKey <SetCallbackFunctionName> (SetCallback-ParamIdx)*</code></pre> <p><code>JavaCallbackDef</code> and <code>JavaCallbackKey</code> use the name of the <code>SetCallbackFunction</code> as its first attribute, as @@ -1973,6 +2036,11 @@ it is core to the semantic mapping of all resources.</p> <ul> <li><code>SetCallbackFunction</code>: <code>SetCallbackFunction</code> name of the native toolkit API responsible to set the callback</li> +<li><code>SetCallback-UserParamIndex</code>: Optional +<code>UserParam</code> parameter-index of the +<code>SetCallbackFunction</code>, allowing to <a +href="#struct-type-user-param-heterogeneous">indicate a heterogeneous +<code>UserParam</code></a></li> <li><code>CallbackFunctionType</code>: The native toolkit API typedef-name of the function-pointer-type, aka the callback type name</li> @@ -2012,9 +2080,35 @@ callback scope, i.e. the callback and all resources will be mapped to this key. The optional <code>SetCallback-KeyClass</code> may override this semantic.</li> </ul> -<h4 +<h4 id="custom-setcallback-keyclass">Custom +<code>SetCallback-KeyClass</code></h4> +<p>The <code>SetCallback-KeyClass</code> is the optional user-written +hash-map-key definition and shall handle all key parameter of the +<code>SetCallbackFunction</code> as defined via +<code>JavaCallbackKey</code>, see above.</p> +<p><code>SetCallback-KeyClass</code> may be used to add external +key-components, e.g. current-thread or a toolkit dependent context.</p> +<p>The <code>SetCallback-KeyClass</code> shall implement the following +hash-map-key standard methods</p> +<ul> +<li><code>boolean equals(Object)</code></li> +<li><code>int hashCode()</code></li> +<li><code>SetCallback-KeyClassName(...)</code> constructor receiving all +key parameter of <code>SetCallbackFunction</code> as defined via +<code>JavaCallbackKey</code>, see above.</li> +</ul> +<h4 id="required-libraryonload">Required <em>LibraryOnLoad</em></h4> +<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> +<h3 id="javacallback-generated-interfaces-classes-and-methods"><em>JavaCallback</em> -Generated Interfaces, Classes and Methods</h4> +Generated Interfaces, Classes and Methods</h3> <p>The public <code>CallbackFunction</code> interface is generated.</p> <p>The default public <code>SetCallback-KeyClass</code> is generated if keys are used and no custom class is specified, see above.</p> @@ -2068,23 +2162,6 @@ and <em>releaseAll<code>SetCallbackFunctionName</code>()</em> methods are not the <em>proper toolkit API way</em> to remove the callback, try to use original <code>SetCallbackFunctionName</code> API method instead using a <code>null</code> <code>CallbackFunction</code> reference.</p> -<h4 id="custom-setcallback-keyclass">Custom -<code>SetCallback-KeyClass</code></h4> -<p>The <code>SetCallback-KeyClass</code> is the optional user-written -hash-map-key definition and shall handle all key parameter of the -<code>SetCallbackFunction</code> as defined via -<code>JavaCallbackKey</code>, see above.</p> -<p><code>SetCallback-KeyClass</code> may be used to add external -key-components, e.g. current-thread or a toolkit dependent context.</p> -<p>The <code>SetCallback-KeyClass</code> shall implement the following -hash-map-key standard methods</p> -<ul> -<li><code>boolean equals(Object)</code></li> -<li><code>int hashCode()</code></li> -<li><code>SetCallback-KeyClassName(...)</code> constructor receiving all -key parameter of <code>SetCallbackFunction</code> as defined via -<code>JavaCallbackKey</code>, see above.</li> -</ul> <h3 id="javacallback-notes"><em>JavaCallback</em> Notes</h3> <p>Please consider the following <em>currently enabled</em> constraints using JavaCallback</p> @@ -2118,7 +2195,11 @@ and the native callback dispatcher <strong>are thread-safe</strong></li> <li>...</li> </ul> <h3 id="javacallback-example-1">JavaCallback Example 1</h3> -<p>This is a generic example.</p> +<p>This examples demonstrates a <a +href="#pure-java-object-user-type-default">homogeneous <em>Java +Object</em> <code>UserParam</code> mapping</a> with a <a +href="#javacallback-key-definition">globally scoped</a> +<code>CallbackFunction</code> and <code>UserParam</code>.</p> <p>The callback <code>T2_CallbackFunc01</code> has global scope, i.e. is not mapped to any key and can be only set globally.</p> <p>C-API header snippet:</p> @@ -2187,6 +2268,11 @@ thread to the <code>JavaVM*</code> generating a new public void InjectMessageCallback01(long id, String msg);</code></pre> <h3 id="javacallback-example-2a-default-keyclass">JavaCallback Example 2a (Default <em>KeyClass</em>)</h3> +<p>This examples demonstrates a <a +href="#pure-java-object-user-type-default">homogeneous <em>Java +Object</em> <code>UserParam</code> mapping</a> with a <a +href="#javacallback-key-definition">key-mapped</a> +<code>CallbackFunction</code> and <code>UserParam</code>.</p> <p>This examples is derived from OpenAL's <code>AL_SOFT_callback_buffer</code> extension.</p> <p>The callback <code>ALBUFFERCALLBACKTYPESOFT</code> is mapped to @@ -2278,6 +2364,11 @@ buffer.</p> public void alEventCallbackInject(int eventType, int object, int param, String msg); </code></pre> <h3 id="javacallback-example-2b-custom-keyclass">JavaCallback Example 2b (Custom <em>KeyClass</em>)</h3> +<p>This examples demonstrates a <a +href="#pure-java-object-user-type-default">homogeneous <em>Java +Object</em> <code>UserParam</code> mapping</a> with a <a +href="#custom-setcallback-keyclass">custom <em>KeyClass</em></a> to map +<code>CallbackFunction</code> and <code>UserParam</code>.</p> <p>Same as example 2a, but implementing a custom <code>SetCallback-KeyClass</code>.</p> <p>Instead of <code>Callback0</code>, the unit <code>test2.*</code> uses @@ -2316,6 +2407,133 @@ which uses one key, i.e. <code>buffer</code>.</p> return "CustomALKey[this "+toHexString(System.identityHashCode(this))+", buffer "+buffer+"]"; } }</code></pre> +<h3 id="javacallback-example-11a-homogeneous-struct-type">JavaCallback +Example 11a (<em>Homogeneous Struct Type</em>)</h3> +<p>This examples demonstrates a <a +href="#struct-type-user-param-homogeneous">homogeneous <em>Struct</em> +<code>UserParam</code> mapping</a> with a <a +href="#javacallback-key-definition">key-mapped</a> +<code>CallbackFunction</code> and <code>UserParam</code>.</p> +<p>The callback <code>T2_CallbackFunc11</code> is passed by the toolkit +to the <code>CallbackFunction</code> and by the user to the registration +method <code>MessageCallback11b(..)</code>.</p> +<p>C-API Header snipped</p> +<pre><code> typedef struct { + int32_t ApiVersion; + void* Data; + long i; + long r; + size_t id; + } T2_Callback11UserType; + + typedef void ( * T2_CallbackFunc11)(size_t id, const T2_Callback11UserType* usrParam, long val); + + void MessageCallback11a(size_t id /* key */, T2_CallbackFunc11 cbFunc, const T2_Callback11UserType* usrParam); + void MessageCallback11aInject(size_t id, long val); </code></pre> +<p>and the following GlueGen configuration</p> +<pre><code> JavaCallbackDef MessageCallback11a T2_CallbackFunc11 1 + JavaCallbackKey MessageCallback11a 0</code></pre> +<p>leading to the following interface</p> +<pre><code> /** JavaCallback interface: T2_CallbackFunc11 -> void (*T2_CallbackFunc11)(size_t id, const T2_Callback11UserType * usrParam, long val) */ + public static interface T2_CallbackFunc11 { + /** Interface to C language function: <br> <code>void callback(size_t id, const T2_Callback11UserType * usrParam, long val)</code><br>Alias for: <code>T2_CallbackFunc11</code> */ + public void callback(long id, T2_Callback11UserType usrParam, long val); + } + + ... + + public static class MessageCallback11aKey { ... } + + ... + + /** Returns set of Key { long id } for <br> <code> void MessageCallback11a(long id, T2_CallbackFunc11 cbFunc, T2_Callback11UserType usrParam)</code> */ + public Set<MessageCallback11aKey> getMessageCallback11aKeys(); + + /** Returns whether callback Key { long id } is mapped for <br> <code> void MessageCallback11a(long id, T2_CallbackFunc11 cbFunc, T2_Callback11UserType usrParam)</code> */ + public boolean isMessageCallback11aMapped(MessageCallback11aKey key); + + /** Returns T2_CallbackFunc11 callback mapped to Key { long id } for <br> <code> void MessageCallback11a(long id, T2_CallbackFunc11 cbFunc, T2_Callback11UserType usrParam)</code> */ + public T2_CallbackFunc11 getMessageCallback11a(MessageCallback11aKey key); + + /** Returns user-param mapped to Key { long id } for <br> <code> void MessageCallback11a(long id, T2_CallbackFunc11 cbFunc, T2_Callback11UserType usrParam)</code> */ + public Object getMessageCallback11aUserParam(MessageCallback11aKey key); + + /** Releases all callback data mapped via Key { long id } skipping toolkit API. Favor passing `null` callback ref to <br> <code> void MessageCallback11a(long id, T2_CallbackFunc11 cbFunc, T2_Callback11UserType usrParam)</code> */ + public int releaseAllMessageCallback11a(); + + /** Releases callback data mapped to Key { long id } skipping toolkit API. Favor passing `null` callback ref to <br> <code> void MessageCallback11a(long id, T2_CallbackFunc11 cbFunc, T2_Callback11UserType usrParam)</code> */ + public void releaseMessageCallback11a(MessageCallback11aKey key); + + /** Entry point (through function pointer) to C language function: <br> <code>void MessageCallback11a(size_t id, T2_CallbackFunc11 cbFunc, const T2_Callback11UserType * usrParam)</code><br> */ + public void MessageCallback11a(long id, T2_CallbackFunc11 cbFunc, T2_Callback11UserType usrParam); + + /** Entry point (through function pointer) to C language function: <br> <code>void MessageCallback11aInject(size_t id, long val)</code><br> */ + public void MessageCallback11aInject(long id, long val); </code></pre> +<h3 +id="javacallback-example-11b-heterogeneous-pointerstruct-type">JavaCallback +Example 11b (<em>Heterogeneous Pointer/Struct Type</em>)</h3> +<p>This examples demonstrates a <a +href="#struct-type-user-param-heterogeneous">heterogeneous +<em>Struct</em> <code>UserParam</code> mapping</a> with a <a +href="#javacallback-key-definition">key-mapped</a> +<code>CallbackFunction</code> and <code>UserParam</code>.</p> +<p>The callback <code>T2_CallbackFunc11</code> is managed by the toolkit +and passed to the callback function, while user passes a <em>void</em> +with the registration method <code>MessageCallback11b(..)</code>. The +toolkit associates the users' <code>void*</code> pointer with the +<code>T2_CallbackFunc11</code>.</p> +<p>C-API Header snipped</p> +<pre><code> typedef struct { + int32_t ApiVersion; + void* Data; + long i; + long r; + size_t id; + } T2_Callback11UserType; + + typedef void ( * T2_CallbackFunc11)(size_t id, const T2_Callback11UserType* usrParam, long val); + + void MessageCallback11b(size_t id /* key */, T2_CallbackFunc11 cbFunc, void* Data); + void MessageCallback11bInject(size_t id, long val);</code></pre> +<p>and the following GlueGen configuration</p> +<pre><code> JavaCallbackDef MessageCallback11b 2 T2_CallbackFunc11 1 + JavaCallbackKey MessageCallback11b 0</code></pre> +<p>leading to the following interface</p> +<pre><code> /** JavaCallback interface: T2_CallbackFunc11 -> void (*T2_CallbackFunc11)(size_t id, const T2_Callback11UserType * usrParam, long val) */ + public static interface T2_CallbackFunc11 { + /** Interface to C language function: <br> <code>void callback(size_t id, const T2_Callback11UserType * usrParam, long val)</code><br>Alias for: <code>T2_CallbackFunc11</code> */ + public void callback(long id, T2_Callback11UserType usrParam, long val); + } + + ... + + public static class MessageCallback11bKey { ... } + + ... + + /** Returns set of Key { long id } for <br> <code> void MessageCallback11b(long id, T2_CallbackFunc11 cbFunc, long Data)</code> */ + public Set<MessageCallback11bKey> getMessageCallback11bKeys(); + + /** Returns whether callback Key { long id } is mapped for <br> <code> void MessageCallback11b(long id, T2_CallbackFunc11 cbFunc, long Data)</code> */ + public boolean isMessageCallback11bMapped(MessageCallback11bKey key); + + /** Returns T2_CallbackFunc11 callback mapped to Key { long id } for <br> <code> void MessageCallback11b(long id, T2_CallbackFunc11 cbFunc, long Data)</code> */ + public T2_CallbackFunc11 getMessageCallback11b(MessageCallback11bKey key); + + /** Returns user-param mapped to Key { long id } for <br> <code> void MessageCallback11b(long id, T2_CallbackFunc11 cbFunc, long Data)</code> */ + public Object getMessageCallback11bUserParam(MessageCallback11bKey key); + + /** Releases all callback data mapped via Key { long id } skipping toolkit API. Favor passing `null` callback ref to <br> <code> void MessageCallback11b(long id, T2_CallbackFunc11 cbFunc, long Data)</code> */ + public int releaseAllMessageCallback11b(); + + /** Releases callback data mapped to Key { long id } skipping toolkit API. Favor passing `null` callback ref to <br> <code> void MessageCallback11b(long id, T2_CallbackFunc11 cbFunc, long Data)</code> */ + public void releaseMessageCallback11b(MessageCallback11bKey key); + + /** Entry point (through function pointer) to C language function: <br> <code>void MessageCallback11b(size_t id, T2_CallbackFunc11 cbFunc, void * Data)</code><br> */ + public void MessageCallback11b(long id, T2_CallbackFunc11 cbFunc, long Data); + + /** Entry point (through function pointer) to C language function: <br> <code>void MessageCallback11bInject(size_t id, long val)</code><br> */ + public void MessageCallback11bInject(long id, long val); </code></pre> <p><em>TODO: Enhance documentation</em></p> <h2 id="misc-configurations">Misc Configurations</h2> <h3 diff --git a/doc/GlueGen_Mapping.md b/doc/GlueGen_Mapping.md index 1ab79b2..6453e64 100644 --- a/doc/GlueGen_Mapping.md +++ b/doc/GlueGen_Mapping.md @@ -40,7 +40,7 @@ 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) +GlueGen supports [registering Java™ callback methods](#java-callback) to receive asynchronous and off-thread native toolkit events, where a generated native callback function dispatches the events to Java™. @@ -299,7 +299,7 @@ A *Struct* may utilize the following data types for its fields * See [*String Mapping*](#string-mapping) above. * *Struct*, i.e. an aggregated or referenced compound variable * *Function Pointer*, a *typedef*'ed and set callable function pointer, see [Struct Function-Pointer Support](#struct-function-pointer-support) below. -* *Java Callback from Native Code*, see [section below](#java-callback-from-native-c-api-support) +* *Java Callback from Native Code*, see [section below](#java-callback) A field may be a direct aggregation, i.e. instance, within the struct including an array or a reference to a single element or array via a pointer. @@ -765,21 +765,58 @@ and similar to `T2_CustomFuncB customFuncB1` public final int CustomFuncB1(T2_UserData pUserData) { .. } ``` -## Java Callback from Native C-API Support +## Java Callback 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. -### Required *LibraryOnLoad* -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*`. +### Implementation Details +Implementation generates a static Java callback dispatcher for each defined `SetCallbackFunction`, which gets invoked by the generated native static counterpart with all arguments required. + +The *static callback* utilizes its own synchronization for thread-safety and fetches the required data set stored at `SetCallbackFunction` to dispatch the call to the users' `CallbackFunction`. +In case the callback has been removed already, the *static callback* simply bails out quietly. + +The native code does not create, release or manage heap memory and therefore is considered safe. + +### *JavaCallback* *UserParam* Mapping +Usually the same `UserParam` type is used in both items (or hooks), `SetCallbackFunctionName` and `CallbackFunctionType`, +which we call a homogeneous `UserParam` mapping. + +Even in a homogeneous `UserParam` mapping, handling of the `UserParam` value might differ in the native binding code. + +To specify a non homogeneous `UserParam` mapping, i.e. heterogeneous `UserParam` mapping, +the `UserParam` index of the `SetCallbackFunction` must be [set in the configuration](#javacallback-configuration). + +The following mappings are supported. + +#### Pure Java Object User Type (default) +A pure Java *Object type* is used for both, `SetCallbackFunctionName` and `CallbackFunctionType`. + +It's a homogeneous `UserParam` mapping, where the native side receives a simple unique ID and shall not dereference the *pointer*. + +The static Java callback dispatcher fetches the Java `UserParam` *Object* from the key-mapped data value. + +#### Struct Type User Param (Homogeneous) +A [GlueGen generated *Struct type*](#struct-mapping) is used for both, `SetCallbackFunctionName` and `CallbackFunctionType`. + +It's a homogeneous `UserParam` mapping, where the native side receives the actual native struct address. + +The static Java callback dispatcher dereferences the received native struct address (*long*), i.e. rebuilding the *struct Object* to be passed to the users' `CallbackFunction`. + +#### Struct Type User Param (Heterogeneous) +An anonymous pointer (*long*) for `SetCallbackFunctionName` and a [GlueGen generated *struct type*](#struct-mapping) for `CallbackFunctionType` is being used. + +It's a heterogeneous `UserParam` mapping, where the toolkit is expected to place the given anonymous pointer inside the defined *struct type* passed to the `CallbackFunction`. + +The `SetCallback-UserParamIndex` for the different parameter-type is [set in the configuration](#javacallback-configuration). + +The static Java callback dispatcher dereferences the received native struct address (*long*), i.e. rebuilding the *struct Object* to be passed to the users' `CallbackFunction`. ### *JavaCallback* Configuration Configuration directives are as follows: - JavaCallbackDef <SetCallbackFunctionName> <CallbackFunctionType> <CallbackFunction-UserParamIndex> [<SetCallback-KeyClassName>] + JavaCallbackDef <SetCallbackFunctionName> [<SetCallback-UserParamIndex>] <CallbackFunctionType> <CallbackFunction-UserParamIndex> [<SetCallback-KeyClassName>] JavaCallbackKey <SetCallbackFunctionName> (SetCallback-ParamIdx)* `JavaCallbackDef` and `JavaCallbackKey` use the name of the `SetCallbackFunction` as its first attribute, @@ -787,6 +824,7 @@ as it is core to the semantic mapping of all resources. `JavaCallbackDef` attributes: - `SetCallbackFunction`: `SetCallbackFunction` name of the native toolkit API responsible to set the callback +- `SetCallback-UserParamIndex`: Optional `UserParam` parameter-index of the `SetCallbackFunction`, allowing to [indicate a heterogeneous `UserParam`](#struct-type-user-param-heterogeneous) - `CallbackFunctionType`: The native toolkit API typedef-name of the function-pointer-type, aka the callback type name - `CallbackFunction-UserParamIndex`: The `userParam` parameter-index of the `CallbackFunctionType` - `SetCallback-KeyClassName`: Name of an optional user-implemented `SetCallback-KeyClass`, providing the hash-map-key - see below @@ -811,7 +849,24 @@ Key arguments must match in `SetCallbackFunction` to remove a previously set `Ca - `SetCallbackFunction`: `SetCallbackFunction` name of the native toolkit API responsible to set the callback - `SetCallback-ParamIdx`: List of parameter indices of the `SetCallbackFunction`, denoting the key(s) limiting the callback scope, i.e. the callback and all resources will be mapped to this key. The optional `SetCallback-KeyClass` may override this semantic. -#### *JavaCallback* Generated Interfaces, Classes and Methods +#### Custom `SetCallback-KeyClass` + +The `SetCallback-KeyClass` is the optional user-written hash-map-key definition +and shall handle all key parameter of the `SetCallbackFunction` as defined via `JavaCallbackKey`, see above. + +`SetCallback-KeyClass` may be used to add external key-components, e.g. current-thread or a toolkit dependent context. + +The `SetCallback-KeyClass` shall implement the following hash-map-key standard methods +- `boolean equals(Object)` +- `int hashCode()` +- `SetCallback-KeyClassName(...)` constructor receiving all key parameter of `SetCallbackFunction` as defined via `JavaCallbackKey`, see above. + +#### Required *LibraryOnLoad* +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*`. + +### *JavaCallback* Generated Interfaces, Classes and Methods The public `CallbackFunction` interface is generated. @@ -838,19 +893,6 @@ If no `SetCallback-KeyClass` is used, the additional *maintenance* methods are: Note that the *release`SetCallbackFunctionName`(\*)* and *releaseAll`SetCallbackFunctionName`()* methods are not the *proper toolkit API way* to remove the callback, try to use original `SetCallbackFunctionName` API method instead using a `null` `CallbackFunction` reference. -#### Custom `SetCallback-KeyClass` - -The `SetCallback-KeyClass` is the optional user-written hash-map-key definition -and shall handle all key parameter of the `SetCallbackFunction` as defined via `JavaCallbackKey`, see above. - -`SetCallback-KeyClass` may be used to add external key-components, e.g. current-thread or a toolkit dependent context. - -The `SetCallback-KeyClass` shall implement the following hash-map-key standard methods -- `boolean equals(Object)` -- `int hashCode()` -- `SetCallback-KeyClassName(...)` constructor receiving all key parameter of `SetCallbackFunction` as defined via `JavaCallbackKey`, see above. - - ### *JavaCallback* Notes Please consider the following *currently enabled* constraints using JavaCallback - Only one interface callback-method binding is allowed for a native callback function, e.g. `T2_CallbackFunc01` (see above) @@ -866,7 +908,7 @@ Please consider the following *currently enabled* constraints using JavaCallback - ... ### JavaCallback Example 1 -This is a generic example. +This examples demonstrates a [homogeneous *Java Object* `UserParam` mapping](#pure-java-object-user-type-default) with a [globally scoped](#javacallback-key-definition) `CallbackFunction` and `UserParam`. The callback `T2_CallbackFunc01` has global scope, i.e. is not mapped to any key and can be only set globally. @@ -941,6 +983,8 @@ public interface Bindingtest2 { ### JavaCallback Example 2a (Default *KeyClass*) +This examples demonstrates a [homogeneous *Java Object* `UserParam` mapping](#pure-java-object-user-type-default) with a [key-mapped](#javacallback-key-definition) `CallbackFunction` and `UserParam`. + This examples is derived from OpenAL's `AL_SOFT_callback_buffer` extension. The callback `ALBUFFERCALLBACKTYPESOFT` is mapped to `buffer` name, i.e. one callback can be set for each buffer. @@ -1040,6 +1084,8 @@ leading to the following interface ### JavaCallback Example 2b (Custom *KeyClass*) +This examples demonstrates a [homogeneous *Java Object* `UserParam` mapping](#pure-java-object-user-type-default) with a [custom *KeyClass*](#custom-setcallback-keyclass) to map `CallbackFunction` and `UserParam`. + Same as example 2a, but implementing a custom `SetCallback-KeyClass`. Instead of `Callback0`, the unit `test2.*` uses `Callback1` to differentiate this case. @@ -1080,6 +1126,141 @@ which uses one key, i.e. `buffer`. } ``` +### JavaCallback Example 11a (*Homogeneous Struct Type*) + +This examples demonstrates a [homogeneous *Struct* `UserParam` mapping](#struct-type-user-param-homogeneous) with a [key-mapped](#javacallback-key-definition) `CallbackFunction` and `UserParam`. + +The callback `T2_CallbackFunc11` is passed by the toolkit to the `CallbackFunction` and by the user to the registration method `MessageCallback11b(..)`. + +C-API Header snipped +``` + typedef struct { + int32_t ApiVersion; + void* Data; + long i; + long r; + size_t id; + } T2_Callback11UserType; + + typedef void ( * T2_CallbackFunc11)(size_t id, const T2_Callback11UserType* usrParam, long val); + + void MessageCallback11a(size_t id /* key */, T2_CallbackFunc11 cbFunc, const T2_Callback11UserType* usrParam); + void MessageCallback11aInject(size_t id, long val); +``` + +and the following GlueGen configuration +``` + JavaCallbackDef MessageCallback11a T2_CallbackFunc11 1 + JavaCallbackKey MessageCallback11a 0 +``` + +leading to the following interface +``` + /** JavaCallback interface: T2_CallbackFunc11 -> void (*T2_CallbackFunc11)(size_t id, const T2_Callback11UserType * usrParam, long val) */ + public static interface T2_CallbackFunc11 { + /** Interface to C language function: <br> <code>void callback(size_t id, const T2_Callback11UserType * usrParam, long val)</code><br>Alias for: <code>T2_CallbackFunc11</code> */ + public void callback(long id, T2_Callback11UserType usrParam, long val); + } + + ... + + public static class MessageCallback11aKey { ... } + + ... + + /** Returns set of Key { long id } for <br> <code> void MessageCallback11a(long id, T2_CallbackFunc11 cbFunc, T2_Callback11UserType usrParam)</code> */ + public Set<MessageCallback11aKey> getMessageCallback11aKeys(); + + /** Returns whether callback Key { long id } is mapped for <br> <code> void MessageCallback11a(long id, T2_CallbackFunc11 cbFunc, T2_Callback11UserType usrParam)</code> */ + public boolean isMessageCallback11aMapped(MessageCallback11aKey key); + + /** Returns T2_CallbackFunc11 callback mapped to Key { long id } for <br> <code> void MessageCallback11a(long id, T2_CallbackFunc11 cbFunc, T2_Callback11UserType usrParam)</code> */ + public T2_CallbackFunc11 getMessageCallback11a(MessageCallback11aKey key); + + /** Returns user-param mapped to Key { long id } for <br> <code> void MessageCallback11a(long id, T2_CallbackFunc11 cbFunc, T2_Callback11UserType usrParam)</code> */ + public Object getMessageCallback11aUserParam(MessageCallback11aKey key); + + /** Releases all callback data mapped via Key { long id } skipping toolkit API. Favor passing `null` callback ref to <br> <code> void MessageCallback11a(long id, T2_CallbackFunc11 cbFunc, T2_Callback11UserType usrParam)</code> */ + public int releaseAllMessageCallback11a(); + + /** Releases callback data mapped to Key { long id } skipping toolkit API. Favor passing `null` callback ref to <br> <code> void MessageCallback11a(long id, T2_CallbackFunc11 cbFunc, T2_Callback11UserType usrParam)</code> */ + public void releaseMessageCallback11a(MessageCallback11aKey key); + + /** Entry point (through function pointer) to C language function: <br> <code>void MessageCallback11a(size_t id, T2_CallbackFunc11 cbFunc, const T2_Callback11UserType * usrParam)</code><br> */ + public void MessageCallback11a(long id, T2_CallbackFunc11 cbFunc, T2_Callback11UserType usrParam); + + /** Entry point (through function pointer) to C language function: <br> <code>void MessageCallback11aInject(size_t id, long val)</code><br> */ + public void MessageCallback11aInject(long id, long val); +``` + +### JavaCallback Example 11b (*Heterogeneous Pointer/Struct Type*) + +This examples demonstrates a [heterogeneous *Struct* `UserParam` mapping](#struct-type-user-param-heterogeneous) with a [key-mapped](#javacallback-key-definition) `CallbackFunction` and `UserParam`. + +The callback `T2_CallbackFunc11` is managed by the toolkit and passed to the callback function, while user passes a *void* with the registration method `MessageCallback11b(..)`. The toolkit associates the users' `void*` pointer with the `T2_CallbackFunc11`. + + +C-API Header snipped +``` + typedef struct { + int32_t ApiVersion; + void* Data; + long i; + long r; + size_t id; + } T2_Callback11UserType; + + typedef void ( * T2_CallbackFunc11)(size_t id, const T2_Callback11UserType* usrParam, long val); + + void MessageCallback11b(size_t id /* key */, T2_CallbackFunc11 cbFunc, void* Data); + void MessageCallback11bInject(size_t id, long val); +``` + +and the following GlueGen configuration +``` + JavaCallbackDef MessageCallback11b 2 T2_CallbackFunc11 1 + JavaCallbackKey MessageCallback11b 0 +``` + +leading to the following interface +``` + /** JavaCallback interface: T2_CallbackFunc11 -> void (*T2_CallbackFunc11)(size_t id, const T2_Callback11UserType * usrParam, long val) */ + public static interface T2_CallbackFunc11 { + /** Interface to C language function: <br> <code>void callback(size_t id, const T2_Callback11UserType * usrParam, long val)</code><br>Alias for: <code>T2_CallbackFunc11</code> */ + public void callback(long id, T2_Callback11UserType usrParam, long val); + } + + ... + + public static class MessageCallback11bKey { ... } + + ... + + /** Returns set of Key { long id } for <br> <code> void MessageCallback11b(long id, T2_CallbackFunc11 cbFunc, long Data)</code> */ + public Set<MessageCallback11bKey> getMessageCallback11bKeys(); + + /** Returns whether callback Key { long id } is mapped for <br> <code> void MessageCallback11b(long id, T2_CallbackFunc11 cbFunc, long Data)</code> */ + public boolean isMessageCallback11bMapped(MessageCallback11bKey key); + + /** Returns T2_CallbackFunc11 callback mapped to Key { long id } for <br> <code> void MessageCallback11b(long id, T2_CallbackFunc11 cbFunc, long Data)</code> */ + public T2_CallbackFunc11 getMessageCallback11b(MessageCallback11bKey key); + + /** Returns user-param mapped to Key { long id } for <br> <code> void MessageCallback11b(long id, T2_CallbackFunc11 cbFunc, long Data)</code> */ + public Object getMessageCallback11bUserParam(MessageCallback11bKey key); + + /** Releases all callback data mapped via Key { long id } skipping toolkit API. Favor passing `null` callback ref to <br> <code> void MessageCallback11b(long id, T2_CallbackFunc11 cbFunc, long Data)</code> */ + public int releaseAllMessageCallback11b(); + + /** Releases callback data mapped to Key { long id } skipping toolkit API. Favor passing `null` callback ref to <br> <code> void MessageCallback11b(long id, T2_CallbackFunc11 cbFunc, long Data)</code> */ + public void releaseMessageCallback11b(MessageCallback11bKey key); + + /** Entry point (through function pointer) to C language function: <br> <code>void MessageCallback11b(size_t id, T2_CallbackFunc11 cbFunc, void * Data)</code><br> */ + public void MessageCallback11b(long id, T2_CallbackFunc11 cbFunc, long Data); + + /** Entry point (through function pointer) to C language function: <br> <code>void MessageCallback11bInject(size_t id, long val)</code><br> */ + public void MessageCallback11bInject(long id, long val); +``` + *TODO: Enhance documentation* ## Misc Configurations diff --git a/src/java/com/jogamp/gluegen/CCodeUnit.java b/src/java/com/jogamp/gluegen/CCodeUnit.java index c70df79..5c0db27 100644 --- a/src/java/com/jogamp/gluegen/CCodeUnit.java +++ b/src/java/com/jogamp/gluegen/CCodeUnit.java @@ -84,11 +84,6 @@ public class CCodeUnit extends CodeUnit { emitln( getJNIOnLoadJNIEnvCode(libraryBasename) ); } - /** Emits {@link #JavaCallbackGlueDataDecl}. */ - public void emitJavaCallbackGlueDataDecl() { - emitln( JavaCallbackGlueDataDecl ); - } - @Override public String toString() { return "CCodeUnit[unit "+cUnitName+", file "+filename+"]"; } @@ -113,16 +108,6 @@ public class CCodeUnit extends CodeUnit { " return jbyteBuffer;\n"+ "}\n"; - /** JavaCallback Glue Data typedef struct */ - public static final String JavaCallbackGlueDataDecl = - "typedef struct {\n"+ - " jobject lockObj;\n"+ - " jobject cbFunc;\n"+ - " jmethodID cbMethodID;\n"+ - " jobject userParam;\n"+ - "} T_JavaCallbackGlueData;\n"+ - "\n"; - /** * Returns native JNI declarations for `JavaVM* {libraryBasename}_jvmHandle` * and `JVMUtil_GetJNIEnv(..)`. diff --git a/src/java/com/jogamp/gluegen/CMethodBindingEmitter.java b/src/java/com/jogamp/gluegen/CMethodBindingEmitter.java index 00cae13..9c7ed0b 100644 --- a/src/java/com/jogamp/gluegen/CMethodBindingEmitter.java +++ b/src/java/com/jogamp/gluegen/CMethodBindingEmitter.java @@ -113,15 +113,11 @@ public class CMethodBindingEmitter extends FunctionEmitter { protected static final String STRING_CHARS_PREFIX = "_strchars_"; - protected static final String T_JavaCallbackGlueData = "T_JavaCallbackGlueData"; - // We need this in order to compute sizes of certain types protected MachineDataInfo machDesc; - private final JavaCallbackInfo javaCallback; - private final String jcbNativeBasename; - private final String jcbFriendlyBasename; private final CMethodBindingEmitter jcbCMethodEmitter; + private final JavaCallbackEmitter javaCallbackEmitter; /** * Constructs an emitter for the specified binding, and sets a default @@ -155,18 +151,17 @@ public class CMethodBindingEmitter extends FunctionEmitter { this.forIndirectBufferAndArrayImplementation = forIndirectBufferAndArrayImplementation; this.machDesc = machDesc; - javaCallback = cfg.setFuncToJavaCallbackMap.get(binding.getName()); + final JavaCallbackInfo javaCallback = cfg.setFuncToJavaCallbackMap.get(binding.getName()); if( null != javaCallback ) { - jcbNativeBasename = CodeGenUtils.capitalizeString( javaCallback.setFuncName+javaCallback.cbSimpleClazzName.replace("_", "") ); - jcbFriendlyBasename = javaCallback.setFuncName+"("+javaCallback.cbSimpleClazzName+")"; + // jcbNativeBasename = CodeGenUtils.capitalizeString( javaCallback.setFuncName+javaCallback.cbSimpleClazzName.replace("_", "") ); jcbCMethodEmitter = new CMethodBindingEmitter(javaCallback.cbFuncBinding, unit, javaPackageName, javaClassName, isOverloadedBinding, isJavaMethodStatic, forImplementingMethodCall, forIndirectBufferAndArrayImplementation, machDesc, configuration); + javaCallbackEmitter = new JavaCallbackEmitter(cfg, binding, javaCallback, null); } else { - jcbNativeBasename = null; - jcbFriendlyBasename = null; jcbCMethodEmitter = null; + javaCallbackEmitter = null; } setCommentEmitter(defaultCommentEmitter); } @@ -328,145 +323,25 @@ public class CMethodBindingEmitter extends FunctionEmitter { */ public final MachineDataInfo getMachineDataInfo() { return machDesc; } - private static final boolean DEBUG_JAVACALLBACK = false; - @Override - protected void emitReturnType() { - if( null != javaCallback ) { - LOG.log(INFO, "BindCFunc.R.JavaCallback: {0}: {1}", binding.getName(), javaCallback); - final String staticCallbackName = "func"+jcbNativeBasename; - - final Type userParamType = javaCallback.cbFuncBinding.getCArgumentType(javaCallback.cbFuncUserParamIdx); - final String userParamArgName = javaCallback.cbFuncBinding.getArgumentName(javaCallback.cbFuncUserParamIdx); - final Type cReturnType = javaCallback.cbFuncBinding.getCReturnType(); - final JavaType jretType = javaCallback.cbFuncBinding.getJavaReturnType(); - unit.emitln(); - // javaCallback.cbFuncCEmitter.emitSignature(); - unit.emit("static "+cReturnType.getCName()+" "+staticCallbackName+"("); - // javaCallback.cbFuncCEmitter.emitArguments(); - unit.emit(javaCallback.cbFuncBinding.getCParameterList(new StringBuilder(), false, 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_JavaCallbackGlueData+"* cb = ("+T_JavaCallbackGlueData+"*) "+userParamArgName+";"); - unit.emitln(" // C Params: "+javaCallback.cbFuncBinding.getCParameterList(new StringBuilder(), false, null).toString()); - unit.emitln(" // J Params: "+javaCallback.cbFuncBinding.getJavaParameterList(new StringBuilder()).toString()); - - final String returnStatement; - if( !cReturnType.isVoid() ) { - unit.emit(" "+cReturnType.getCName()+" _res = 0;"); - returnStatement = "return _res;"; - } else { - returnStatement = "return;"; - } - unit.emitln(" if( NULL == cb ) { fprintf(stderr, \"Info: Callback '"+jcbFriendlyBasename+"': NULL "+userParamArgName+", skipping!\\n\"); "+returnStatement+" }"); - unit.emitln(); - unit.emitln(" // Use-after-free of '*cb' possible up until after GetObjectRefType() check for a brief moment!"); - unit.emitln(" // Use a copy to avoid data-race between GetObjectRefType() and MonitorEnter()\");"); - unit.emitln(" jobject lockObj = cb->lockObj;"); - unit.emitln(" if( 0 == lockObj ) { fprintf(stderr, \"Info: Callback '"+jcbFriendlyBasename+"': NULL lock, skipping!\\n\"); "+returnStatement+" }"); - unit.emitln(); - unit.emitln(" jobjectRefType refType = (*env)->GetObjectRefType(env, lockObj);"); - unit.emitln(" if( 0 == refType ) { fprintf(stderr, \"Info: Callback '"+jcbFriendlyBasename+"': User after free(lock), skipping!\\n\"); "+returnStatement+" }"); - unit.emitln(" jint lockRes = (*env)->MonitorEnter(env, lockObj);"); - unit.emitln(" if( 0 != lockRes ) { fprintf(stderr, \"Info: Callback '"+jcbFriendlyBasename+"': MonitorEnter failed %d, skipping!\\n\", lockRes); "+returnStatement+" }"); - unit.emitln(" // synchronized block"); - /** - * Since we have acquired the lock, in-sync w/ our Java code, cb->cbFunc and cb->userParam could not have been changed! - * - unit.emitln(" refType = (*env)->GetObjectRefType(env, cb->userParam);"); - unit.emitln(" if( 0 == refType ) {"); - unit.emitln(" fprintf(stderr, \"Info: Callback '"+staticCallbackName+"(..)': User after free(userParam), skipping!\\n\");"); - unit.emitln(" lockRes = (*env)->MonitorExit(env, cb->lockObj);"); - unit.emitln(" if( 0 != lockRes ) { fprintf(stderr, \"Info: Callback '"+staticCallbackName+"(..)': MonitorExit failed %d\\n\", lockRes); }"); - unit.emitln(" "+returnStatement); - unit.emitln(" }"); - */ - if( !cReturnType.isVoid() ) { - unit.emit(" _res = ("+cReturnType.getCName()+") "); - } else { - unit.emit(" "); - } - unit.emit("(*env)->Call" + CodeGenUtils.capitalizeString( jretType.getName() ) +"Method(env, cb->cbFunc, cb->cbMethodID, "); - // javaCallback.cbFuncCEmitter.emitBodyPassCArguments(); - jcbCMethodEmitter.emitJavaCallbackBodyPassJavaArguments(javaCallback, "cb->userParam"); - unit.emitln(");"); - unit.emitln(" if( (*env)->ExceptionCheck(env) ) {"); - unit.emitln(" fprintf(stderr, \"Info: Callback '"+jcbFriendlyBasename+"': Exception in Java Callback caught:\\n\");"); - unit.emitln(" (*env)->ExceptionDescribe(env);"); - unit.emitln(" (*env)->ExceptionClear(env);"); - unit.emitln(" }"); - - // javaCallback.cbFuncCEmitter.emitBodyUserVariableAssignments(); - // javaCallback.cbFuncCEmitter.emitBodyVariablePostCallCleanup(); - // javaCallback.cbFuncCEmitter.emitBodyMapCToJNIType(-1 /* return value */, true /* addLocalVar */) - - unit.emitln(" lockRes = (*env)->MonitorExit(env, cb->lockObj);"); - unit.emitln(" if( 0 != lockRes ) { fprintf(stderr, \"Info: Callback '"+jcbFriendlyBasename+"': MonitorExit failed %d\\n\", lockRes); }"); - unit.emitln(" "+returnStatement); - } - 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.cbFuncUserParamIdx ) { - continue; - } - if( emitBodyMapCToJNIType(i, true /* addLocalVar */) ) { - ++count; - } - } - return count; - } - /* pp */ int emitJavaCallbackBodyPassJavaArguments(final JavaCallbackInfo jcbi, final String userParamVarName) { - int count = 0; - boolean needsComma = false; - for (int i = 0; i < binding.getNumArguments(); i++) { - if (needsComma) { - unit.emit(", "); - needsComma = false; - } - if( i == jcbi.cbFuncUserParamIdx ) { - unit.emit( userParamVarName ); - } else { - unit.emit( binding.getArgumentName(i) + "_jni" ); - } - needsComma = true; - ++count; - } - return count; + protected StringBuilder appendReturnType(final StringBuilder buf) { + buf.append("JNIEXPORT "); + buf.append(binding.getJavaReturnType().jniTypeName()); + buf.append(" JNICALL"); + return buf; } @Override - protected void emitName() { - unit.emitln(); // start name on new line - unit.emit(JavaEmitter.getJNIMethodNamePrefix(getJavaPackageName(), getJavaClassName())); - unit.emit("_"); + protected StringBuilder appendName(final StringBuilder buf) { + buf.append(System.lineSeparator()); // start name on new line + buf.append(JavaEmitter.getJNIMethodNamePrefix(getJavaPackageName(), getJavaClassName())); + buf.append("_"); if (isOverloadedBinding) { - unit.emit(jniMangle(binding)); + buf.append(jniMangle(binding)); } else { - unit.emit(JavaEmitter.jniMangle(getImplName())); + buf.append(JavaEmitter.jniMangle(getImplName())); } + return buf; } protected String getImplSuffix() { @@ -481,24 +356,24 @@ public class CMethodBindingEmitter extends FunctionEmitter { } @Override - protected int emitArguments() { - unit.emit("JNIEnv *env, "); + protected int appendArguments(final StringBuilder buf) { + buf.append("JNIEnv *env, "); int numEmitted = 1; // initially just the JNIEnv if (isJavaMethodStatic && !binding.hasContainingType()) { - unit.emit("jclass"); + buf.append("jclass"); } else { - unit.emit("jobject"); + buf.append("jobject"); } - unit.emit(" _unused"); + buf.append(" _unused"); ++numEmitted; if( binding.isReturnCompoundByValue() ) { - unit.emit(", jclass _clazzBuffers"); + buf.append(", jclass _clazzBuffers"); ++numEmitted; } if (binding.hasContainingType()) { // "this" argument always comes down in argument 0 as direct buffer - unit.emit(", jobject " + JavaMethodBindingEmitter.javaThisArgumentName()); + buf.append(", jobject " + JavaMethodBindingEmitter.javaThisArgumentName()); } for (int i = 0; i < binding.getNumArguments(); i++) { final JavaType javaArgType = binding.getJavaArgumentType(i); @@ -512,91 +387,47 @@ public class CMethodBindingEmitter extends FunctionEmitter { if (javaArgType.isJNIEnv() || binding.isArgumentThisPointer(i)) { continue; } - unit.emit(", "); - unit.emit(javaArgType.jniTypeName()); - unit.emit(" "); - unit.emit(binding.getArgumentName(i)); + buf.append(", "); + buf.append(javaArgType.jniTypeName()); + buf.append(" "); + buf.append(binding.getArgumentName(i)); ++numEmitted; if (javaArgType.isPrimitiveArray() || javaArgType.isNIOBuffer()) { - unit.emit(", jint " + byteOffsetArgName(i)); + buf.append(", jint " + byteOffsetArgName(i)); if(forIndirectBufferAndArrayImplementation) { - unit.emit(", jboolean " + isNIOArgName(i)); + buf.append(", jboolean " + isNIOArgName(i)); } } else if (javaArgType.isNIOBufferArray()) { - unit.emit(", jintArray " + + buf.append(", jintArray " + 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, jobject jlockObj, jlongArray jnativeUserParam"); - numEmitted+=2; - } else { - LOG.log(INFO, "BindCFunc.JavaCallback: {0}: NONE", binding.getName()); + if( null != javaCallbackEmitter ) { + numEmitted += javaCallbackEmitter.appendCAdditionalParameter(buf); } return numEmitted; } @Override + protected void emitAdditionalCode() { + if( null != javaCallbackEmitter ) { + javaCallbackEmitter.emitCAdditionalCode(unit, jcbCMethodEmitter); + } + } + + @Override protected void emitBody() { unit.emitln(" {"); // unit().emitln("printf(\" - - - - "+ getName() + getImplSuffix() +" - - - -\\n\");"); 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_JavaCallbackGlueData+"* "+nativeUserParamVarName+";"); - // unit.emit(", jstring jcallbackSignature, jobject jlockObj, jlongArray jnativeUserParam"); - unit.emitln(" if( NULL == jlockObj ) { (*env)->FatalError(env, \"Null jlockObj in '"+jcbFriendlyBasename+"'\"); }"); - unit.emitln(" if( NULL == jnativeUserParam ) { (*env)->FatalError(env, \"Null jnativeUserParam in '"+jcbFriendlyBasename+"'\"); }"); - unit.emitln(" const size_t jnativeUserParam_size = (*env)->GetArrayLength(env, jnativeUserParam);"); - unit.emitln(" if( 1 > jnativeUserParam_size ) { (*env)->FatalError(env, \"nativeUserParam size < 1 in '"+jcbFriendlyBasename+"'\"); }"); - unit.emitln(" if( NULL != "+cbFuncArgName+" ) {"); - unit.emitln(" if( NULL == "+userParamArgName+" ) { (*env)->FatalError(env, \"Null "+userParamArgName+" in '"+jcbFriendlyBasename+"'\"); }"); - unit.emitln(" "+nativeUserParamVarName+" = ("+T_JavaCallbackGlueData+"*) calloc(1, sizeof("+T_JavaCallbackGlueData+"));"); - unit.emitln(" if( NULL == "+nativeUserParamVarName+" ) { (*env)->FatalError(env, \"Can't alloc "+nativeUserParamVarName+" in '"+jcbFriendlyBasename+"'\"); }"); - unit.emitln(" "+nativeUserParamVarName+"->lockObj = (*env)->NewGlobalRef(env, jlockObj);"); - unit.emitln(" if( NULL == "+nativeUserParamVarName+"->lockObj ) { (*env)->FatalError(env, \"Failed NewGlobalRef(lock) in '"+jcbFriendlyBasename+"'\"); }"); - unit.emitln(" "+nativeUserParamVarName+"->cbFunc = (*env)->NewGlobalRef(env, "+cbFuncArgName+");"); - unit.emitln(" if( NULL == "+nativeUserParamVarName+"->cbFunc ) { (*env)->FatalError(env, \"Failed NewGlobalRef(func) in '"+jcbFriendlyBasename+"'\"); }"); - unit.emitln(" "+nativeUserParamVarName+"->userParam = (*env)->NewGlobalRef(env, "+userParamArgName+");"); - unit.emitln(" if( NULL == "+nativeUserParamVarName+"->userParam ) { (*env)->FatalError(env, \"Failed NewGlobalRef(userParam) in '"+jcbFriendlyBasename+"'\"); }"); - unit.emitln(" {"); - unit.emitln(" jclass cbClazz = (*env)->GetObjectClass(env, "+nativeUserParamVarName+"->cbFunc);"); - unit.emitln(" if( NULL == cbClazz ) { (*env)->FatalError(env, \"Failed GetObjectClass in '"+jcbFriendlyBasename+"'\"); }"); - unit.emitln(" const char* callbackSignature = (*env)->GetStringUTFChars(env, jcallbackSignature, (jboolean*)NULL);"); - unit.emitln(" if( NULL == callbackSignature ) { (*env)->FatalError(env, \"Failed callbackSignature in '"+jcbFriendlyBasename+"'\"); }"); - 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 '"+jcbFriendlyBasename+"'\"); }"); - 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);"); - if( DEBUG_JAVACALLBACK ) { - unit.emitln(" fprintf(stderr, \"YYY user %p -> native %p\\n\", "+userParamArgName+", "+nativeUserParamVarName+");"); - } - unit.emitln(" }"); - unit.emitln(); + if( null != javaCallbackEmitter ) { + javaCallbackEmitter.emitCSetFuncPreCall(unit); } emitBodyCallCFunction(); emitBodyUserVariableAssignments(); @@ -606,24 +437,6 @@ public class CMethodBindingEmitter extends FunctionEmitter { } unit.emitln("}"); unit.emitln(); - if( null != jcb ) { - final String capIfaceName = CodeGenUtils.capitalizeString( getInterfaceName() ); - unit.emitln("JNIEXPORT void JNICALL"); - unit.emit(JavaEmitter.getJNIMethodNamePrefix(getJavaPackageName(), getJavaClassName())); - unit.emitln("_release"+capIfaceName+"Impl(JNIEnv *env, jobject _unused, jlong jnativeUserParam) {"); - unit.emitln(" // already locked"); - unit.emitln(" "+T_JavaCallbackGlueData+"* nativeUserParam = ("+T_JavaCallbackGlueData+"*) (intptr_t) jnativeUserParam;"); - unit.emitln(" if( NULL != nativeUserParam ) {"); - unit.emitln(" (*env)->DeleteGlobalRef(env, nativeUserParam->lockObj);"); - unit.emitln(" (*env)->DeleteGlobalRef(env, nativeUserParam->cbFunc);"); - unit.emitln(" (*env)->DeleteGlobalRef(env, nativeUserParam->userParam);"); - unit.emitln(" // Ensure even w/ use-after-free jobject refs are NULL and invalid to avoid accidental reuse."); - unit.emitln(" memset(nativeUserParam, 0, sizeof("+T_JavaCallbackGlueData+"));"); - unit.emitln(" free(nativeUserParam);"); - unit.emitln(" }"); - unit.emitln("}"); - unit.emitln(); - } } protected void emitBodyVariableDeclarations() { @@ -1173,9 +986,8 @@ 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"); + if( null != javaCallbackEmitter ) { + javaCallbackEmitter.emitCOptArgumentSuffix(unit, i); } } } @@ -1241,7 +1053,7 @@ public class CMethodBindingEmitter extends FunctionEmitter { * @param addLocalVar if true, emit instantiating the local JNI variable. * @return true if a non-void result has been produced, otherwise false */ - protected boolean emitBodyMapCToJNIType(final int argIdx, final boolean addLocalVar) + public boolean emitBodyMapCToJNIType(final int argIdx, final boolean addLocalVar) { // WARNING: this code assumes that the return type has already been // typedef-resolved. @@ -1436,12 +1248,11 @@ public class CMethodBindingEmitter extends FunctionEmitter { final StringBuilder buf = new StringBuilder(); buf.append(JavaEmitter.jniMangle(getImplName())); buf.append(getImplSuffix()); - if( null == this.javaCallback ) { + if( null == javaCallbackEmitter ) { 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 + appendJNIMangledArgs(binding, forIndirectBufferAndArrayImplementation, buf); + if( null != javaCallbackEmitter ) { + javaCallbackEmitter.appendCAdditionalJNIDescriptor(buf); } } return buf.toString(); @@ -1454,13 +1265,13 @@ public class CMethodBindingEmitter extends FunctionEmitter { * @param buf * @return */ - public static StringBuilder getJNIMangledArgs(final MethodBinding binding, final boolean forIndirectBufferAndArrayImplementation, final StringBuilder buf) { + public static StringBuilder appendJNIMangledArgs(final MethodBinding binding, final boolean forIndirectBufferAndArrayImplementation, final StringBuilder buf) { if (binding.isReturnCompoundByValue()) { - getJNIMangledArg(Class.class, buf, true); + JavaType.appendJNIDescriptor(buf, Class.class, true); } if (binding.hasContainingType()) { // "this" argument always comes down in argument 0 as direct buffer - getJNIMangledArg(java.nio.ByteBuffer.class, buf, true); + JavaType.appendJNIDescriptor(buf, java.nio.ByteBuffer.class, true); } for (int i = 0; i < binding.getNumArguments(); i++) { if (binding.isArgumentThisPointer(i)) { @@ -1477,31 +1288,31 @@ public class CMethodBindingEmitter extends FunctionEmitter { } else { Class<?> c = type.getJavaClass(); if (c != null) { - getJNIMangledArg(c, buf, false); + JavaType.appendJNIDescriptor(buf, c, false); // If Buffer offset arguments were added, we need to mangle the JNI for the // extra arguments if (type.isNIOBuffer()) { - getJNIMangledArg(Integer.TYPE, buf, false); + JavaType.appendJNIDescriptor(buf, Integer.TYPE, false); if(forIndirectBufferAndArrayImplementation) { - getJNIMangledArg(Boolean.TYPE, buf, false); + JavaType.appendJNIDescriptor(buf, Boolean.TYPE, false); } } else if (type.isNIOBufferArray()) { final int[] intArrayType = new int[0]; c = intArrayType.getClass(); - getJNIMangledArg(c , buf, true); + JavaType.appendJNIDescriptor(buf, c , true); } if (type.isPrimitiveArray()) { - getJNIMangledArg(Integer.TYPE, buf, false); + JavaType.appendJNIDescriptor(buf, Integer.TYPE, false); } } else if (type.isNamedClass()) { buf.append(type.getJNIMethodDesciptor()); } else if (type.isCompoundTypeWrapper()) { // Mangle wrappers for C structs as ByteBuffer - getJNIMangledArg(java.nio.ByteBuffer.class, buf, true); + JavaType.appendJNIDescriptor(buf, java.nio.ByteBuffer.class, true); } else if (type.isArrayOfCompoundTypeWrappers()) { // Mangle arrays of C structs as ByteBuffer[] final java.nio.ByteBuffer[] tmp = new java.nio.ByteBuffer[0]; - getJNIMangledArg(tmp.getClass(), buf, true); + JavaType.appendJNIDescriptor(buf, tmp.getClass(), true); } else if (type.isJNIEnv()) { // These are not exposed at the Java level } else { @@ -1514,54 +1325,6 @@ public class CMethodBindingEmitter extends FunctionEmitter { return buf; } - public static void getJNIMangledArg(final Class<?> c, final StringBuilder res, final boolean syntheticArgument) { - if (c.isPrimitive()) { - if (c == Boolean.TYPE) res.append("Z"); - else if (c == Byte.TYPE) res.append("B"); - else if (c == Character.TYPE) res.append("C"); - else if (c == Short.TYPE) res.append("S"); - else if (c == Integer.TYPE) res.append("I"); - else if (c == Long.TYPE) res.append("J"); - else if (c == Float.TYPE) res.append("F"); - else if (c == Double.TYPE) res.append("D"); - else throw new RuntimeException("Illegal primitive type \"" + c.getName() + "\""); - } else { - // Arrays and NIO Buffers are always passed down as java.lang.Object. - // The only arrays that show up as true arrays in the signature - // are the synthetic byte offset arrays created when passing - // down arrays of direct Buffers. Compound type wrappers are - // passed down as ByteBuffers (no good reason, just to avoid - // accidental conflation) so we mangle them differently. - if (syntheticArgument) { - if (c.isArray()) { - res.append("_3"); - final Class<?> componentType = c.getComponentType(); - // Handle arrays of compound type wrappers differently for - // convenience of the Java-level glue code generation - getJNIMangledArg(componentType, res, - (componentType == java.nio.ByteBuffer.class)); - } else { - res.append("L"); - res.append(c.getName().replace('.', '_')); - res.append("_2"); - } - } else { - if (c.isArray()) { - res.append("_3"); - getJNIMangledArg(c.getComponentType(), res, false); - } else if (c == java.lang.String.class) { - res.append("L"); - res.append(c.getName().replace('.', '_')); - res.append("_2"); - } else { - res.append("L"); - res.append("java_lang_Object"); - res.append("_2"); - } - } - } - } - private void emitOutOfMemoryCheck(final String varName, final String errorMessage) { unit.emitln(" if ( NULL == " + varName + " ) {"); unit.emitln(" (*env)->ThrowNew(env, (*env)->FindClass(env, \"java/lang/OutOfMemoryError\"),"); diff --git a/src/java/com/jogamp/gluegen/FunctionEmitter.java b/src/java/com/jogamp/gluegen/FunctionEmitter.java index a089a41..5037fc4 100644 --- a/src/java/com/jogamp/gluegen/FunctionEmitter.java +++ b/src/java/com/jogamp/gluegen/FunctionEmitter.java @@ -114,6 +114,7 @@ public abstract class FunctionEmitter { * Emit the function to the {@link #getUnit()} */ public final void emit() { + emitAdditionalCode(); emitDocComment(); //output.println(" // Emitter: " + getClass().getName()); emitSignature(); @@ -139,6 +140,7 @@ public abstract class FunctionEmitter { */ public CommentEmitter getCommentEmitter() { return commentEmitter; } + protected void emitAdditionalCode() { } protected void emitDocComment() { if (commentEmitter != null) { @@ -154,32 +156,42 @@ public abstract class FunctionEmitter { } } - protected void emitSignature() { + protected final void emitSignature() { + unit.emit(appendSignature(new StringBuilder()).toString()); + } - unit.emit(getBaseIndentString()); // indent method + protected StringBuilder appendSignature(final StringBuilder buf) { + buf.append(getBaseIndentString()); // indent method - final int numEmitted = emitModifiers(); + final int numEmitted = appendModifiers(buf); if (numEmitted > 0) { - unit.emit(" "); + buf.append(" "); } - emitReturnType(); - unit.emit(" "); + appendReturnType(buf); + buf.append(" "); - emitName(); - unit.emit("("); + appendName(buf); + buf.append("("); - emitArguments(); - unit.emit(")"); + appendArguments(buf); + buf.append(")"); + return buf; } - protected int emitModifiers() { + protected final int emitModifiers() { + final StringBuilder buf = new StringBuilder(); + final int n = appendModifiers(buf); + unit.emit(buf.toString()); + return n; + } + protected int appendModifiers(final StringBuilder buf) { int numEmitted = 0; for (final Iterator<EmissionModifier> it = getModifiers(); it.hasNext(); ) { - unit.emit(it.next().toString()); + buf.append(it.next().toString()); ++numEmitted; if (it.hasNext()) { - unit.emit(" "); + buf.append(" "); } } return numEmitted; @@ -190,10 +202,23 @@ public abstract class FunctionEmitter { protected String getCommentStartString() { return "/* "; } protected String getCommentEndString() { return " */"; } - protected abstract void emitReturnType(); - protected abstract void emitName(); + protected final void emitReturnType() { + unit.emit(appendReturnType(new StringBuilder()).toString()); + } + protected abstract StringBuilder appendReturnType(StringBuilder buf); + protected final void emitName() { + unit.emit(appendName(new StringBuilder()).toString()); + } + protected abstract StringBuilder appendName(StringBuilder buf); + /** Returns the number of arguments emitted. */ + protected final int emitArguments() { + final StringBuilder buf = new StringBuilder(); + final int n = appendArguments(buf); + unit.emit(buf.toString()); + return n; + } /** Returns the number of arguments emitted. */ - protected abstract int emitArguments(); + protected abstract int appendArguments(StringBuilder buf); protected abstract void emitBody(); public static class EmissionModifier { diff --git a/src/java/com/jogamp/gluegen/JavaCallbackEmitter.java b/src/java/com/jogamp/gluegen/JavaCallbackEmitter.java new file mode 100644 index 0000000..96e1e77 --- /dev/null +++ b/src/java/com/jogamp/gluegen/JavaCallbackEmitter.java @@ -0,0 +1,666 @@ +/** + * 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; + +import com.jogamp.gluegen.JavaConfiguration.JavaCallbackInfo; +import com.jogamp.gluegen.cgram.types.Type; + +public final class JavaCallbackEmitter { + final JavaConfiguration cfg; + final MethodBinding binding; + final String setFuncSignature; + final JavaCallbackInfo info; + final String capIfaceName; + final String lowIfaceName; + final String lockInstanceName; + final String dataMapInstanceName; + final String dataInstanceName; + final String DataClassName; + final String fqUsrParamClassName; + final JavaType cbFuncJavaReturnType; + final String jcbNextIDVarName; + + final String setFuncCBArgName; + final Type setFuncUserParamCType; + final JavaType setFuncUserParamJType; + final String setFuncUserParamTypeName; + final String setFuncUserParamArgName; + + final boolean customKeyClass; + final String KeyClassName; + final boolean useDataMap; + + public JavaCallbackEmitter(final JavaConfiguration cfg, final MethodBinding mb, final JavaCallbackInfo javaCallback, final String setFuncSignature) { + this.cfg = cfg; + this.binding = mb; + this.setFuncSignature = setFuncSignature; + this.info = javaCallback; + + capIfaceName = CodeGenUtils.capitalizeString( mb.getInterfaceName() ); + lowIfaceName = CodeGenUtils.decapitalizeString( mb.getInterfaceName() ); + lockInstanceName = lowIfaceName+"Lock"; + dataMapInstanceName = lowIfaceName+"DataMap"; + dataInstanceName = lowIfaceName+"Data"; + DataClassName = capIfaceName+"Data"; + fqUsrParamClassName = cfg.packageName()+"."+cfg.className()+"."+DataClassName; + cbFuncJavaReturnType = javaCallback.cbFuncBinding.getJavaReturnType(); + jcbNextIDVarName = "NEXT_"+capIfaceName+"_ID"; + + setFuncCBArgName = binding.getArgumentName(javaCallback.setFuncCBParamIdx); + setFuncUserParamCType = mb.getCArgumentType(javaCallback.setFuncUserParamIdx); + setFuncUserParamJType = mb.getJavaArgumentType(javaCallback.setFuncUserParamIdx); + setFuncUserParamTypeName = setFuncUserParamJType.getName(); + setFuncUserParamArgName = binding.getArgumentName(javaCallback.setFuncUserParamIdx); + + if( null != javaCallback.setFuncKeyClassName ) { + customKeyClass = true;; + KeyClassName = javaCallback.setFuncKeyClassName; + useDataMap = true; + } else { + customKeyClass = false; + KeyClassName = capIfaceName+"Key"; + useDataMap = javaCallback.setFuncKeyIndices.size() > 0; + } + } + + public void emitJavaSetFuncPreCall(final CodeUnit unit) { + unit.emitln(" synchronized( "+lockInstanceName+" ) {"); + unit.emit (" final long nativeUserParam = "); + if( setFuncUserParamJType.isLong() ) { + unit.emitln(" "+setFuncUserParamArgName+";"); + } else if( setFuncUserParamJType.isCompoundTypeWrapper() ) { + unit.emitln(" null != "+setFuncUserParamArgName+" ? "+setFuncUserParamArgName+".getDirectBufferAddress() : 0;"); + } else { + unit.emitln(""+jcbNextIDVarName+"++;"); + unit.emitln(" if( 0 >= "+jcbNextIDVarName+" ) { "+jcbNextIDVarName+" = 1; }"); + } + unit.emitln(" if( null != "+setFuncCBArgName+" ) {"); + unit.emitln(" add"+capIfaceName+"("+binding.getJavaCallSelectArguments(new StringBuilder(), info.setFuncKeyIndices, true).toString()+ + "new "+DataClassName+"("+setFuncCBArgName+", "+setFuncUserParamArgName+"));"); + unit.emitln(" }"); + unit.emitln(); + } + + public void emitJavaSetFuncPostCall(final CodeUnit unit) { + unit.emitln(" if( null == "+setFuncCBArgName+" ) {"); + unit.emitln(" // callback released (null func) -> release a previously mapped instance "); + if( useDataMap ) { + unit.emitln(" release"+capIfaceName+"( new "+KeyClassName+"( "+binding.getJavaCallSelectArguments(new StringBuilder(), info.setFuncKeyIndices, false).toString()+" ) );"); + } else { + unit.emitln(" release"+capIfaceName+"();"); + } + unit.emitln(" }"); + unit.emitln(" } // synchronized "); + } + + public void emitJavaAdditionalCode(final CodeUnit unit, final boolean isInterface) { + if( isInterface ) { + if( useDataMap ) { + if( !customKeyClass && !info.keyClassEmitted ) { + emitJavaKeyClass(unit); + unit.emitln(); + info.keyClassEmitted = true; + } + emitJavaBriefAPIDoc(unit, "Returns ", "set of ", "", "for "); + unit.emitln(" public Set<"+KeyClassName+"> get"+capIfaceName+"Keys();"); + unit.emitln(); + emitJavaBriefAPIDoc(unit, "Returns ", "whether callback ", "if callback ", "is mapped for "); + unit.emitln(" public boolean is"+capIfaceName+"Mapped("+KeyClassName+" key);"); + unit.emitln(); + emitJavaBriefAPIDoc(unit, "Returns "+info.cbFuncTypeName+" callback ", "mapped to ", "", "for "); + unit.emitln(" public "+info.cbFuncTypeName+" get"+capIfaceName+"("+KeyClassName+" key);"); + unit.emitln(); + emitJavaBriefAPIDoc(unit, "Returns user-param ", "mapped to ", "", "for "); + unit.emitln(" public Object get"+capIfaceName+"UserParam("+KeyClassName+" key);"); + unit.emitln(); + emitJavaBriefAPIDoc(unit, "Releases all callback data ", "mapped via ", "", "skipping toolkit API. Favor passing `null` callback ref to "); + unit.emitln(" public int releaseAll"+capIfaceName+"();"); + unit.emitln(); + emitJavaBriefAPIDoc(unit, "Releases callback data ", "mapped to ", "", "skipping toolkit API. Favor passing `null` callback ref to "); + unit.emitln(" public void release"+capIfaceName+"("+KeyClassName+" key);"); + unit.emitln(); + } else { + emitJavaBriefAPIDoc(unit, "Returns ", "whether callback ", "if callback ", "is mapped for "); + unit.emitln(" public boolean is"+capIfaceName+"Mapped();"); + unit.emitln(); + emitJavaBriefAPIDoc(unit, "Returns "+info.cbFuncTypeName+" callback ", "mapped to ", "", "for "); + unit.emitln(" public "+info.cbFuncTypeName+" get"+capIfaceName+"();"); + unit.emitln(); + emitJavaBriefAPIDoc(unit, "Returns user-param ", "mapped to ", "", "for "); + unit.emitln(" public Object get"+capIfaceName+"UserParam();"); + unit.emitln(); + emitJavaBriefAPIDoc(unit, "Releases callback data ", "", "", "skipping toolkit API. Favor passing `null` callback ref to "); + unit.emitln(" public void release"+capIfaceName+"();"); + unit.emitln(); + } + } else { + if( useDataMap ) { + if( !customKeyClass && !info.keyClassEmitted ) { + emitJavaKeyClass(unit); + unit.emitln(); + info.keyClassEmitted = true; + } + emitJavaBriefAPIDoc(unit, "Returns ", "set of ", "", "for "); + unit.emitln(" public final Set<"+KeyClassName+"> get"+capIfaceName+"Keys() {"); + unit.emitln(" synchronized( "+lockInstanceName+" ) {"); + unit.emitln(" return "+dataMapInstanceName+".keySet();"); + unit.emitln(" }"); + unit.emitln(" }"); + unit.emitln(); + emitJavaBriefAPIDoc(unit, "Returns ", "whether callback ", "if callback ", "is mapped for "); + unit.emitln(" public final boolean is"+capIfaceName+"Mapped("+KeyClassName+" key) {"); + unit.emitln(" synchronized( "+lockInstanceName+" ) {"); + unit.emitln(" return null != "+dataMapInstanceName+".get(key);"); + unit.emitln(" }"); + unit.emitln(" }"); + unit.emitln(); + + emitJavaBriefAPIDoc(unit, "Returns "+info.cbFuncTypeName+" callback ", "mapped to ", "", "for "); + unit.emitln(" public final "+info.cbFuncTypeName+" get"+capIfaceName+"("+KeyClassName+" key) {"); + unit.emitln(" synchronized( "+lockInstanceName+" ) {"); + unit.emitln(" final "+DataClassName+" value = "+dataMapInstanceName+".get(key);"); + unit.emitln(" return null != value ? value.func : null;"); + unit.emitln(" }"); + unit.emitln(" }"); + unit.emitln(); + + emitJavaBriefAPIDoc(unit, "Returns user-param ", "mapped to ", "", "for "); + unit.emitln(" public final Object get"+capIfaceName+"UserParam("+KeyClassName+" key) {"); + unit.emitln(" synchronized( "+lockInstanceName+" ) {"); + unit.emitln(" final "+DataClassName+" value = "+dataMapInstanceName+".get(key);"); + unit.emitln(" return null != value ? value.param : null;"); + unit.emitln(" }"); + unit.emitln(" }"); + unit.emitln(); + emitJavaBriefAPIDoc(unit, "Releases all callback data ", "mapped via ", "", "skipping toolkit API. Favor passing `null` callback ref to "); + unit.emitln(" public final int releaseAll"+capIfaceName+"() {"); + unit.emitln(" synchronized( "+lockInstanceName+" ) {"); + unit.emitln(" final Set<"+KeyClassName+"> keySet = "+dataMapInstanceName+".keySet();"); + unit.emitln(" final "+KeyClassName+"[] keys = keySet.toArray(new "+KeyClassName+"[keySet.size()]);"); + unit.emitln(" for(int i=0; i<keys.length; ++i) {"); + unit.emitln(" final "+KeyClassName+" key = keys[i];"); + unit.emitln(" release"+capIfaceName+"(key);"); + unit.emitln(" }"); + unit.emitln(" return keys.length;"); + unit.emitln(" }"); + unit.emitln(" }"); + unit.emitln(); + emitJavaBriefAPIDoc(unit, "Releases callback data ", "mapped to ", "", "skipping toolkit API. Favor passing `null` callback ref to "); + unit.emitln(" public final void release"+capIfaceName+"("+KeyClassName+" key) {"); + unit.emitln(" synchronized( "+lockInstanceName+" ) {"); + unit.emitln(" /* final "+DataClassName+" value = */ "+dataMapInstanceName+".remove(key);"); + unit.emitln(" }"); + unit.emitln(" }"); + unit.emitln(); + } else { + emitJavaBriefAPIDoc(unit, "Returns ", "whether callback ", "if callback ", "is mapped for "); + unit.emitln(" public final boolean is"+capIfaceName+"Mapped() {"); + unit.emitln(" synchronized( "+lockInstanceName+" ) {"); + unit.emitln(" return null != "+dataInstanceName+";"); + unit.emitln(" }"); + unit.emitln(" }"); + unit.emitln(); + + emitJavaBriefAPIDoc(unit, "Returns "+info.cbFuncTypeName+" callback ", "mapped to ", "", "for "); + unit.emitln(" public final "+info.cbFuncTypeName+" get"+capIfaceName+"() {"); + unit.emitln(" synchronized( "+lockInstanceName+" ) {"); + unit.emitln(" final "+DataClassName+" value = "+dataInstanceName+";"); + unit.emitln(" return null != value ? value.func : null;"); + unit.emitln(" }"); + unit.emitln(" }"); + unit.emitln(); + + emitJavaBriefAPIDoc(unit, "Returns user-param ", "mapped to ", "", "for "); + unit.emitln(" public final Object get"+capIfaceName+"UserParam() {"); + unit.emitln(" synchronized( "+lockInstanceName+" ) {"); + unit.emitln(" final "+DataClassName+" value = "+dataInstanceName+";"); + unit.emitln(" return null != value ? value.param : null;"); + unit.emitln(" }"); + unit.emitln(" }"); + unit.emitln(); + + emitJavaBriefAPIDoc(unit, "Releases callback data ", "", "", "skipping toolkit API. Favor passing `null` callback ref to "); + unit.emitln(" public final void release"+capIfaceName+"() {"); + unit.emitln(" synchronized( "+lockInstanceName+" ) {"); + unit.emitln(" // final "+DataClassName+" value = "+dataInstanceName+";"); + unit.emitln(" "+dataInstanceName+" = null;"); + unit.emitln(" }"); + unit.emitln(" }"); + unit.emitln(); + } + unit.emitln(" private final void add"+capIfaceName+"("+binding.getJavaSelectParameter(new StringBuilder(), info.setFuncKeyIndices, true).toString()+DataClassName+" value) {"); + if( useDataMap ) { + unit.emitln(" final "+KeyClassName+" key = new "+KeyClassName+"("+binding.getJavaCallSelectArguments(new StringBuilder(), info.setFuncKeyIndices, false).toString()+");"); + unit.emitln(" /* final "+DataClassName+" old = */ "+dataMapInstanceName+".put(key, value);"); + } else { + unit.emitln(" // final "+DataClassName+" old = "+dataInstanceName+";"); + unit.emitln(" "+dataInstanceName+" = value;"); + } + unit.emitln(" }"); + unit.emitln(); + if( !cfg.emittedJavaCallbackUserParamClasses.contains(fqUsrParamClassName) ) { + emitJavaDataClass(unit); + cfg.emittedJavaCallbackUserParamClasses.add(fqUsrParamClassName); + } + if( useDataMap ) { + unit.emitln(" private static final Map<"+KeyClassName+", "+DataClassName+"> "+dataMapInstanceName+" = new HashMap<"+KeyClassName+", "+DataClassName+">();"); + } else { + unit.emitln(" private static "+DataClassName+" "+dataInstanceName+" = null;"); + } + unit.emitln(" private static long "+jcbNextIDVarName+" = 1;"); + unit.emitln(" private static final Object "+lockInstanceName+" = new Object();"); + unit.emitln(); + emitJavaStaticCallback(unit); + } + } + + private final void emitJavaBriefAPIDoc(final CodeUnit unit, final String actionText, final String relationToKey, final String noKeyText, final String relationToFunc) { + unit.emit(" /** "+actionText); + if( info.setFuncKeyIndices.size() > 0 ) { + unit.emit(relationToKey); + unit.emit("Key { "+binding.getJavaSelectParameter(new StringBuilder(), info.setFuncKeyIndices, false).toString()+" } "); + } else { + unit.emit(noKeyText); + } + unit.emit(relationToFunc); + unit.emitln("<br> <code>"+setFuncSignature+"</code> */"); + } + + private final void emitJavaKeyClass(final CodeUnit unit) { + emitJavaBriefAPIDoc(unit, "", "", "", "for "); + unit.emitln(" public static class "+KeyClassName+" {"); + binding.forEachParameter( ( final int idx, final int consumedCount, final Type cType, final JavaType jType, final String name ) -> { + if( !cType.isVoid() && info.setFuncKeyIndices.contains(idx) ) { + unit.emitln(" public final "+jType+" "+name+";"); + return true; + } else { + return false; + } + } ); + unit.emitln(" public "+KeyClassName+"("+binding.getJavaSelectParameter(new StringBuilder(), info.setFuncKeyIndices, false).toString()+") {"); + binding.forEachParameter( ( final int idx, final int consumedCount, final Type cType, final JavaType jType, final String name ) -> { + if( !cType.isVoid() && info.setFuncKeyIndices.contains(idx) ) { + unit.emitln(" this."+name+" = "+name+";"); + return true; + } else { + return false; + } + } ); + unit.emitln(" }"); + unit.emitln(" @Override"); + unit.emitln(" public boolean equals(final Object o) {"); + unit.emitln(" if( this == o ) {"); + unit.emitln(" return true;"); + unit.emitln(" }"); + unit.emitln(" if( !(o instanceof "+KeyClassName+") ) {"); + unit.emitln(" return false;"); + unit.emitln(" }"); + { + final int count = binding.forEachParameter( ( final int idx, final int consumedCount, final Type cType, final JavaType jType, final String name ) -> { + if( !cType.isVoid() && info.setFuncKeyIndices.contains(idx) ) { + if( 0 == consumedCount ) { + unit.emitln(" final "+KeyClassName+" o2 = ("+KeyClassName+")o;"); + unit.emit (" return "); + } else { + unit.emitln(" &&"); + unit.emit (" "); + } + if( jType.isPrimitive() || idx == info.setFuncUserParamIdx ) { + unit.emit(name+" == o2."+name); + } else { + unit.emit(name+".equals( o2."+name+" )"); + } + return true; + } else { + return false; + } + } ); + if( 0 == count ) { + unit.emit(" return true"); + } + unit.emitln(";"); + } + unit.emitln(" }"); + unit.emitln(" @Override"); + unit.emitln(" public int hashCode() {"); + { + final int count = binding.forEachParameter( ( final int idx, final int consumedCount, final Type cType, final JavaType jType, final String name ) -> { + if( !cType.isVoid() && info.setFuncKeyIndices.contains(idx) ) { + if( 0 == consumedCount ) { + unit.emitln(" // 31 * x == (x << 5) - x"); + unit.emit (" int hash = "); + } else { + unit.emit (" hash = ((hash << 5) - hash) + "); + } + if( jType.isPrimitive() ) { + if( jType.isLong() ) { + unit.emitln("HashUtil.getAddrHash32_EqualDist( "+name+" );"); + } else { + unit.emitln(name+";"); + } + } else { + if( idx == info.setFuncUserParamIdx ) { + unit.emitln("System.identityHashCode( "+name+" );"); + } else { + unit.emitln(name+".hashCode();"); + } + } + return true; + } else { + return false; + } + } ); + if( 0 == count ) { + unit.emitln(" return 0;"); + } else { + unit.emitln(" return hash;"); + } + } + unit.emitln(" }"); + unit.emitln(" }"); + } + + private final void emitJavaDataClass(final CodeUnit unit) { + unit.emitln(" private static class "+DataClassName+" {"); + unit.emitln(" // userParamArgCType "+setFuncUserParamCType); + unit.emitln(" // userParamArgJType "+setFuncUserParamJType); + unit.emitln(" final "+info.cbFuncTypeName+" func;"); + unit.emitln(" final "+setFuncUserParamTypeName+" param;"); + unit.emitln(" "+DataClassName+"("+info.cbFuncTypeName+" func, "+setFuncUserParamTypeName+" param) {"); + unit.emitln(" this.func = func;"); + unit.emitln(" this.param = param;"); + unit.emitln(" }"); + unit.emitln(" }"); + } + + public final String getJavaStaticCallbackSignature() { + final StringBuilder buf = new StringBuilder(); + buf.append("("); + info.cbFuncBinding.forEachParameter( ( final int idx, final int consumedCount, final Type cType, final JavaType jType, final String name ) -> { + if( !cType.isVoid() ) { + if( idx == info.cbFuncUserParamIdx ) { + buf.append("J"); + } else { + buf.append(jType.getDescriptor()); + } + return true; + } else { + return false; + } + } ); + buf.append(")"); + buf.append(cbFuncJavaReturnType.getDescriptor()); + return buf.toString(); + } + + public final int appendJavaAdditionalJNIParameter(final StringBuilder buf) { + buf.append("Class<?> clazz, String callbackSignature, long nativeUserParam"); + return 3; + } + public final int appendJavaAdditionalJNIArguments(final StringBuilder buf) { + buf.append("this.getClass(), \"" + getJavaStaticCallbackSignature()+ "\", nativeUserParam"); + return 3; + } + private final void emitJavaStaticCallback(final CodeUnit unit) { + unit.emitln(" /** Static callback invocation, dispatching to "+info.cbSimpleClazzName+" for callback <br> <code>"+ + info.cbFuncType.toString(info.cbFuncTypeName, false, true)+"</code> */"); + unit.emit (" /* pp */ static "+cbFuncJavaReturnType.getName()+" invoke"+capIfaceName+"("); + final boolean[] mapNativePtrToCompound = { false }; + final JavaType[] origUserParamJType = { null }; + info.cbFuncBinding.forEachParameter( ( final int idx, final int consumedCount, final Type cType, final JavaType jType, final String name ) -> { + if( !cType.isVoid() ) { + if( 0 < consumedCount ) { unit.emit(", "); } + if( idx == info.cbFuncUserParamIdx ) { + unit.emit("long nativeUserParamPtr"); + if( jType.isCompoundTypeWrapper() ) { + mapNativePtrToCompound[0] = true; + origUserParamJType[0] = jType; + } + } else { + unit.emit(jType+" "+name); + } + return true; + } else { + return false; + } + } ); + unit.emitln(") {"); + if( mapNativePtrToCompound[0] ) { + unit.emitln(" final "+origUserParamJType[0]+" "+info.cbFuncUserParamName+" = "+origUserParamJType[0]+".derefPointer(nativeUserParamPtr);"); + } + if( useDataMap ) { + unit.emitln(" final "+DataClassName+" value;"); + } else { + unit.emitln(" final "+DataClassName+" value;"); + } + unit.emitln(" synchronized( "+lockInstanceName+" ) {"); + if( useDataMap ) { + unit.emitln(" final "+KeyClassName+" key = new "+KeyClassName+"("+binding.getJavaCallSelectArguments(new StringBuilder(), info.setFuncKeyIndices, false).toString()+");"); + unit.emitln(" value = "+dataMapInstanceName+".get(key);"); + } else { + unit.emitln(" value = "+dataInstanceName+";"); + } + unit.emitln(" }"); + unit.emitln(" if( null == value ) {"); + if( !cbFuncJavaReturnType.isVoid() ) { + unit.emitln(" return 0;"); + } else { + unit.emitln(" return;"); + } + unit.emitln(" }"); + if( !cbFuncJavaReturnType.isVoid() ) { + unit.emit(" return "); + } else { + unit.emit(" "); + } + unit.emit("value.func.callback("); + info.cbFuncBinding.forEachParameter( ( final int idx, final int consumedCount, final Type cType, final JavaType jType, final String name ) -> { + if( !cType.isVoid() ) { + if( 0 < consumedCount ) { unit.emit(", "); } + if( idx == info.cbFuncUserParamIdx && !mapNativePtrToCompound[0] ) { + unit.emit("value.param"); + } else { + unit.emit(name); + } + return true; + } else { + return false; + } + } ); + unit.emitln(");"); + unit.emitln(" }"); + unit.emitln(); + } + + // + // C JNI Code .. + // + + public int appendCAdditionalParameter(final StringBuilder buf) { + buf.append(", jclass clazz, jstring jcallbackSignature, jlong jnativeUserParam"); + return 3; + } + + public void emitCOptArgumentSuffix(final CodeUnit unit, final int argIdx) { + if( ( argIdx == info.setFuncCBParamIdx || argIdx == info.setFuncUserParamIdx ) ) { + unit.emit("_native"); + } + } + + public void appendCAdditionalJNIDescriptor(final StringBuilder buf) { + JavaType.appendJNIDescriptor(buf, Class.class, false); // to account for the additional 'jclass clazz' parameter + JavaType.appendJNIDescriptor(buf, String.class, false); // to account for the additional 'jstring jcallbackSignature' parameter + JavaType.appendJNIDescriptor(buf, long.class, false); // to account for the additional 'long nativeUserParam' parameter + } + + public void emitCSetFuncPreCall(final CodeUnit unit) { + final String jcbNativeBasename = CodeGenUtils.capitalizeString( info.setFuncName ); + final String jcbFriendlyBasename = info.setFuncName+"("+info.cbSimpleClazzName+")"; + final String staticBindingMethodName = "invoke"+jcbNativeBasename; + final String staticBindingClazzVarName = "clazz"+jcbNativeBasename; + final String staticBindingMethodIDVarName = "method"+jcbNativeBasename; + final String cbFuncArgName = binding.getArgumentName(info.setFuncCBParamIdx); + final String userParamTypeName = info.cbFuncUserParamType.getCName(); + final String userParamArgName = binding.getArgumentName(info.setFuncUserParamIdx); + final String nativeCBFuncVarName = cbFuncArgName+"_native"; + final String nativeUserParamVarName = userParamArgName+"_native"; + unit.emitln(); + unit.emitln(" // JavaCallback handling"); + unit.emitln(" if( NULL == clazz ) { (*env)->FatalError(env, \"NULL clazz passed to '"+jcbFriendlyBasename+"'\"); }"); + unit.emitln(" "+info.cbFuncTypeName+" "+nativeCBFuncVarName+";"); + unit.emitln(" "+userParamTypeName+"* "+nativeUserParamVarName+";"); + unit.emitln(" if( NULL != "+cbFuncArgName+" ) {"); + unit.emitln(" const char* callbackSignature = (*env)->GetStringUTFChars(env, jcallbackSignature, (jboolean*)NULL);"); + unit.emitln(" if( NULL == callbackSignature ) { (*env)->FatalError(env, \"Failed callbackSignature in '"+jcbFriendlyBasename+"'\"); }"); + unit.emitln(" jmethodID cbMethodID = (*env)->GetStaticMethodID(env, clazz, \""+staticBindingMethodName+"\", callbackSignature);"); + unit.emitln(" if( NULL == cbMethodID ) {"); + unit.emitln(" char cmsg[400];"); + unit.emitln(" snprintf(cmsg, 400, \"Failed GetStaticMethodID of '"+staticBindingMethodName+"(%s)' in '"+jcbFriendlyBasename+"'\", callbackSignature);"); + unit.emitln(" (*env)->FatalError(env, cmsg);"); + unit.emitln(" }"); + unit.emitln(" (*env)->ReleaseStringUTFChars(env, jcallbackSignature, callbackSignature);"); + unit.emitln(" "+staticBindingClazzVarName+" = clazz;"); + unit.emitln(" "+staticBindingMethodIDVarName+" = cbMethodID;"); + unit.emitln(" "+nativeCBFuncVarName+" = func"+jcbNativeBasename+";"); + unit.emitln(" "+nativeUserParamVarName+" = ("+userParamTypeName+"*) jnativeUserParam;"); + unit.emitln(" } else {"); + unit.emitln(" "+nativeCBFuncVarName+" = NULL;"); + unit.emitln(" "+nativeUserParamVarName+" = NULL;"); + unit.emitln(" }"); + unit.emitln(); + + } + + public void emitCAdditionalCode(final CodeUnit unit, final CMethodBindingEmitter jcbCMethodEmitter) { + final String jcbNativeBasename = CodeGenUtils.capitalizeString( info.setFuncName ); + final String jcbFriendlyBasename = info.setFuncName+"("+info.cbSimpleClazzName+")"; + final String staticBindingClazzVarName = "clazz"+jcbNativeBasename; + final String staticBindingMethodIDVarName = "method"+jcbNativeBasename; + final String staticCallbackName = "func"+jcbNativeBasename; + // final Type userParamType = javaCallback.cbFuncBinding.getCArgumentType(javaCallback.cbFuncUserParamIdx); + final String userParamTypeName = info.cbFuncUserParamType.getCName(); + final String userParamArgName = info.cbFuncBinding.getArgumentName(info.cbFuncUserParamIdx); + final Type cReturnType = info.cbFuncBinding.getCReturnType(); + final JavaType jretType = info.cbFuncBinding.getJavaReturnType(); + unit.emitln(); + unit.emitln("static jclass "+staticBindingClazzVarName+" = NULL;"); + unit.emitln("static jmethodID "+staticBindingMethodIDVarName+" = NULL;"); + unit.emitln(); + // javaCallback.cbFuncCEmitter.emitSignature(); + unit.emit("static "+cReturnType.getCName()+" "+staticCallbackName+"("); + // javaCallback.cbFuncCEmitter.emitArguments(); + unit.emit(info.cbFuncBinding.getCParameterList(new StringBuilder(), false, null).toString()); + unit.emitln(") {"); + // javaCallback.cbFuncCEmitter.emitBody(); + { + unit.emitln(" JNIEnv* env = JVMUtil_GetJNIEnv();"); + unit.emitln(" jclass cbClazz = "+staticBindingClazzVarName+";"); + unit.emitln(" jmethodID cbMethod = "+staticBindingMethodIDVarName+";"); + unit.emitln(" if( NULL == env || NULL == cbClazz || NULL == cbMethod ) {"); + if( !cReturnType.isVoid() ) { + unit.emitln(" return 0;"); + } else { + unit.emitln(" return;"); + } + unit.emitln(" }"); + // javaCallback.cbFuncCEmitter.emitBodyVariableDeclarations(); + // javaCallback.cbFuncCEmitter.emitBodyUserVariableDeclarations(); + // javaCallback.cbFuncCEmitter.emitBodyVariablePreCallSetup(); + emitJavaCallbackBodyCToJavaPreCall(jcbCMethodEmitter); + + // javaCallback.cbFuncCEmitter.emitBodyCallCFunction(); + unit.emitln(" "+userParamTypeName+"* "+userParamArgName+"_jni = ("+userParamTypeName+"*) "+userParamArgName+";"); + unit.emitln(" // C Params: "+info.cbFuncBinding.getCParameterList(new StringBuilder(), false, null).toString()); + unit.emitln(" // J Params: "+info.cbFuncBinding.getJavaParameterList(new StringBuilder()).toString()); + + final String returnStatement; + if( !cReturnType.isVoid() ) { + unit.emit(" "+cReturnType.getCName()+" _res = 0;"); + returnStatement = "return _res;"; + } else { + returnStatement = "return;"; + } + if( !cReturnType.isVoid() ) { + unit.emit(" _res = ("+cReturnType.getCName()+") "); + } else { + unit.emit(" "); + } + unit.emit("(*env)->CallStatic" + CodeGenUtils.capitalizeString( jretType.getName() ) +"Method(env, cbClazz, cbMethod, "); + // javaCallback.cbFuncCEmitter.emitBodyPassCArguments(); + emitJavaCallbackBodyPassJavaArguments(unit, jcbCMethodEmitter.binding, null); //"NULL"); + unit.emitln(");"); + unit.emitln(" if( (*env)->ExceptionCheck(env) ) {"); + unit.emitln(" fprintf(stderr, \"Info: Callback '"+jcbFriendlyBasename+"': Exception in Java Callback caught:\\n\");"); + unit.emitln(" (*env)->ExceptionDescribe(env);"); + unit.emitln(" (*env)->ExceptionClear(env);"); + unit.emitln(" }"); + + // javaCallback.cbFuncCEmitter.emitBodyUserVariableAssignments(); + // javaCallback.cbFuncCEmitter.emitBodyVariablePostCallCleanup(); + // javaCallback.cbFuncCEmitter.emitBodyMapCToJNIType(-1 /* return value */, true /* addLocalVar */) + + unit.emitln(" "+returnStatement); + } + unit.emitln("}"); + unit.emitln(); + } + + /* pp */ int emitJavaCallbackBodyCToJavaPreCall(final CMethodBindingEmitter ce) { + int count = 0; + for (int i = 0; i < ce.binding.getNumArguments(); i++) { + if( i == info.cbFuncUserParamIdx ) { + continue; + } + if( ce.emitBodyMapCToJNIType(i, true /* addLocalVar */) ) { + ++count; + } + } + return count; + } + + /* pp */ int emitJavaCallbackBodyPassJavaArguments(final CodeUnit unit, final MethodBinding binding, final String userParamVarName) { + int count = 0; + boolean needsComma = false; + for (int i = 0; i < binding.getNumArguments(); i++) { + if (needsComma) { + unit.emit(", "); + needsComma = false; + } + if( i == info.cbFuncUserParamIdx && null != userParamVarName ) { + unit.emit( userParamVarName ); + } else { + unit.emit( binding.getArgumentName(i) + "_jni" ); + } + needsComma = true; + ++count; + } + return count; + } + + +} diff --git a/src/java/com/jogamp/gluegen/JavaConfiguration.java b/src/java/com/jogamp/gluegen/JavaConfiguration.java index 3e2e680..10f43a7 100644 --- a/src/java/com/jogamp/gluegen/JavaConfiguration.java +++ b/src/java/com/jogamp/gluegen/JavaConfiguration.java @@ -156,17 +156,19 @@ public class JavaConfiguration { final int cbFuncUserParamIdx; final String setFuncName; final List<Integer> setFuncKeyIndices = new ArrayList<Integer>(); + final int setFuncUserParamIdx; // optional final String setFuncKeyClassName; // optional - JavaCallbackDef(final String cbFuncTypeName, final int cbFuncUserParamIdx, final String setFuncName, final String setFuncKeyClassName) { + JavaCallbackDef(final String cbFuncTypeName, final int cbFuncUserParamIdx, final String setFuncName, final int setFuncUserParamIdx, final String setFuncKeyClassName) { this.cbFuncTypeName = cbFuncTypeName; this.cbFuncUserParamIdx = cbFuncUserParamIdx; this.setFuncName = setFuncName; + this.setFuncUserParamIdx = setFuncUserParamIdx; this.setFuncKeyClassName = setFuncKeyClassName; } @Override public String toString() { - return String.format("JavaCallbackDef[cbFunc[type %s, userParamIdx %d], set[%s, keys %s, KeyClass %s]]", - cbFuncTypeName, cbFuncUserParamIdx, setFuncName, setFuncKeyIndices.toString(), setFuncKeyClassName); + return String.format("JavaCallbackDef[cbFunc[type %s, userParamIdx %d], set[%s, keys %s, userParamIdx %d, KeyClass %s]]", + cbFuncTypeName, cbFuncUserParamIdx, setFuncName, setFuncKeyIndices.toString(), setFuncUserParamIdx, setFuncKeyClassName); } } private final List<JavaCallbackDef> javaCallbackList = new ArrayList<JavaCallbackDef>(); @@ -1630,7 +1632,25 @@ public class JavaConfiguration { protected void readJavaCallbackDef(final StringTokenizer tok, final String filename, final int lineNo) { try { final String setFuncName = tok.nextToken(); - final String cbFuncTypeName = tok.nextToken(); + final int setFuncUserParamIdx; + final String cbFuncTypeName; + { + final String stok = tok.nextToken(); + int ival = -1; + String sval = null; + try { + ival = Integer.valueOf(stok); + } catch(final NumberFormatException nfe) { + sval = stok; + } + if( null == sval ) { + setFuncUserParamIdx = ival; + cbFuncTypeName = tok.nextToken(); + } else { + setFuncUserParamIdx = -1; + cbFuncTypeName = sval; + } + } final Integer cbFuncUserParamIdx = Integer.valueOf(tok.nextToken()); final String cbFuncKeyClassName; if( tok.hasMoreTokens() ) { @@ -1638,7 +1658,7 @@ public class JavaConfiguration { } else { cbFuncKeyClassName = null; } - final JavaCallbackDef jcd = new JavaCallbackDef(cbFuncTypeName, cbFuncUserParamIdx, setFuncName, cbFuncKeyClassName); + final JavaCallbackDef jcd = new JavaCallbackDef(cbFuncTypeName, cbFuncUserParamIdx, setFuncName, setFuncUserParamIdx, cbFuncKeyClassName); javaCallbackList.add(jcd); javaCallbackSetFuncToDef.put(setFuncName, jcd); } catch (final NoSuchElementException e) { @@ -2279,13 +2299,12 @@ public class JavaConfiguration { final String cbFuncTypeName; final String cbSimpleClazzName; final String cbFQClazzName; - final String cbMethodSignature; + final String staticCBMethodSignature; final FunctionType cbFuncType; final MethodBinding cbFuncBinding; final int cbFuncUserParamIdx; - - final Type userParamType; - final String userParamName; + final String cbFuncUserParamName; + final Type cbFuncUserParamType; final String setFuncName; final List<Integer> setFuncKeyIndices; @@ -2295,38 +2314,38 @@ public class JavaConfiguration { int setFuncUserParamIdx; boolean keyClassEmitted; - public JavaCallbackInfo(final String cbFuncTypeName, final String cbSimpleClazzName, final String cbFQClazzName, final String cbMethodSignature, + public JavaCallbackInfo(final String cbFuncTypeName, final String cbSimpleClazzName, final String cbFQClazzName, final String staticCBMethodSignature, final FunctionType cbFuncType, final MethodBinding cbFuncBinding, final int cbFuncUserParamIdx, - final String setFuncName, final List<Integer> setFuncKeyIndices, final String setFuncKeyClassName) { + final String setFuncName, final int setFuncUserParamIdx, final List<Integer> setFuncKeyIndices, final String setFuncKeyClassName) { this.cbFuncTypeName = cbFuncTypeName; this.cbSimpleClazzName = cbSimpleClazzName; this.cbFQClazzName = cbFQClazzName; - this.cbMethodSignature = cbMethodSignature; + this.staticCBMethodSignature = staticCBMethodSignature; this.cbFuncType = cbFuncType; this.cbFuncBinding = cbFuncBinding; - int paramIdx = -2; - Type paramType = null; - String paramName = null; - if( 0 <= cbFuncUserParamIdx && cbFuncUserParamIdx < cbFuncType.getNumArguments() ) { - final Type t = cbFuncType.getArgumentType(cbFuncUserParamIdx); - if( null != t && t.isPointer() ) { - // OK '<something>*' - paramIdx = cbFuncUserParamIdx; - paramName = cbFuncType.getArgumentName(cbFuncUserParamIdx); - paramType = t.getTargetType(); + { + int paramIdx = -2; + Type paramType = null; + String paramName = null; + if( 0 <= cbFuncUserParamIdx && cbFuncUserParamIdx < cbFuncType.getNumArguments() ) { + final Type t = cbFuncType.getArgumentType(cbFuncUserParamIdx); + if( null != t && t.isPointer() ) { + // OK '<something>*' + paramIdx = cbFuncUserParamIdx; + paramName = cbFuncType.getArgumentName(cbFuncUserParamIdx); + paramType = t.getTargetType(); + } } + this.cbFuncUserParamIdx = paramIdx; + this.cbFuncUserParamName = paramName; + this.cbFuncUserParamType = paramType; } - this.cbFuncUserParamIdx = paramIdx; - - this.userParamType = paramType; - this.userParamName = paramName; - this.setFuncName = setFuncName; this.setFuncKeyIndices = setFuncKeyIndices; this.setFuncKeyClassName = setFuncKeyClassName; this.setFuncProcessed = false; this.setFuncCBParamIdx = -1; - this.setFuncUserParamIdx = -1; + this.setFuncUserParamIdx = setFuncUserParamIdx; this.keyClassEmitted = false; } @@ -2335,7 +2354,13 @@ public class JavaConfiguration { if( 0 <= cbParamIdx && 0 <= userParamIdx ) { setFuncProcessed = true; setFuncCBParamIdx = cbParamIdx; - setFuncUserParamIdx = userParamIdx; + if( 0 <= setFuncUserParamIdx ) { + if( setFuncUserParamIdx != userParamIdx ) { + throw new IllegalArgumentException("Mismatch pre-set setFuncUserParamIdx "+setFuncUserParamIdx+", given "+userParamIdx+": "+toString()); + } + } else { + setFuncUserParamIdx = userParamIdx; + } } else { setFuncCBParamIdx = -1; setFuncUserParamIdx = -1; @@ -2346,8 +2371,8 @@ public class JavaConfiguration { @Override public String toString() { return String.format("JavaCallbackInfo[cbFunc[%s%s, userParam[idx %d, '%s', %s], set[%s(ok %b, cbIdx %d, upIdx %d, keys %s, KeyClass '%s'], %s]", - cbFuncTypeName, cbMethodSignature, - cbFuncUserParamIdx, userParamName, userParamType.getSignature(null).toString(), + cbFuncTypeName, staticCBMethodSignature, + cbFuncUserParamIdx, cbFuncUserParamName, cbFuncUserParamType.getSignature(null).toString(), setFuncName, setFuncProcessed, setFuncCBParamIdx, setFuncUserParamIdx, setFuncKeyIndices.toString(), setFuncKeyClassName, cbFuncType.toString(cbFuncTypeName, false, true)); diff --git a/src/java/com/jogamp/gluegen/JavaEmitter.java b/src/java/com/jogamp/gluegen/JavaEmitter.java index 2ea8d30..4a81a01 100644 --- a/src/java/com/jogamp/gluegen/JavaEmitter.java +++ b/src/java/com/jogamp/gluegen/JavaEmitter.java @@ -1471,9 +1471,9 @@ public class JavaEmitter implements GlueEmitter { throw new UnsupportedOperationException("Reused FuncTypeName "+jcbd.cbFuncTypeName+" used with different FuncUserParamIdx "+jcbi0.cbFuncUserParamIdx+" -> "+jcbd.cbFuncUserParamIdx+". Func "+ funcType.toString(jcbd.cbFuncTypeName, false, true)); } - final JavaCallbackInfo jcbi1 = new JavaCallbackInfo(jcbd.cbFuncTypeName, cbSimpleClazzName, cbFQClazzName, jcbi0.cbMethodSignature, + final JavaCallbackInfo jcbi1 = new JavaCallbackInfo(jcbd.cbFuncTypeName, cbSimpleClazzName, cbFQClazzName, jcbi0.staticCBMethodSignature, funcType, jcbi0.cbFuncBinding, jcbi0.cbFuncUserParamIdx, - jcbd.setFuncName, jcbd.setFuncKeyIndices, jcbd.setFuncKeyClassName); + jcbd.setFuncName, jcbd.setFuncUserParamIdx, jcbd.setFuncKeyIndices, jcbd.setFuncKeyClassName); cfg.setFuncToJavaCallbackMap.put(jcbd.setFuncName, jcbi1); LOG.log(INFO, "JavaCallbackInfo: Reusing {0} -> {1}", jcbd.setFuncName, jcbi0); } else { @@ -1493,7 +1493,7 @@ public class JavaEmitter implements GlueEmitter { } final JavaCallbackInfo jcbi1 = new JavaCallbackInfo(jcbd.cbFuncTypeName, cbSimpleClazzName, cbFQClazzName, cbMethodSignature.toString(), funcType, cbFuncBinding, jcbd.cbFuncUserParamIdx, - jcbd.setFuncName, jcbd.setFuncKeyIndices, jcbd.setFuncKeyClassName); + jcbd.setFuncName, jcbd.setFuncUserParamIdx, jcbd.setFuncKeyIndices, jcbd.setFuncKeyClassName); cfg.setFuncToJavaCallbackMap.put(jcbd.setFuncName, jcbi1); javaCallbackInterfaceMap.put(cbFQClazzName, jcbi1); LOG.log(INFO, "JavaCallbackInfo: Added {0} -> {1}", jcbd.setFuncName, jcbi1); @@ -3019,9 +3019,6 @@ public class JavaEmitter implements GlueEmitter { 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()); - if( cfg.getJavaCallbackList().size() > 0 ) { - cUnit().emitJavaCallbackGlueDataDecl(); - } } } catch (final Exception e) { throw new RuntimeException( @@ -3114,13 +3111,29 @@ public class JavaEmitter implements GlueEmitter { // Replace JavaCallback type with generated interface name jcbiSetFuncCBParamIdx=i; mappedType = JavaType.createForNamedClass( jcbi.cbFQClazzName ); - } else if( null != jcbi && jcbi.userParamName.equals( cArgName ) && + } else if( null != jcbi && i == jcbi.setFuncUserParamIdx && cArgType.isPointer() ) { + // Replace userParam argument '<userParamType>*' if 'void*' with Object + jcbiSetFuncUserParamIdx=i; + if( cArgType.getTargetType().isVoid() ) { + if( jcbi.cbFuncUserParamType.isCompound() ) { + mappedType = JavaType.createForClass(long.class); + } else { + mappedType = JavaType.forObjectClass(); + } + } + } else if( null != jcbi && jcbi.cbFuncUserParamName.equals( cArgName ) && ( !jcbi.setFuncProcessed || i == jcbi.setFuncUserParamIdx ) && - cArgType.isPointer() && jcbi.userParamType.equals( cArgType.getTargetType() ) ) + cArgType.isPointer() && jcbi.cbFuncUserParamType.equals( cArgType.getTargetType() ) ) { - // Replace optional userParam argument '<userParamType>*' with Object + // Replace userParam argument '<userParamType>*' if 'void*' with Object jcbiSetFuncUserParamIdx=i; - mappedType = JavaType.forObjectClass(); + if( cArgType.getTargetType().isVoid() ) { + if( jcbi.cbFuncUserParamType.isCompound() ) { + mappedType = JavaType.createForClass(long.class); + } else { + mappedType = JavaType.forObjectClass(); + } + } } else if (stringArgIndices != null && stringArgIndices.contains(i)) { // Take into account any ArgumentIsString configuration directives that apply // System.out.println("Forcing conversion of " + binding.getName() + " arg #" + i + " from byte[] to String "); @@ -3153,7 +3166,8 @@ public class JavaEmitter implements GlueEmitter { } 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); + LOG.log(INFO, "BindFunc.JavaCallback: {0}: set[cbParamIdx {1}, userParamIdx {2}], {3}, {4}", + sym.getName(), jcbiSetFuncCBParamIdx, jcbiSetFuncUserParamIdx, sym.getType().toString(sym.getName(), false, true), jcbi); } final MethodBinding mb = new MethodBinding(sym, delegationImplName, javaReturnType, javaArgumentTypes, diff --git a/src/java/com/jogamp/gluegen/JavaMethodBindingEmitter.java b/src/java/com/jogamp/gluegen/JavaMethodBindingEmitter.java index 6a93973..fe4f82a 100644 --- a/src/java/com/jogamp/gluegen/JavaMethodBindingEmitter.java +++ b/src/java/com/jogamp/gluegen/JavaMethodBindingEmitter.java @@ -95,7 +95,7 @@ public class JavaMethodBindingEmitter extends FunctionEmitter { private String returnedArrayLengthExpression; private boolean returnedArrayLengthExpressionOnlyForComments = false; - private final JavaCallbackInfo javaCallback; + private final JavaCallbackEmitter javaCallbackEmitter; // A suffix used to create a temporary outgoing array of Buffers to // represent an array of compound type wrappers @@ -134,7 +134,12 @@ public class JavaMethodBindingEmitter extends FunctionEmitter { } else { setCommentEmitter(defaultInterfaceCommentEmitter); } - javaCallback = cfg.setFuncToJavaCallbackMap.get(binding.getName()); + final JavaCallbackInfo javaCallback = cfg.setFuncToJavaCallbackMap.get(binding.getName()); + if( null != javaCallback ) { + javaCallbackEmitter = new JavaCallbackEmitter(cfg, binding, javaCallback, appendSignature(new StringBuilder()).toString()); + } else { + javaCallbackEmitter = null; + } // !forImplementingMethodCall && !isInterface } @@ -156,7 +161,7 @@ public class JavaMethodBindingEmitter extends FunctionEmitter { epilogue = arg.epilogue; returnedArrayLengthExpression = arg.returnedArrayLengthExpression; returnedArrayLengthExpressionOnlyForComments = arg.returnedArrayLengthExpressionOnlyForComments; - javaCallback = arg.javaCallback; + javaCallbackEmitter = arg.javaCallbackEmitter; } public boolean isNativeMethod() { return isNativeMethod; } @@ -262,8 +267,8 @@ public class JavaMethodBindingEmitter extends FunctionEmitter { } @Override - protected void emitReturnType() { - unit.emit(getReturnTypeString(false)); + protected StringBuilder appendReturnType(final StringBuilder buf) { + return buf.append(getReturnTypeString(false)); } protected String erasedTypeString(final JavaType type, final boolean skipBuffers) { @@ -335,33 +340,34 @@ public class JavaMethodBindingEmitter extends FunctionEmitter { } @Override - protected void emitName() { + protected StringBuilder appendName(final StringBuilder buf) { if (isPrivateNativeMethod) { - unit.emit(getNativeImplMethodName()); + buf.append(getNativeImplMethodName()); } else if( isInterface()) { - unit.emit(getInterfaceName()); + buf.append(getInterfaceName()); } else { - unit.emit(getImplName()); + buf.append(getImplName()); } + return buf; } @Override - protected int emitArguments() { + protected int appendArguments(final StringBuilder buf) { boolean needComma = false; int numEmitted = 0; if( hasModifier(JavaMethodBindingEmitter.NATIVE) && binding.isReturnCompoundByValue() ) { - unit.emit("final Class<?> _clazzBuffers"); + buf.append("final Class<?> _clazzBuffers"); ++numEmitted; needComma = true; } if (isPrivateNativeMethod && binding.hasContainingType()) { // Always emit outgoing "this" argument if (needComma) { - unit.emit(", "); + buf.append(", "); } - unit.emit("ByteBuffer "); - unit.emit(javaThisArgumentName()); + buf.append("ByteBuffer "); + buf.append(javaThisArgumentName()); ++numEmitted; needComma = true; } @@ -385,12 +391,12 @@ public class JavaMethodBindingEmitter extends FunctionEmitter { } if (needComma) { - unit.emit(", "); + buf.append(", "); } - unit.emit(erasedTypeString(type, false)); - unit.emit(" "); - unit.emit(getArgumentName(i)); + buf.append(erasedTypeString(type, false)); + buf.append(" "); + buf.append(getArgumentName(i)); ++numEmitted; needComma = true; @@ -398,12 +404,12 @@ public class JavaMethodBindingEmitter extends FunctionEmitter { // Add Buffer and array index offset arguments after each associated argument if (forDirectBufferImplementation || forIndirectBufferAndArrayImplementation) { if (type.isNIOBuffer()) { - unit.emit(", int " + byteOffsetArgName(i)); + buf.append(", int " + byteOffsetArgName(i)); if(!useNIODirectOnly) { - unit.emit(", boolean " + isNIOArgName(i)); + buf.append(", boolean " + isNIOArgName(i)); } } else if (type.isNIOBufferArray()) { - unit.emit(", int[] " + byteOffsetArrayArgName(i)); + buf.append(", int[] " + byteOffsetArrayArgName(i)); } } @@ -412,17 +418,16 @@ public class JavaMethodBindingEmitter extends FunctionEmitter { if(useNIOOnly) { throw new RuntimeException("NIO[Direct]Only "+binding+" is set, but "+getArgumentName(i)+" is a primitive array"); } - unit.emit(", int " + offsetArgName(i)); + buf.append(", int " + offsetArgName(i)); } } if( hasModifier(JavaMethodBindingEmitter.NATIVE) && - null != javaCallback ) + null != javaCallbackEmitter ) { if (needComma) { - unit.emit(", "); + buf.append(", "); } - unit.emit("String callbackSignature, Object lockObj, long[/*1*/] nativeUserParam"); - ++numEmitted; + numEmitted += javaCallbackEmitter.appendJavaAdditionalJNIParameter(buf); } return numEmitted; } @@ -455,125 +460,11 @@ public class JavaMethodBindingEmitter extends FunctionEmitter { return getArgumentName(i) + "_offset"; } - private static final boolean DEBUG_JAVACALLBACK = false; - - private final void emitJavaCallbackBirefAPIDoc(final String actionText, final String relationToKey, final String noKeyText, final String relationToFunc) { - unit.emit(" /** "+actionText); - if( javaCallback.setFuncKeyIndices.size() > 0 ) { - unit.emit(relationToKey); - unit.emit("Key { "+binding.getJavaSelectParameter(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+" } "); - } else { - unit.emit(noKeyText); - } - unit.emit(relationToFunc); - unit.emit("<br> <code>"); - emitSignature(); - unit.emitln("</code> **/"); - } - private final void emitJavaCallbackKeyClass(final String capIfaceName, final String keyClassName) { - emitJavaCallbackBirefAPIDoc("", "", "", "for "); - unit.emitln(" public static class "+keyClassName+" {"); - binding.forEachParameter( ( final int idx, final int consumedCount, final Type cType, final JavaType jType, final String name ) -> { - if( !cType.isVoid() && javaCallback.setFuncKeyIndices.contains(idx) ) { - unit.emitln(" public final "+jType+" "+name+";"); - return true; - } else { - return false; - } - } ); - unit.emitln(" public "+keyClassName+"("+binding.getJavaSelectParameter(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+") {"); - binding.forEachParameter( ( final int idx, final int consumedCount, final Type cType, final JavaType jType, final String name ) -> { - if( !cType.isVoid() && javaCallback.setFuncKeyIndices.contains(idx) ) { - unit.emitln(" this."+name+" = "+name+";"); - return true; - } else { - return false; - } - } ); - unit.emitln(" }"); - unit.emitln(" @Override"); - unit.emitln(" public boolean equals(final Object o) {"); - unit.emitln(" if( this == o ) {"); - unit.emitln(" return true;"); - unit.emitln(" }"); - unit.emitln(" if( !(o instanceof "+keyClassName+") ) {"); - unit.emitln(" return false;"); - unit.emitln(" }"); - { - final int count = binding.forEachParameter( ( final int idx, final int consumedCount, final Type cType, final JavaType jType, final String name ) -> { - if( !cType.isVoid() && javaCallback.setFuncKeyIndices.contains(idx) ) { - if( 0 == consumedCount ) { - unit.emitln(" final "+keyClassName+" o2 = ("+keyClassName+")o;"); - unit.emit (" return "); - } else { - unit.emitln(" &&"); - unit.emit (" "); - } - if( jType.isPrimitive() || idx == javaCallback.setFuncUserParamIdx ) { - unit.emit(name+" == o2."+name); - } else { - unit.emit(name+".equals( o2."+name+" )"); - } - return true; - } else { - return false; - } - } ); - if( 0 == count ) { - unit.emit(" return true"); - } - unit.emitln(";"); - } - unit.emitln(" }"); - unit.emitln(" @Override"); - unit.emitln(" public int hashCode() {"); - { - final int count = binding.forEachParameter( ( final int idx, final int consumedCount, final Type cType, final JavaType jType, final String name ) -> { - if( !cType.isVoid() && javaCallback.setFuncKeyIndices.contains(idx) ) { - if( 0 == consumedCount ) { - unit.emitln(" // 31 * x == (x << 5) - x"); - unit.emit (" int hash = "); - } else { - unit.emit (" hash = ((hash << 5) - hash) + "); - } - if( jType.isPrimitive() ) { - if( jType.isLong() ) { - unit.emitln("HashUtil.getAddrHash32_EqualDist( "+name+" );"); - } else { - unit.emitln(name+";"); - } - } else { - if( idx == javaCallback.setFuncUserParamIdx ) { - unit.emitln("System.identityHashCode( "+name+" );"); - } else { - unit.emitln(name+".hashCode();"); - } - } - return true; - } else { - return false; - } - } ); - if( 0 == count ) { - unit.emitln(" return 0;"); - } else { - unit.emitln(" return hash;"); - } - } - unit.emitln(" }"); - unit.emitln(" }"); - } - private final void emitJavaCallbackDataClass(final String capIfaceName, final String dataClassName) { - unit.emitln(" private static class "+dataClassName+" {"); - unit.emitln(" final "+javaCallback.cbFuncTypeName+" func;"); - unit.emitln(" final Object param;"); - unit.emitln(" final long nativeParam;"); - unit.emitln(" "+dataClassName+"("+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(" }"); + @Override + protected void emitAdditionalCode() { + if( null != javaCallbackEmitter && !isPrivateNativeMethod ) { + javaCallbackEmitter.emitJavaAdditionalCode(unit, isInterface()); + } } @Override @@ -594,201 +485,6 @@ public class JavaMethodBindingEmitter extends FunctionEmitter { } unit.emitln(" }"); } - if( null != javaCallback && !isPrivateNativeMethod ) { - final String capIfaceName = CodeGenUtils.capitalizeString( getInterfaceName() ); - final String lowIfaceName = CodeGenUtils.decapitalizeString( getInterfaceName() ); - final String lockInstanceName = lowIfaceName+"Lock"; - final String dataMapInstanceName = lowIfaceName+"DataMap"; - final String dataInstanceName = lowIfaceName+"Data"; - final boolean customKeyClass; - final String KeyClassName; - final boolean useDataMap; - if( null != javaCallback.setFuncKeyClassName ) { - customKeyClass = true;; - KeyClassName = javaCallback.setFuncKeyClassName; - useDataMap = true; - } else { - customKeyClass = false; - KeyClassName = CodeGenUtils.capitalizeString(capIfaceName+"Key"); - useDataMap = javaCallback.setFuncKeyIndices.size() > 0; - } - final String DataClassName = CodeGenUtils.capitalizeString( javaCallback.cbFuncTypeName+"Data" ); - final String fqUsrParamClassName = cfg.packageName()+"."+cfg.className()+"."+DataClassName; - unit.emitln(); - if( isInterface() ) { - if( useDataMap ) { - if( !customKeyClass && !javaCallback.keyClassEmitted ) { - emitJavaCallbackKeyClass(capIfaceName, KeyClassName); - unit.emitln(); - javaCallback.keyClassEmitted = true; - } - emitJavaCallbackBirefAPIDoc("Returns ", "set of ", "", "for "); - unit.emitln(" public Set<"+KeyClassName+"> get"+capIfaceName+"Keys();"); - unit.emitln(); - emitJavaCallbackBirefAPIDoc("Returns ", "whether callback ", "if callback ", "is mapped for "); - unit.emitln(" public boolean is"+capIfaceName+"Mapped("+KeyClassName+" key);"); - unit.emitln(); - emitJavaCallbackBirefAPIDoc("Returns "+javaCallback.cbFuncTypeName+" callback ", "mapped to ", "", "for "); - unit.emitln(" public "+javaCallback.cbFuncTypeName+" get"+capIfaceName+"("+KeyClassName+" key);"); - unit.emitln(); - emitJavaCallbackBirefAPIDoc("Returns user-param ", "mapped to ", "", "for "); - unit.emitln(" public Object get"+capIfaceName+"UserParam("+KeyClassName+" key);"); - unit.emitln(); - emitJavaCallbackBirefAPIDoc("Releases all callback data ", "mapped via ", "", "skipping toolkit API. Favor passing `null` callback ref to "); - unit.emitln(" public int releaseAll"+capIfaceName+"();"); - unit.emitln(); - emitJavaCallbackBirefAPIDoc("Releases callback data ", "mapped to ", "", "skipping toolkit API. Favor passing `null` callback ref to "); - unit.emitln(" public void release"+capIfaceName+"("+KeyClassName+" key);"); - unit.emitln(); - } else { - emitJavaCallbackBirefAPIDoc("Returns ", "whether callback ", "if callback ", "is mapped for "); - unit.emitln(" public boolean is"+capIfaceName+"Mapped();"); - unit.emitln(); - emitJavaCallbackBirefAPIDoc("Returns "+javaCallback.cbFuncTypeName+" callback ", "mapped to ", "", "for "); - unit.emitln(" public "+javaCallback.cbFuncTypeName+" get"+capIfaceName+"();"); - unit.emitln(); - emitJavaCallbackBirefAPIDoc("Returns user-param ", "mapped to ", "", "for "); - unit.emitln(" public Object get"+capIfaceName+"UserParam();"); - unit.emitln(); - emitJavaCallbackBirefAPIDoc("Releases callback data ", "", "", "skipping toolkit API. Favor passing `null` callback ref to "); - unit.emitln(" public void release"+capIfaceName+"();"); - unit.emitln(); - } - } else { - if( useDataMap ) { - if( !customKeyClass && !javaCallback.keyClassEmitted ) { - emitJavaCallbackKeyClass(capIfaceName, KeyClassName); - unit.emitln(); - javaCallback.keyClassEmitted = true; - } - emitJavaCallbackBirefAPIDoc("Returns ", "set of ", "", "for "); - unit.emitln(" public final Set<"+KeyClassName+"> get"+capIfaceName+"Keys() {"); - unit.emitln(" synchronized( "+lockInstanceName+" ) {"); - unit.emitln(" return "+dataMapInstanceName+".keySet();"); - unit.emitln(" }"); - unit.emitln(" }"); - unit.emitln(); - emitJavaCallbackBirefAPIDoc("Returns ", "whether callback ", "if callback ", "is mapped for "); - unit.emitln(" public final boolean is"+capIfaceName+"Mapped("+KeyClassName+" key) {"); - unit.emitln(" synchronized( "+lockInstanceName+" ) {"); - unit.emitln(" return null != "+dataMapInstanceName+".get(key);"); - unit.emitln(" }"); - unit.emitln(" }"); - unit.emitln(); - - emitJavaCallbackBirefAPIDoc("Returns "+javaCallback.cbFuncTypeName+" callback ", "mapped to ", "", "for "); - unit.emitln(" public final "+javaCallback.cbFuncTypeName+" get"+capIfaceName+"("+KeyClassName+" key) {"); - unit.emitln(" synchronized( "+lockInstanceName+" ) {"); - unit.emitln(" final "+DataClassName+" value = "+dataMapInstanceName+".get(key);"); - unit.emitln(" return null != value ? value.func : null;"); - unit.emitln(" }"); - unit.emitln(" }"); - unit.emitln(); - - emitJavaCallbackBirefAPIDoc("Returns user-param ", "mapped to ", "", "for "); - unit.emitln(" public final Object get"+capIfaceName+"UserParam("+KeyClassName+" key) {"); - unit.emitln(" synchronized( "+lockInstanceName+" ) {"); - unit.emitln(" final "+DataClassName+" value = "+dataMapInstanceName+".get(key);"); - unit.emitln(" return null != value ? value.param : null;"); - unit.emitln(" }"); - unit.emitln(" }"); - unit.emitln(); - emitJavaCallbackBirefAPIDoc("Releases all callback data ", "mapped via ", "", "skipping toolkit API. Favor passing `null` callback ref to "); - unit.emitln(" public final int releaseAll"+capIfaceName+"() {"); - unit.emitln(" synchronized( "+lockInstanceName+" ) {"); - unit.emitln(" final Set<"+KeyClassName+"> keySet = "+dataMapInstanceName+".keySet();"); - unit.emitln(" final "+KeyClassName+"[] keys = keySet.toArray(new "+KeyClassName+"[keySet.size()]);"); - unit.emitln(" for(int i=0; i<keys.length; ++i) {"); - unit.emitln(" final "+KeyClassName+" key = keys[i];"); - unit.emitln(" release"+capIfaceName+"(key);"); - unit.emitln(" }"); - unit.emitln(" return keys.length;"); - unit.emitln(" }"); - unit.emitln(" }"); - unit.emitln(); - emitJavaCallbackBirefAPIDoc("Releases callback data ", "mapped to ", "", "skipping toolkit API. Favor passing `null` callback ref to "); - unit.emitln(" public final void release"+capIfaceName+"("+KeyClassName+" key) {"); - unit.emitln(" synchronized( "+lockInstanceName+" ) {"); - unit.emitln(" final "+DataClassName+" value = "+dataMapInstanceName+".remove(key);"); - if( DEBUG_JAVACALLBACK ) { - unit.emitln(" System.err.println(\"ZZZ Release \"+key+\" -> value.nativeParam 0x\"+Long.toHexString(null!=value?value.nativeParam:0));"); - } - unit.emitln(" if( null != value ) {"); - unit.emitln(" release"+capIfaceName+"Impl(value.nativeParam);"); - unit.emitln(" }"); - unit.emitln(" }"); - unit.emitln(" }"); - } else { - emitJavaCallbackBirefAPIDoc("Returns ", "whether callback ", "if callback ", "is mapped for "); - unit.emitln(" public final boolean is"+capIfaceName+"Mapped() {"); - unit.emitln(" synchronized( "+lockInstanceName+" ) {"); - unit.emitln(" return null != "+dataInstanceName+";"); - unit.emitln(" }"); - unit.emitln(" }"); - unit.emitln(); - - emitJavaCallbackBirefAPIDoc("Returns "+javaCallback.cbFuncTypeName+" callback ", "mapped to ", "", "for "); - unit.emitln(" public final "+javaCallback.cbFuncTypeName+" get"+capIfaceName+"() {"); - unit.emitln(" synchronized( "+lockInstanceName+" ) {"); - unit.emitln(" final "+DataClassName+" value = "+dataInstanceName+";"); - unit.emitln(" return null != value ? value.func : null;"); - unit.emitln(" }"); - unit.emitln(" }"); - unit.emitln(); - - emitJavaCallbackBirefAPIDoc("Returns user-param ", "mapped to ", "", "for "); - unit.emitln(" public final Object get"+capIfaceName+"UserParam() {"); - unit.emitln(" synchronized( "+lockInstanceName+" ) {"); - unit.emitln(" final "+DataClassName+" value = "+dataInstanceName+";"); - unit.emitln(" return null != value ? value.param : null;"); - unit.emitln(" }"); - unit.emitln(" }"); - unit.emitln(); - - emitJavaCallbackBirefAPIDoc("Releases callback data ", "", "", "skipping toolkit API. Favor passing `null` callback ref to "); - unit.emitln(" public final void release"+capIfaceName+"() {"); - unit.emitln(" synchronized( "+lockInstanceName+" ) {"); - unit.emitln(" final "+DataClassName+" value = "+dataInstanceName+";"); - unit.emitln(" "+dataInstanceName+" = null;"); - if( DEBUG_JAVACALLBACK ) { - unit.emitln(" System.err.println(\"ZZZ Release \"+key+\" -> value.nativeParam 0x\"+Long.toHexString(null!=value?value.nativeParam:0));"); - } - unit.emitln(" if( null != value ) {"); - unit.emitln(" release"+capIfaceName+"Impl(value.nativeParam);"); - unit.emitln(" }"); - unit.emitln(" }"); - unit.emitln(" }"); - } - unit.emitln(" private native void release"+capIfaceName+"Impl(long nativeUserParam);"); - unit.emitln(); - unit.emitln(" private final void add"+capIfaceName+"("+binding.getJavaSelectParameter(new StringBuilder(), javaCallback.setFuncKeyIndices, true).toString()+DataClassName+" value) {"); - if( useDataMap ) { - unit.emitln(" final "+KeyClassName+" key = new "+KeyClassName+"("+binding.getJavaCallSelectArguments(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+");"); - unit.emitln(" final "+DataClassName+" old = "+dataMapInstanceName+".put(key, value);"); - } else { - unit.emitln(" final "+DataClassName+" old = "+dataInstanceName+";"); - unit.emitln(" "+dataInstanceName+" = value;"); - } - if( DEBUG_JAVACALLBACK ) { - unit.emitln(" System.err.println(\"ZZZ Map \"+key+\" -> value.nativeParam 0x\"+Long.toHexString(null!=value?value.nativeParam:0));"); - } - unit.emitln(" if( null != old ) {"); - unit.emitln(" release"+capIfaceName+"Impl(old.nativeParam);"); - unit.emitln(" }"); - unit.emitln(" }"); - if( !cfg.emittedJavaCallbackUserParamClasses.contains(fqUsrParamClassName) ) { - emitJavaCallbackDataClass(capIfaceName, DataClassName); - cfg.emittedJavaCallbackUserParamClasses.add(fqUsrParamClassName); - } - if( useDataMap ) { - unit.emitln(" private final Map<"+KeyClassName+", "+DataClassName+"> "+dataMapInstanceName+" = new HashMap<"+KeyClassName+", "+DataClassName+">();"); - } else { - unit.emitln(" private "+DataClassName+" "+dataInstanceName+" = null;"); - } - unit.emitln(" private final Object "+lockInstanceName+" = new Object();"); - unit.emitln(); - } - } } protected void emitPrologueOrEpilogue(final List<String> code) { @@ -901,15 +597,11 @@ public class JavaMethodBindingEmitter extends FunctionEmitter { protected void emitReturnVariableSetupAndCall(final MethodBinding binding) { final JavaType returnType = binding.getJavaReturnType(); + boolean needsResultAssignment = false; - final String capIfaceName = CodeGenUtils.capitalizeString( getInterfaceName() ); - if( null != javaCallback ) { - final String lowIfaceName = CodeGenUtils.decapitalizeString( getInterfaceName() ); - final String lockInstanceName = lowIfaceName+"Lock"; - unit.emitln(" synchronized( "+lockInstanceName+" ) {"); - unit.emitln(" final long[] nativeUserParam = { 0 };"); - unit.emitln(); + if( null != javaCallbackEmitter ) { + javaCallbackEmitter.emitJavaSetFuncPreCall(unit); } if (!returnType.isVoid()) { unit.emit(" "); @@ -941,36 +633,9 @@ 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); - final String DataClassName = CodeGenUtils.capitalizeString( javaCallback.cbFuncTypeName+"Data" ); - final String KeyClassName; - final boolean useDataMap; - if( null != javaCallback.setFuncKeyClassName ) { - KeyClassName = javaCallback.setFuncKeyClassName; - useDataMap = true; - } else { - KeyClassName = CodeGenUtils.capitalizeString(capIfaceName+"Key"); - useDataMap = javaCallback.setFuncKeyIndices.size() > 0; - } - if( DEBUG_JAVACALLBACK ) { - unit.emitln(" System.err.println(\"ZZZ returned nativeUserParam 0x\"+Long.toHexString(nativeUserParam[0]));"); - } + if( null != javaCallbackEmitter ) { unit.emitln(); - unit.emitln(" if( 0 != nativeUserParam[0] ) {"); - unit.emitln(" // callback registrated -> add will release a previously mapped instance "); - unit.emitln(" add"+capIfaceName+"("+binding.getJavaCallSelectArguments(new StringBuilder(), javaCallback.setFuncKeyIndices, true).toString()+ - "new "+DataClassName+"("+funcArgName+", "+userParamArgName+", nativeUserParam[0]));"); - unit.emitln(" } else {"); - unit.emitln(" // callback released (null func) -> release a previously mapped instance "); - if( useDataMap ) { - unit.emitln(" release"+capIfaceName+"( new "+KeyClassName+"( "+binding.getJavaCallSelectArguments(new StringBuilder(), javaCallback.setFuncKeyIndices, false).toString()+" ) );"); - } else { - unit.emitln(" release"+capIfaceName+"();"); - } - unit.emitln(" }"); - unit.emitln(" } // synchronized "); + javaCallbackEmitter.emitJavaSetFuncPostCall(unit); } emitPostCallCleanup(binding); @@ -1094,14 +759,13 @@ public class JavaMethodBindingEmitter extends FunctionEmitter { needComma = true; ++numArgsEmitted; } - if( null != javaCallback ) { - final String lowIfaceName = CodeGenUtils.decapitalizeString( getInterfaceName() ); - final String lockInstanceName = lowIfaceName+"Lock"; + if( null != javaCallbackEmitter ) { if (needComma) { unit.emit(", "); } - unit.emit("\"" + javaCallback.cbMethodSignature + "\", "+lockInstanceName+", nativeUserParam"); - ++numArgsEmitted; + final StringBuilder buf = new StringBuilder(); + numArgsEmitted += javaCallbackEmitter.appendJavaAdditionalJNIArguments(buf); + unit.emit(buf.toString()); } return numArgsEmitted; } @@ -1301,12 +965,11 @@ public class JavaMethodBindingEmitter extends FunctionEmitter { } } - protected class InterfaceCommentEmitter extends JavaMethodBindingEmitter.DefaultCommentEmitter { - - @Override - protected void emitBeginning(final FunctionEmitter emitter, final PrintWriter writer) { - writer.print("Interface to C language function: <br> "); - } + protected class InterfaceCommentEmitter extends JavaMethodBindingEmitter.DefaultCommentEmitter { + @Override + protected void emitBeginning(final FunctionEmitter emitter, final PrintWriter writer) { + writer.print("Interface to C language function: <br> "); } + } } diff --git a/src/java/com/jogamp/gluegen/JavaType.java b/src/java/com/jogamp/gluegen/JavaType.java index 682a95a..3d6d839 100644 --- a/src/java/com/jogamp/gluegen/JavaType.java +++ b/src/java/com/jogamp/gluegen/JavaType.java @@ -334,6 +334,84 @@ public class JavaType { } /** + * Appends the descriptor (internal type signature) corresponding to the given Class<?> c. + * @param buf the StringBuilder sink + * @param c the Class<?> to append the descriptor for + * @param useTrueType if true, use the actual Class<?> name for non primitives, otherwise java.lang.Object will be used (flattened) + * @return the given StringBuilder sink for chaining + */ + public static StringBuilder appendDescriptor(final StringBuilder buf, final Class<?> c, final boolean useTrueType) { + if (c.isPrimitive()) { + if (c == Boolean.TYPE) buf.append("Z"); + else if (c == Byte.TYPE) buf.append("B"); + else if (c == Character.TYPE) buf.append("C"); + else if (c == Short.TYPE) buf.append("S"); + else if (c == Integer.TYPE) buf.append("I"); + else if (c == Long.TYPE) buf.append("J"); + else if (c == Float.TYPE) buf.append("F"); + else if (c == Double.TYPE) buf.append("D"); + else throw new RuntimeException("Illegal primitive type \"" + c.getName() + "\""); + } else { + // Arrays and NIO Buffers are always passed down as java.lang.Object. + // The only arrays that show up as true arrays in the signature + // are the synthetic byte offset arrays created when passing + // down arrays of direct Buffers. Compound type wrappers are + // passed down as ByteBuffers (no good reason, just to avoid + // accidental conflation) so we mangle them differently. + if (useTrueType) { + if (c.isArray()) { + buf.append("["); + final Class<?> componentType = c.getComponentType(); + // Handle arrays of compound type wrappers differently for + // convenience of the Java-level glue code generation + appendDescriptor(buf, componentType, + (componentType == java.nio.ByteBuffer.class)); + } else { + buf.append("L"); + buf.append(c.getName().replace('.', '/')); + buf.append(";"); + } + } else { + if (c.isArray()) { + buf.append("["); + appendDescriptor(buf, c.getComponentType(), false); + } else if (c == java.lang.String.class) { + buf.append("L"); + buf.append(c.getName().replace('.', '/')); + buf.append(";"); + } else { + buf.append("L"); + buf.append("java_lang_Object"); + buf.append(";"); + } + } + } + return buf; + } + + /** + * Appends the native (JNI) method-name descriptor corresponding to the given Class<?> c, + * i.e. replacing chars {@link #appendDescriptor(StringBuilder, Class, boolean)} as follows + * <ul> + * <li>`_` -> `_1`</li> + * <li>`/` -> `_`</li> + * <li>`;` -> `_2`</li> + * <li>`[` -> `_3`</li> + * </ul> + * Only the newly appended segment to the StringBuilder sink will be converted to (JNI) method-name using {@link #toJNIMethodDescriptor(StringBuilder, int)}. + * @param buf the StringBuilder sink + * @param c the Class<?> to append the descriptor for + * @param useTrueType if true, use the actual Class<?> name for non primitives, otherwise java.lang.Object will be used (flattened) + * @return the given StringBuilder sink for chaining + * @see JNI Spec 2, Chapter 2, Resolving Native Method Names + * @see #toJNIMethodDescriptor(StringBuilder) + */ + public static StringBuilder appendJNIDescriptor(final StringBuilder res, final Class<?> c, final boolean useTrueType) { + final int start = res.length(); + return toJNIMethodDescriptor( appendDescriptor(res, c, useTrueType), start ); + } + + /** * Converts the assumed descriptor (internal type signature) to a native (JNI) method-name descriptor, * i.e. replacing chars {@link #getDescriptor()} as follows * <ul> @@ -342,6 +420,7 @@ public class JavaType { * <li>`;` -> `_2`</li> * <li>`[` -> `_3`</li> * </ul> + * @param descriptor the char sequence holding the original descriptor * @see JNI Spec 2, Chapter 2, Resolving Native Method Names */ public static String toJNIMethodDescriptor(final String descriptor) { @@ -351,6 +430,36 @@ public class JavaType { .replace("[", "_3"); } + /** + * Converts the assumed descriptor (internal type signature) to a native (JNI) method-name descriptor, + * i.e. replacing chars {@link #getDescriptor()} as follows + * <ul> + * <li>`_` -> `_1`</li> + * <li>`/` -> `_`</li> + * <li>`;` -> `_2`</li> + * <li>`[` -> `_3`</li> + * </ul> + * @param descriptor the char buffer holding the original descriptor + * @param start start position of the segment to convert, use 0 if whole buffr shall be converted + * @return returns passed descriptor buffer for chaining + * @see JNI Spec 2, Chapter 2, Resolving Native Method Names + */ + public static StringBuilder toJNIMethodDescriptor(final StringBuilder descriptor, final int start) { + replace(descriptor, start, "_", "_1"); + replace(descriptor, start, "/", "_"); + replace(descriptor, start, ";", "_2"); + replace(descriptor, start, "[", "_3"); + return descriptor; + } + private static StringBuilder replace(final StringBuilder buf, int start, final String target, final String replacement) { + start = buf.indexOf(target, start); + while( 0 <= start ) { + buf.replace(start, start + target.length(), replacement); + start = buf.indexOf(target, start + replacement.length()); + } + return buf; + } + /** Returns the String corresponding to the JNI type for this type, or NULL if it can't be represented (i.e., it's a boxing class that we need to call getBuffer() on.) */ diff --git a/src/java/com/jogamp/gluegen/procaddress/ProcAddressCMethodBindingEmitter.java b/src/java/com/jogamp/gluegen/procaddress/ProcAddressCMethodBindingEmitter.java index a280d6e..c20b07a 100644 --- a/src/java/com/jogamp/gluegen/procaddress/ProcAddressCMethodBindingEmitter.java +++ b/src/java/com/jogamp/gluegen/procaddress/ProcAddressCMethodBindingEmitter.java @@ -102,17 +102,16 @@ public class ProcAddressCMethodBindingEmitter extends CMethodBindingEmitter { } @Override - protected int emitArguments() { - int numEmitted = super.emitArguments(); + protected int appendArguments(final StringBuilder buf) { + int numEmitted = super.appendArguments(buf); if (callThroughProcAddress) { if (numEmitted > 0) { - unit.emit(", "); + buf.append(", "); } - unit.emit(procAddressJavaTypeName); - unit.emit(" procAddress"); + buf.append(procAddressJavaTypeName); + buf.append(" procAddress"); ++numEmitted; } - return numEmitted; } @@ -224,7 +223,7 @@ public class ProcAddressCMethodBindingEmitter extends CMethodBindingEmitter { protected String jniMangle(final MethodBinding binding) { final StringBuilder buf = new StringBuilder(super.jniMangle(binding)); if (callThroughProcAddress && 0 <= buf.indexOf("__") ) { - getJNIMangledArg(Long.TYPE, buf, false); // to account for the additional _addr_ parameter + JavaType.appendJNIDescriptor(buf, Long.TYPE, false); // to account for the additional _addr_ parameter } return buf.toString(); } diff --git a/src/java/com/jogamp/gluegen/procaddress/ProcAddressJavaMethodBindingEmitter.java b/src/java/com/jogamp/gluegen/procaddress/ProcAddressJavaMethodBindingEmitter.java index 0d5de1c..b6ed21b 100644 --- a/src/java/com/jogamp/gluegen/procaddress/ProcAddressJavaMethodBindingEmitter.java +++ b/src/java/com/jogamp/gluegen/procaddress/ProcAddressJavaMethodBindingEmitter.java @@ -91,19 +91,18 @@ public class ProcAddressJavaMethodBindingEmitter extends JavaMethodBindingEmitte } @Override - protected int emitArguments() { - int numEmitted = super.emitArguments(); + protected int appendArguments(final StringBuilder buf) { + int numEmitted = super.appendArguments(buf); if (callThroughProcAddress) { if (changeNameAndArguments) { if (numEmitted > 0) { - unit.emit(", "); + buf.append(", "); } - unit.emit("long procAddress"); + buf.append("long procAddress"); ++numEmitted; } } - return numEmitted; } diff --git a/src/junit/com/jogamp/gluegen/test/junit/generation/Test4JavaCallback.java b/src/junit/com/jogamp/gluegen/test/junit/generation/Test4JavaCallback.java index 406008a..8c1d726 100644 --- a/src/junit/com/jogamp/gluegen/test/junit/generation/Test4JavaCallback.java +++ b/src/junit/com/jogamp/gluegen/test/junit/generation/Test4JavaCallback.java @@ -34,7 +34,10 @@ import java.util.Set; import com.jogamp.common.os.NativeLibrary; import com.jogamp.gluegen.test.junit.generation.Bindingtest2.ALBUFFERCALLBACKTYPESOFT; import com.jogamp.gluegen.test.junit.generation.Bindingtest2.AlBufferCallback0Key; +import com.jogamp.gluegen.test.junit.generation.Bindingtest2.MessageCallback11aKey; +import com.jogamp.gluegen.test.junit.generation.Bindingtest2.MessageCallback11bKey; import com.jogamp.gluegen.test.junit.generation.Bindingtest2.T2_CallbackFunc01; +import com.jogamp.gluegen.test.junit.generation.Bindingtest2.T2_CallbackFunc11; import com.jogamp.gluegen.test.junit.generation.impl.Bindingtest2Impl; import org.junit.AfterClass; @@ -197,8 +200,9 @@ public class Test4JavaCallback extends BaseClass { @Override public void callback(final int buffer, final Object userptr, final int sampledata, final int numbytes) { final MyUserParam02 myUserParam = (MyUserParam02)userptr; - id_res[0] = sampledata + numbytes + myUserParam.i; - myUserParam.j = id_res[0]; + final long res = sampledata + numbytes + myUserParam.i; + id_res[0] = res; + myUserParam.j = res; myUserParam.buffer = buffer; System.err.println("chapter02.myCallback01: buffer "+buffer+", sampledata "+sampledata+", numbytes "+numbytes); } @@ -207,8 +211,9 @@ public class Test4JavaCallback extends BaseClass { @Override public void callback(final int buffer, final Object userptr, final int sampledata, final int numbytes) { final MyUserParam02 myUserParam = (MyUserParam02)userptr; - id_res[0] = sampledata * numbytes + myUserParam.i; - myUserParam.j = id_res[0]; + final long res = sampledata * numbytes + myUserParam.i; + id_res[0] = res; + myUserParam.j = res; myUserParam.buffer = buffer; System.err.println("chapter02.myCallback02: buffer "+buffer+", sampledata "+sampledata+", numbytes "+numbytes); } @@ -402,8 +407,9 @@ public class Test4JavaCallback extends BaseClass { @Override public void callback(final int buffer, final Object userptr, final int sampledata, final int numbytes) { final MyUserParam02 myUserParam = (MyUserParam02)userptr; - id_res[0] = sampledata + numbytes + myUserParam.i; - myUserParam.j = id_res[0]; + final long res = sampledata + numbytes + myUserParam.i; + id_res[0] = res; + myUserParam.j = res;; myUserParam.buffer = buffer; System.err.println("chapter03.myCallback01: buffer "+buffer+", sampledata "+sampledata+", numbytes "+numbytes); } @@ -412,8 +418,9 @@ public class Test4JavaCallback extends BaseClass { @Override public void callback(final int buffer, final Object userptr, final int sampledata, final int numbytes) { final MyUserParam02 myUserParam = (MyUserParam02)userptr; - id_res[0] = sampledata * numbytes + myUserParam.i; - myUserParam.j = id_res[0]; + final long res = sampledata * numbytes + myUserParam.i; + id_res[0] = res; + myUserParam.j = res; myUserParam.buffer = buffer; System.err.println("chapter03.myCallback02: buffer "+buffer+", sampledata "+sampledata+", numbytes "+numbytes); } @@ -633,8 +640,9 @@ public class Test4JavaCallback extends BaseClass { myUserParam.throwPreAction = false; throw new RuntimeException("Exception test.pre: chapter04.myCallback01"); } - id_res[0] = sampledata + numbytes + myUserParam.i; - myUserParam.j = id_res[0]; + final long res = sampledata + numbytes + myUserParam.i; + id_res[0] = res; + myUserParam.j = res; myUserParam.buffer = buffer; System.err.println("chapter04.myCallback01: buffer "+buffer+", sampledata "+sampledata+", numbytes "+numbytes); if( myUserParam.throwPostAction ) { @@ -651,8 +659,9 @@ public class Test4JavaCallback extends BaseClass { myUserParam.throwPreAction = false; throw new RuntimeException("Exception test.pre: chapter04.myCallback02"); } - id_res[0] = sampledata * numbytes + myUserParam.i; - myUserParam.j = id_res[0]; + final long res = sampledata * numbytes + myUserParam.i; + id_res[0] = res; + myUserParam.j = res; myUserParam.buffer = buffer; System.err.println("chapter04.myCallback02: buffer "+buffer+", sampledata "+sampledata+", numbytes "+numbytes); if( myUserParam.throwPostAction ) { @@ -872,6 +881,404 @@ public class Test4JavaCallback extends BaseClass { } } + /** + * Test Bindingtest2 with T2_CallbackFunc11 JavaCallback via MessageCallback11a() + * using the default MessageCallback11aKey class. + */ + @Test + public void chapter11a() throws Exception { + final Bindingtest2 bt2 = new Bindingtest2Impl(); + + final long userParam01Ptr = 0xAFFEBEAFC0FFEEL; + final long userParam02Ptr = 0xC0FFEEDEADBEAFL; + + final long[] id_res = { -1 }; + final T2_CallbackFunc11 myCallback01 = new T2_CallbackFunc11() { + @Override + public void callback(final long id, final T2_Callback11UserType usrParam, final long val) { + Assert.assertEquals(42, usrParam.getApiVersion()); // native toolkit should have set API version + if( 1 == id ) { + BaseClass.assertAPTR(userParam01Ptr, usrParam.getData()); + } else if( 2 == id ) { + BaseClass.assertAPTR(userParam02Ptr, usrParam.getData()); + } + final long res = val + usrParam.getI(); + id_res[0] = res; + usrParam.setR(res); + usrParam.setId(id); + System.err.println("chapter11a.myCallback01: id "+id+", val "+val); + } + }; + final T2_CallbackFunc11 myCallback02 = new T2_CallbackFunc11() { + @Override + public void callback(final long id, final T2_Callback11UserType usrParam, final long val) { + Assert.assertEquals(42, usrParam.getApiVersion()); // native toolkit should have set API version + if( 1 == id ) { + BaseClass.assertAPTR(userParam01Ptr, usrParam.getData()); + } else if( 2 == id ) { + BaseClass.assertAPTR(userParam02Ptr, usrParam.getData()); + } + final long res = val * usrParam.getI(); + id_res[0] = res; + usrParam.setR(res); + usrParam.setId(id); + System.err.println("chapter11a.myCallback02: id "+id+", val "+val); + } + }; + final int id1 = 1; + final int id2 = 2; + final int id3 = 3; + final MessageCallback11aKey id1Key = new MessageCallback11aKey(id1); + final MessageCallback11aKey id2Key = new MessageCallback11aKey(id2); + final MessageCallback11aKey id3Key = new MessageCallback11aKey(id3); + final T2_Callback11UserType userParam01 = T2_Callback11UserType.create(); // native toolkit should set API version + userParam01.setData(userParam01Ptr); + userParam01.setI(1); + final T2_Callback11UserType userParam02 = T2_Callback11UserType.create(); // native toolkit should set API version + userParam02.setData(userParam02Ptr); + userParam02.setI(2); + Assert.assertEquals( 1, userParam01.getI()); + Assert.assertEquals( 0, userParam01.getR()); + Assert.assertEquals( 0, userParam01.getId()); + Assert.assertEquals( 2, userParam02.getI()); + Assert.assertEquals( 0, userParam02.getR()); + Assert.assertEquals( 0, userParam02.getId()); + + Assert.assertEquals(false, bt2.isMessageCallback11aMapped(id1Key)); + Assert.assertEquals(false, bt2.isMessageCallback11aMapped(id2Key)); + Assert.assertEquals(false, bt2.isMessageCallback11aMapped(id3Key)); + Assert.assertEquals(0, bt2.getMessageCallback11aKeys().size()); + + // 1st mapping: buffer1 -> myCallback01, userParam01 + bt2.MessageCallback11a(id1, myCallback01, userParam01); + Assert.assertEquals(true, bt2.isMessageCallback11aMapped(id1Key)); + Assert.assertEquals(false, bt2.isMessageCallback11aMapped(id2Key)); + Assert.assertEquals(false, bt2.isMessageCallback11aMapped(id3Key)); + Assert.assertEquals(userParam01, bt2.getMessageCallback11aUserParam(id1Key)); + Assert.assertEquals(null, bt2.getMessageCallback11aUserParam(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11aUserParam(id3Key)); + Assert.assertEquals(myCallback01, bt2.getMessageCallback11a(id1Key)); + Assert.assertEquals(null, bt2.getMessageCallback11a(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11a(id3Key)); + Assert.assertEquals(1, bt2.getMessageCallback11aKeys().size()); + { + final Set<MessageCallback11aKey> keys = bt2.getMessageCallback11aKeys(); + Assert.assertEquals(true, keys.contains(id1Key)); + Assert.assertEquals(false, keys.contains(id2Key)); + Assert.assertEquals(false, keys.contains(id3Key)); + } + + // 2nd mapping: buffer2 -> myCallback02, userParam02 + bt2.MessageCallback11a(id2, myCallback02, userParam02); + Assert.assertEquals(true, bt2.isMessageCallback11aMapped(id1Key)); + Assert.assertEquals(true, bt2.isMessageCallback11aMapped(id2Key)); + Assert.assertEquals(false, bt2.isMessageCallback11aMapped(id3Key)); + Assert.assertEquals(userParam01, bt2.getMessageCallback11aUserParam(id1Key)); + Assert.assertEquals(userParam02, bt2.getMessageCallback11aUserParam(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11aUserParam(id3Key)); + Assert.assertEquals(myCallback01, bt2.getMessageCallback11a(id1Key)); + Assert.assertEquals(myCallback02, bt2.getMessageCallback11a(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11a(id3Key)); + Assert.assertEquals(2, bt2.getMessageCallback11aKeys().size()); + { + final Set<MessageCallback11aKey> keys = bt2.getMessageCallback11aKeys(); + Assert.assertEquals(true, keys.contains(id1Key)); + Assert.assertEquals(true, keys.contains(id2Key)); + Assert.assertEquals(false, keys.contains(id3Key)); + } + + { + final Thread thread = new Thread(new Runnable() { + @Override + public void run() { + bt2.MessageCallback11aInject(id1, 10); // buffer1 -> myCallback01, userParam01 + } + }); + thread.start(); + thread.join(); + Assert.assertEquals( 10+1, id_res[0]); + Assert.assertEquals( 1, userParam01.getI()); + Assert.assertEquals( 10+1, userParam01.getR()); + Assert.assertEquals( 1, userParam01.getId()); + Assert.assertEquals( 2, userParam02.getI()); + Assert.assertEquals( 0, userParam02.getR()); + Assert.assertEquals( 0, userParam02.getId()); + } + { + bt2.MessageCallback11aInject(id2, 10); // buffer2 -> myCallback02, userParam02 + Assert.assertEquals( 10*2, id_res[0]); + Assert.assertEquals( 1, userParam01.getI()); + Assert.assertEquals( 10+1, userParam01.getR()); + Assert.assertEquals( 1, userParam01.getId()); + Assert.assertEquals( 2, userParam02.getI()); + Assert.assertEquals( 10*2, userParam02.getR()); + Assert.assertEquals( 2, userParam02.getId()); + } + + // Switch the callback function for buffer2 -> myCallback01, userParam02 + bt2.MessageCallback11a(id2, myCallback01, userParam02); + Assert.assertEquals(true, bt2.isMessageCallback11aMapped(id1Key)); + Assert.assertEquals(true, bt2.isMessageCallback11aMapped(id2Key)); + Assert.assertEquals(false, bt2.isMessageCallback11aMapped(id3Key)); + Assert.assertEquals(userParam01, bt2.getMessageCallback11aUserParam(id1Key)); + Assert.assertEquals(userParam02, bt2.getMessageCallback11aUserParam(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11aUserParam(id3Key)); + Assert.assertEquals(myCallback01, bt2.getMessageCallback11a(id1Key)); + Assert.assertEquals(myCallback01, bt2.getMessageCallback11a(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11a(id3Key)); + + { + bt2.MessageCallback11aInject(id1, 11); // buffer1 -> myCallback01, userParam01 + Assert.assertEquals( 11+1, id_res[0]); + Assert.assertEquals( 1, userParam01.getI()); + Assert.assertEquals( 11+1, userParam01.getR()); + Assert.assertEquals( 1, userParam01.getId()); + Assert.assertEquals( 2, userParam02.getI()); + Assert.assertEquals( 10*2, userParam02.getR()); + Assert.assertEquals( 2, userParam02.getId()); + } + { + final Thread thread = new Thread(new Runnable() { + @Override + public void run() { + bt2.MessageCallback11aInject(id2, 22); // buffer2 -> myCallback01, userParam02 + } + }); + thread.start(); + thread.join(); + Assert.assertEquals( 22+2, id_res[0]); + Assert.assertEquals( 1, userParam01.getI()); + Assert.assertEquals( 11+1, userParam01.getR()); + Assert.assertEquals( 1, userParam01.getId()); + Assert.assertEquals( 2, userParam02.getI()); + Assert.assertEquals( 22+2, userParam02.getR()); + Assert.assertEquals( 2, userParam02.getId()); + } + + // Just release the buffer2 callback and mapped resources + bt2.MessageCallback11a(id2, null, userParam02); // usrptr is not key, only id is key! + Assert.assertEquals(true, bt2.isMessageCallback11aMapped(id1Key)); + Assert.assertEquals(false, bt2.isMessageCallback11aMapped(id2Key)); + Assert.assertEquals(false, bt2.isMessageCallback11aMapped(id3Key)); + Assert.assertEquals(userParam01, bt2.getMessageCallback11aUserParam(id1Key)); + Assert.assertEquals(null, bt2.getMessageCallback11aUserParam(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11aUserParam(id3Key)); + Assert.assertEquals(myCallback01, bt2.getMessageCallback11a(id1Key)); + Assert.assertEquals(null, bt2.getMessageCallback11a(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11a(id3Key)); + Assert.assertEquals(1, bt2.getMessageCallback11aKeys().size()); + { + final Set<MessageCallback11aKey> keys = bt2.getMessageCallback11aKeys(); + Assert.assertEquals(true, keys.contains(id1Key)); + Assert.assertEquals(false, keys.contains(id2Key)); + Assert.assertEquals(false, keys.contains(id3Key)); + } + + // Just release the buffer1 callback and mapped resources + bt2.MessageCallback11a(id1, null, null); // usrptr is not key, only id is key! + Assert.assertEquals(false, bt2.isMessageCallback11aMapped(id1Key)); + Assert.assertEquals(false, bt2.isMessageCallback11aMapped(id2Key)); + Assert.assertEquals(false, bt2.isMessageCallback11aMapped(id3Key)); + Assert.assertEquals(null, bt2.getMessageCallback11aUserParam(id1Key)); + Assert.assertEquals(null, bt2.getMessageCallback11aUserParam(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11aUserParam(id3Key)); + Assert.assertEquals(null, bt2.getMessageCallback11a(id1Key)); + Assert.assertEquals(null, bt2.getMessageCallback11a(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11a(id3Key)); + Assert.assertEquals(0, bt2.getMessageCallback11aKeys().size()); + { + final Set<MessageCallback11aKey> keys = bt2.getMessageCallback11aKeys(); + Assert.assertEquals(false, keys.contains(id1Key)); + Assert.assertEquals(false, keys.contains(id2Key)); + Assert.assertEquals(false, keys.contains(id3Key)); + } + + { + bt2.MessageCallback11aInject(id2, 5); // unmapped, no change in data + Assert.assertEquals( 22+2, id_res[0]); + Assert.assertEquals( 1, userParam01.getI()); + Assert.assertEquals( 11+1, userParam01.getR()); + Assert.assertEquals( 1, userParam01.getId()); + Assert.assertEquals( 2, userParam02.getI()); + Assert.assertEquals( 22+2, userParam02.getR()); + Assert.assertEquals( 2, userParam02.getId()); + } + } + + /** + * Test Bindingtest2 with T2_CallbackFunc11 JavaCallback via MessageCallback11b() + * using the default MessageCallback11bKey class. + */ + @Test + public void chapter11b() throws Exception { + final Bindingtest2 bt2 = new Bindingtest2Impl(); + + final long userParam01Ptr = 0xAFFEBEAFC0FFEEL; + final long userParam02Ptr = 0xC0FFEEDEADBEAFL; + + final long[] id_res = { -1 }; + final T2_CallbackFunc11 myCallback01 = new T2_CallbackFunc11() { + @Override + public void callback(final long id, final T2_Callback11UserType usrParam, final long val) { + Assert.assertEquals(42, usrParam.getApiVersion()); // native toolkit should have set API version + if( 1 == id ) { + BaseClass.assertAPTR(userParam01Ptr, usrParam.getData()); + } else if( 2 == id ) { + BaseClass.assertAPTR(userParam02Ptr, usrParam.getData()); + } + final long res = val + id; + id_res[0] = res; + usrParam.setR(res); + usrParam.setId(id); + System.err.println("chapter11b.myCallback01: id "+id+", val "+val); + } + }; + final T2_CallbackFunc11 myCallback02 = new T2_CallbackFunc11() { + @Override + public void callback(final long id, final T2_Callback11UserType usrParam, final long val) { + Assert.assertEquals(42, usrParam.getApiVersion()); // native toolkit should have set API version + if( 1 == id ) { + BaseClass.assertAPTR(userParam01Ptr, usrParam.getData()); + } else if( 2 == id ) { + BaseClass.assertAPTR(userParam02Ptr, usrParam.getData()); + } + final long res = val * id; + id_res[0] = res; + usrParam.setR(res); + usrParam.setId(id); + System.err.println("chapter11b.myCallback02: id "+id+", val "+val); + } + }; + final int id1 = 1; + final int id2 = 2; + final int id3 = 3; + final MessageCallback11bKey id1Key = new MessageCallback11bKey(id1); + final MessageCallback11bKey id2Key = new MessageCallback11bKey(id2); + final MessageCallback11bKey id3Key = new MessageCallback11bKey(id3); + + Assert.assertEquals(false, bt2.isMessageCallback11bMapped(id1Key)); + Assert.assertEquals(false, bt2.isMessageCallback11bMapped(id2Key)); + Assert.assertEquals(false, bt2.isMessageCallback11bMapped(id3Key)); + Assert.assertEquals(0, bt2.getMessageCallback11bKeys().size()); + + // 1st mapping: buffer1 -> myCallback01, userParam01Ptr + bt2.MessageCallback11b(id1, myCallback01, userParam01Ptr); + Assert.assertEquals(true, bt2.isMessageCallback11bMapped(id1Key)); + Assert.assertEquals(false, bt2.isMessageCallback11bMapped(id2Key)); + Assert.assertEquals(false, bt2.isMessageCallback11bMapped(id3Key)); + Assert.assertEquals(null, bt2.getMessageCallback11bUserParam(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11bUserParam(id3Key)); + Assert.assertEquals(myCallback01, bt2.getMessageCallback11b(id1Key)); + Assert.assertEquals(null, bt2.getMessageCallback11b(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11b(id3Key)); + Assert.assertEquals(1, bt2.getMessageCallback11bKeys().size()); + { + final Set<MessageCallback11bKey> keys = bt2.getMessageCallback11bKeys(); + Assert.assertEquals(true, keys.contains(id1Key)); + Assert.assertEquals(false, keys.contains(id2Key)); + Assert.assertEquals(false, keys.contains(id3Key)); + } + + // 2nd mapping: buffer2 -> myCallback02, userParam02Ptr + bt2.MessageCallback11b(id2, myCallback02, userParam02Ptr); + Assert.assertEquals(true, bt2.isMessageCallback11bMapped(id1Key)); + Assert.assertEquals(true, bt2.isMessageCallback11bMapped(id2Key)); + Assert.assertEquals(false, bt2.isMessageCallback11bMapped(id3Key)); + Assert.assertEquals(null, bt2.getMessageCallback11bUserParam(id3Key)); + Assert.assertEquals(null, bt2.getMessageCallback11b(id3Key)); + Assert.assertEquals(2, bt2.getMessageCallback11bKeys().size()); + { + final Set<MessageCallback11bKey> keys = bt2.getMessageCallback11bKeys(); + Assert.assertEquals(true, keys.contains(id1Key)); + Assert.assertEquals(true, keys.contains(id2Key)); + Assert.assertEquals(false, keys.contains(id3Key)); + } + + { + final Thread thread = new Thread(new Runnable() { + @Override + public void run() { + bt2.MessageCallback11bInject(id1, 10); // buffer1 -> myCallback01, userParam01 + } + }); + thread.start(); + thread.join(); + Assert.assertEquals( 10+1, id_res[0]); + } + { + bt2.MessageCallback11bInject(id2, 10); // buffer2 -> myCallback02, userParam02 + Assert.assertEquals( 10*2, id_res[0]); + } + + // Switch the callback function for buffer2 -> myCallback01, userParam02Ptr + bt2.MessageCallback11b(id2, myCallback01, userParam02Ptr); + Assert.assertEquals(true, bt2.isMessageCallback11bMapped(id1Key)); + Assert.assertEquals(true, bt2.isMessageCallback11bMapped(id2Key)); + Assert.assertEquals(false, bt2.isMessageCallback11bMapped(id3Key)); + Assert.assertEquals(null, bt2.getMessageCallback11bUserParam(id3Key)); + Assert.assertEquals(myCallback01, bt2.getMessageCallback11b(id1Key)); + Assert.assertEquals(myCallback01, bt2.getMessageCallback11b(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11b(id3Key)); + + { + bt2.MessageCallback11bInject(id1, 11); // buffer1 -> myCallback01, userParam01 + Assert.assertEquals( 11+1, id_res[0]); + } + { + final Thread thread = new Thread(new Runnable() { + @Override + public void run() { + bt2.MessageCallback11bInject(id2, 22); // buffer2 -> myCallback01, userParam02 + } + }); + thread.start(); + thread.join(); + Assert.assertEquals( 22+2, id_res[0]); + } + + // Just release the buffer2 callback and mapped resources + bt2.MessageCallback11b(id2, null, userParam02Ptr); // usrptr is not key, only id is key! + Assert.assertEquals(true, bt2.isMessageCallback11bMapped(id1Key)); + Assert.assertEquals(false, bt2.isMessageCallback11bMapped(id2Key)); + Assert.assertEquals(false, bt2.isMessageCallback11bMapped(id3Key)); + Assert.assertEquals(null, bt2.getMessageCallback11bUserParam(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11bUserParam(id3Key)); + Assert.assertEquals(myCallback01, bt2.getMessageCallback11b(id1Key)); + Assert.assertEquals(null, bt2.getMessageCallback11b(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11b(id3Key)); + Assert.assertEquals(1, bt2.getMessageCallback11bKeys().size()); + { + final Set<MessageCallback11bKey> keys = bt2.getMessageCallback11bKeys(); + Assert.assertEquals(true, keys.contains(id1Key)); + Assert.assertEquals(false, keys.contains(id2Key)); + Assert.assertEquals(false, keys.contains(id3Key)); + } + + // Just release the buffer1 callback and mapped resources + bt2.MessageCallback11b(id1, null, 0); // usrptr is not key, only id is key! + Assert.assertEquals(false, bt2.isMessageCallback11bMapped(id1Key)); + Assert.assertEquals(false, bt2.isMessageCallback11bMapped(id2Key)); + Assert.assertEquals(false, bt2.isMessageCallback11bMapped(id3Key)); + Assert.assertEquals(null, bt2.getMessageCallback11bUserParam(id1Key)); + Assert.assertEquals(null, bt2.getMessageCallback11bUserParam(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11bUserParam(id3Key)); + Assert.assertEquals(null, bt2.getMessageCallback11b(id1Key)); + Assert.assertEquals(null, bt2.getMessageCallback11b(id2Key)); + Assert.assertEquals(null, bt2.getMessageCallback11b(id3Key)); + Assert.assertEquals(0, bt2.getMessageCallback11bKeys().size()); + { + final Set<MessageCallback11bKey> keys = bt2.getMessageCallback11bKeys(); + Assert.assertEquals(false, keys.contains(id1Key)); + Assert.assertEquals(false, keys.contains(id2Key)); + Assert.assertEquals(false, keys.contains(id3Key)); + } + + { + bt2.MessageCallback11bInject(id2, 5); // unmapped, no change in data + Assert.assertEquals( 22+2, id_res[0]); + } + } + public static class CustomMessageCallback11Key { public CustomMessageCallback11Key() { } 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 7a7cb43..28fb3aa 100644 --- a/src/junit/com/jogamp/gluegen/test/junit/generation/test2.c +++ b/src/junit/com/jogamp/gluegen/test/junit/generation/test2.c @@ -34,16 +34,16 @@ T2_PointerStorage * createT2PointerStorage() { for(int i=0; i<10; ++i) { s->int32PtrArray[i] = &StaticInt32Array[i]; } - s->undefStructPtr = &StaticUndefStructArray[0]; + s->undefStructPtr = (T2_UndefStructPtr) &StaticUndefStructArray[0]; for(int i=0; i<10; ++i) { - s->undefStructPtrArray[i] = &StaticUndefStructArray[i]; + s->undefStructPtrArray[i] = (T2_UndefStructPtr) &StaticUndefStructArray[i]; } for(int i=0; i<10; ++i) { - s->customFuncAVariantsArray[i] = ( i %2 == 0 ) ? CustomFuncA1 : CustomFuncA2; + s->customFuncAVariantsArray[i] = (T2_CustomFuncA) ( ( i %2 == 0 ) ? CustomFuncA1 : CustomFuncA2 ); } for(int i=0; i<10; ++i) { - s->customFuncBVariantsArray[i] = ( i %2 == 0 ) ? CustomFuncB1 : CustomFuncB2; + s->customFuncBVariantsArray[i] = (T2_CustomFuncB) ( ( i %2 == 0 ) ? CustomFuncB1 : CustomFuncB2 ); } return s; } @@ -61,8 +61,8 @@ int Initialize(T2_InitializeOptions* Options) { strncpy((char*)Options->ProductVersion, "Product Version", 100); // yuck: nonsense-warning Options->ApiVersion = 1; Options->Reserved1 = (void*) 0x0000CAFFEEBEEFUL; - Options->CustomFuncA1 = CustomFuncA1; - *( (T2_CustomFuncA*) &Options->CustomFuncA2 ) = CustomFuncA2; // yuck: real yuck + Options->CustomFuncA1 = (T2_CustomFuncA) CustomFuncA1; + *( (T2_CustomFuncA*) &Options->CustomFuncA2 ) = (T2_CustomFuncA) CustomFuncA2; // yuck: real yuck Options->CustomFuncB1 = CustomFuncB1; Options->CustomFuncB2 = CustomFuncB2; Options->customFuncBVariants[0] = CustomFuncB1; @@ -148,6 +148,8 @@ void alBufferCallback0(int buffer /* key */, int format, int freq, ALBUFFERCALLB void alBufferCallback0Inject(int buffer, int sampledata, int numbytes) { if( buffer < 0 || MAX_AL_BUFFER <= buffer ) { fprintf(stderr, "Error: alBufferCallback0Inject: buffer not in range [0..%d), is %d\n", MAX_AL_BUFFER, buffer); + fflush(NULL); + return; } if( NULL != alBufferCallback0_callback[buffer] ) { fprintf(stderr, "XXX alBufferCallback0Inject: buffer %d, func %p, user %p\n", buffer, alBufferCallback0_callback[buffer], alBufferCallback0_userptr[buffer]); @@ -176,6 +178,8 @@ void alBufferCallback1(int buffer /* key */, int format, int freq, ALBUFFERCALLB void alBufferCallback1Inject(int buffer, int sampledata, int numbytes) { if( buffer < 0 || MAX_AL_BUFFER <= buffer ) { fprintf(stderr, "Error: alBufferCallback1Inject: buffer not in range [0..%d), is %d\n", MAX_AL_BUFFER, buffer); + fflush(NULL); + return; } if( NULL != alBufferCallback1_callback[buffer] ) { fprintf(stderr, "XXX alBufferCallback1Inject: buffer %d, func %p, user %p\n", buffer, alBufferCallback1_callback[buffer], alBufferCallback1_userptr[buffer]); @@ -184,3 +188,69 @@ void alBufferCallback1Inject(int buffer, int sampledata, int numbytes) { } } +// +// +// + +static const int MAX_C11_BUFFER = 5; + +static T2_CallbackFunc11 MessageCallback11a_callback[] = { NULL, NULL, NULL, NULL, NULL }; +static T2_Callback11UserType* MessageCallback11a_userptr[] = { NULL, NULL, NULL, NULL, NULL }; + +void MessageCallback11a(size_t id /* key */, T2_CallbackFunc11 cbFunc, const T2_Callback11UserType* usrParam) { + if( id < 0 || MAX_C11_BUFFER <= id ) { + fprintf(stderr, "Error: MessageCallback11a: id not in range [0..%d), is %d\n", MAX_C11_BUFFER, id); + } else { + MessageCallback11a_callback[id] = cbFunc; + MessageCallback11a_userptr[id] = (T2_Callback11UserType*)usrParam; + if( NULL != usrParam ) { + MessageCallback11a_userptr[id]->ApiVersion = 42; + } + fprintf(stderr, "XXX MessageCallback11a id %d -> func %p, user %p\n", id, cbFunc, usrParam); + } + fflush(NULL); +} +void MessageCallback11aInject(size_t id, long val) { + if( id < 0 || MAX_C11_BUFFER <= id ) { + fprintf(stderr, "Error: MessageCallback11aInjecta: id not in range [0..%d), is %d\n", MAX_C11_BUFFER, id); + fflush(NULL); + return; + } + if( NULL != MessageCallback11a_callback[id] ) { + fprintf(stderr, "XXX MessageCallback11aInjecta: id %d, func %p, user %p\n", id, MessageCallback11a_callback[id], MessageCallback11a_userptr[id]); + fflush(NULL); + (*MessageCallback11a_callback[id])(id, MessageCallback11a_userptr[id], val); + } +} + +// +// +// + +static T2_CallbackFunc11 MessageCallback11b_callback[] = { NULL, NULL, NULL, NULL, NULL }; +static T2_Callback11UserType MessageCallback11b_userptr[]; + +void MessageCallback11b(size_t id /* key */, T2_CallbackFunc11 cbFunc, void* Data) { + if( id < 0 || MAX_C11_BUFFER <= id ) { + fprintf(stderr, "Error: MessageCallback11b: id not in range [0..%d), is %d\n", MAX_C11_BUFFER, id); + } else { + MessageCallback11b_callback[id] = cbFunc; + MessageCallback11b_userptr[id].ApiVersion = 42; + MessageCallback11b_userptr[id].Data = Data; + fprintf(stderr, "XXX MessageCallback11b id %d -> func %p, user %p\n", id, cbFunc, Data); + } + fflush(NULL); +} +void MessageCallback11bInject(size_t id, long val) { + if( id < 0 || MAX_C11_BUFFER <= id ) { + fprintf(stderr, "Error: MessageCallback11bInject: id not in range [0..%d), is %d\n", MAX_C11_BUFFER, id); + fflush(NULL); + return; + } + if( NULL != MessageCallback11b_callback[id] ) { + fprintf(stderr, "XXX MessageCallback11bInject: id %d, func %p, user %p\n", id, MessageCallback11b_callback[id], &MessageCallback11b_userptr[id]); + fflush(NULL); + (*MessageCallback11b_callback[id])(id, &MessageCallback11b_userptr[id], val); + } +} + 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 472471c..83045cb 100644 --- a/src/junit/com/jogamp/gluegen/test/junit/generation/test2.cfg +++ b/src/junit/com/jogamp/gluegen/test/junit/generation/test2.cfg @@ -158,14 +158,20 @@ JavaCallbackKey alBufferCallback1 0 # Begin JavaCallback # -# typedef void ( * T2_CallbackFunc11)(const T2_Callback11UserType* usrParam); -# void MessageCallback11(T2_CallbackFunc11 cbFunc, const T2_Callback11UserType* usrParam); -# void InjectMessageCallback11(size_t id, const char* msg); -ArgumentIsString InjectMessageCallback11 1 -JavaCallbackDef MessageCallback11 T2_CallbackFunc11 0 com.jogamp.gluegen.test.junit.generation.Test4JavaCallback.CustomMessageCallback11Key +# typedef void ( * T2_CallbackFunc11)(size_t id, const T2_Callback11UserType* usrParam); +# void MessageCallback11a(size_t id /* key */, T2_CallbackFunc11 cbFunc, const T2_Callback11UserType* usrParam); +# void MessageCallback11aInject(size_t id); +#JavaCallbackDef MessageCallback11a T2_CallbackFunc11 1 com.jogamp.gluegen.test.junit.generation.Test4JavaCallback.CustomMessageCallback11Key +JavaCallbackDef MessageCallback11a T2_CallbackFunc11 1 +JavaCallbackKey MessageCallback11a 0 # # End JavaCallback +# void MessageCallback11b(size_t id /* key */, T2_CallbackFunc11 cbFunc, void* Data); +# void MessageCallback11bInject(size_t id); +JavaCallbackDef MessageCallback11b 2 T2_CallbackFunc11 1 +JavaCallbackKey MessageCallback11b 0 + CustomCCode #include "test2.h" Import com.jogamp.gluegen.test.junit.generation.Bindingtest2 @@ -173,6 +179,7 @@ Import com.jogamp.gluegen.test.junit.generation.T2_PointerStorage Import com.jogamp.gluegen.test.junit.generation.T2_InitializeOptions Import com.jogamp.gluegen.test.junit.generation.T2_ThreadAffinity Import com.jogamp.gluegen.test.junit.generation.T2_UserData +Import com.jogamp.gluegen.test.junit.generation.T2_Callback11UserType CustomJavaCode Bindingtest2Impl private static Bindingtest2ProcAddressTable _table = new Bindingtest2ProcAddressTable(); CustomJavaCode Bindingtest2Impl public static void resetProcAddressTable(DynamicLookupHelper lookup) { 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 d067390..183f905 100644 --- a/src/junit/com/jogamp/gluegen/test/junit/generation/test2.h +++ b/src/junit/com/jogamp/gluegen/test/junit/generation/test2.h @@ -89,15 +89,21 @@ void alBufferCallback1(int buffer /* key */, int format, int freq, ALBUFFERCALLB void alBufferCallback1Inject(int buffer, int sampledata, int numbytes); // -// T2_CallbackFunc11 +// T2_CallbackFunc11[ab] // typedef struct { int32_t ApiVersion; void* Data; + long i; + long r; + size_t id; } T2_Callback11UserType; -typedef void ( * T2_CallbackFunc11)(const T2_Callback11UserType* usrParam); +typedef void ( * T2_CallbackFunc11)(size_t id, const T2_Callback11UserType* usrParam, long val); -void MessageCallback11(T2_CallbackFunc11 cbFunc, const T2_Callback11UserType* usrParam); -void InjectMessageCallback11(size_t id, const char* msg); +void MessageCallback11a(size_t id /* key */, T2_CallbackFunc11 cbFunc, const T2_Callback11UserType* usrParam); +void MessageCallback11aInject(size_t id, long val); + +void MessageCallback11b(size_t id /* key */, T2_CallbackFunc11 cbFunc, void* Data); +void MessageCallback11bInject(size_t id, long val); diff --git a/www/index.html b/www/index.html index 812d3b3..b424ec5 100644 --- a/www/index.html +++ b/www/index.html @@ -73,7 +73,7 @@ potential calls to <a href="../doc/GlueGen_Mapping.html#struct-function-pointer-support">embedded function pointer</a>. </p> <p> - GlueGen supports <a href="../doc/GlueGen_Mapping.html#java-callback-from-native-c-api-support">registering Java™ callback methods</a> + GlueGen supports <a href="../doc/GlueGen_Mapping.html#java-callback">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> |