AVPKit
JNIHelper.cpp
1 /*******************************************************************************
2  * Copyright (c) 2024, 2026, Olivier Ayache. All rights reserved.
3  *
4  * This file is part of AVPKit.
5  *
6  * AVPKit is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * AVPKit is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with AVPKit. If not, see <http://www.gnu.org/licenses/>.
18  *******************************************************************************/
19 #include <iostream>
20 
21 #include <stdexcept>
22 
23 #include "config.h"
24 
25 #include "JNIHelper.h"
26 
27 #ifdef HAVE_UNISTD_H
28 #include <unistd.h>
29 #define GET_PROCESS_ID() ((int32_t)getpid())
30 #else
31 #define GET_PROCESS_ID() (0)
32 #endif
33 // declare a signature for the memory manager
34 extern void VSJNI_MemoryManagerInit(JavaVM* aJVM);
35 
36 namespace com { namespace avpkit { namespace ferry {
37  JNIHelper* JNIHelper::sSingleton = 0;
38  volatile bool JNIHelper::sDebuggerAttached = false;
39 
40  JNIHelper*
41  JNIHelper :: getHelper()
42  {
43  JNIHelper *retval = 0;
44  retval = sSingleton;
45  if (!retval) {
46  retval = new JNIHelper();
47  sSingleton = retval;
48  }
49  return retval;
50  }
51 
52  void
53  JNIHelper :: shutdownHelper()
54  {
55  if (sSingleton) {
56  delete sSingleton;
57  sSingleton = 0;
58  }
59  }
60 
61  JNIHelper :: JNIHelper()
62  {
63  mCachedVM = 0;
64  mVersion = JNI_VERSION_1_2;
65  mJNIPointerReference_class = 0;
66  // Now, look for our get and set methods.
67  mJNIPointerReference_setPointer_mid = 0;
68  mJNIPointerReference_getPointer_mid = 0;
69  mOutOfMemoryErrorSingleton = 0;
70 
71  mThread_class = 0;
72  mThread_isInterrupted_mid = 0;
73  mThread_currentThread_mid = 0;
74 
75  mInterruptedException_class = 0;
76  }
77 
78  void
79  JNIHelper :: addCallback(std::list<CallbackHelper*>* list,
80  CallbackFunc callback,
81  void *closure)
82  {
83  CallbackHelper *helper = new CallbackHelper;
84  if (helper)
85  {
86  helper->mCallback = callback;
87  helper->mClosure = closure;
88  list->push_back(helper);
89  helper = 0;
90  }
91  }
92 
93  void
94  JNIHelper :: processCallbacks(std::list<CallbackHelper*>* list,
95  JavaVM* vm,
96  bool execFunc)
97  {
98  while (!list->empty())
99  {
100  // then call them.
101  CallbackHelper *helper = list->front();
102  if (helper) {
103  if (helper->mCallback && execFunc)
104  helper->mCallback(vm, helper->mClosure);
105  delete helper;
106  helper = 0;
107  }
108  // now delete the helper ref from the front
109  list->pop_front();
110  }
111  }
112 
113  JNIHelper :: ~JNIHelper()
114  {
115  JNIEnv *env;
116 
117  processCallbacks(&mInitializationCallbacks, 0, false);
118  processCallbacks(&mTerminationCallbacks, mCachedVM, true);
119 
120  env = this->getEnv();
121  if (env)
122  {
123  if (mOutOfMemoryErrorSingleton)
124  {
125  env->DeleteGlobalRef(mOutOfMemoryErrorSingleton);
126  mOutOfMemoryErrorSingleton = 0;
127  }
128  if (mJNIPointerReference_class)
129  {
130  // Tell the JVM we're done with it.
131  env->DeleteWeakGlobalRef(mJNIPointerReference_class);
132  mJNIPointerReference_class = 0;
133  }
134  if (mThread_class)
135  {
136  env->DeleteWeakGlobalRef(mThread_class);
137  mThread_class = 0;
138  }
139  if (mInterruptedException_class)
140  {
141  env->DeleteWeakGlobalRef(mInterruptedException_class);
142  mInterruptedException_class = 0;
143  }
144  }
145  mCachedVM = 0;
146  }
147 
148  void
149  JNIHelper :: setVM(JavaVM * jvm)
150  {
151  if (!mCachedVM)
152  {
153  mCachedVM = jvm;
154 
155  // Now, let's cache the commonly used classes.
156  JNIEnv *env=this->getEnv();
157 
158  // It can be helpful to attach a GDB or Debugger to
159  // a Running JVM. This function attempts to see if that's
160  // required.
161  waitForDebugger(env);
162 
163  jclass cls=0;
164 
165  // let's set up a singleton out of memory error for potential
166  // reuse later
167  cls = env->FindClass("java/lang/OutOfMemoryError");
168  if (!cls || env->ExceptionCheck())
169  return;
170 
171  jmethodID constructor = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
172  if (!constructor || env->ExceptionCheck())
173  return;
174 
175  jstring errorMessage = env->NewStringUTF(
176  "Sorry, but we're all out of native memory. How out"
177  " of native memory are we? Well, we're so out we're"
178  " throwing a exception we allocated at"
179  " the beginning of program time, so while the stack trace will"
180  " be wrong, at least you get some useful feedback");
181  if (!errorMessage)
182  return;
183  jthrowable exception=static_cast<jthrowable>(
184  env->NewObject(cls, constructor, errorMessage));
185  env->DeleteLocalRef(errorMessage);
186  if (!exception) {
187  return;
188  }
189 
190  mOutOfMemoryErrorSingleton = static_cast<jthrowable>(env->NewGlobalRef(exception));
191  if (!mOutOfMemoryErrorSingleton || env->ExceptionCheck())
192  return;
193 
194  env->DeleteLocalRef(cls);
195  cls = env->FindClass("com/avpkit/ferry/JNIPointerReference");
196  if (!cls || env->ExceptionCheck())
197  return;
198  // Keep a reference around
199  mJNIPointerReference_class=env->NewWeakGlobalRef(cls);
200  if (!mJNIPointerReference_class || env->ExceptionCheck())
201  return;
202 
203  // Now, look for our get and set mehods.
204  mJNIPointerReference_setPointer_mid = env->GetMethodID(cls, "setPointer",
205  "(J)J");
206  if (!mJNIPointerReference_setPointer_mid || env->ExceptionCheck())
207  return;
208 
209  mJNIPointerReference_getPointer_mid = env->GetMethodID(cls, "getPointer",
210  "()J");
211  if (!mJNIPointerReference_getPointer_mid || env->ExceptionCheck())
212  return;
213 
214  env->DeleteLocalRef(cls);
215 
216  cls = env->FindClass("com/avpkit/ferry/JNIThreadProxy");
217  if (!cls || env->ExceptionCheck())
218  return;
219  // Keep a reference around
220  mThread_class=env->NewWeakGlobalRef(cls);
221  if (!mThread_class || env->ExceptionCheck())
222  return;
223 
224  // now look for the isInterrupted method
225  mThread_currentThread_mid = env->GetStaticMethodID(cls,
226  "currentThread", "()Ljava/lang/Thread;");
227  if (!mThread_currentThread_mid || env->ExceptionCheck())
228  return;
229 
230  mThread_isInterrupted_mid = env->GetMethodID(cls,
231  "isInterrupted", "()Z");
232  if (!mThread_isInterrupted_mid || env->ExceptionCheck())
233  return;
234  mThread_interrupt_mid = env->GetMethodID(cls,
235  "interrupt", "()V");
236  if (!mThread_interrupt_mid || env->ExceptionCheck())
237  return;
238 
239  env->DeleteLocalRef(cls);
240 
241  // Are there any pending callbacks?
242  processCallbacks(&mInitializationCallbacks, mCachedVM, true);
243 
244  // And Initialize the JNI Memory manager;
245  // This means that any callbacks will NOT use the
246  // JVM for memory management.
247  VSJNI_MemoryManagerInit(mCachedVM);
248 
249  }
250  }
251 
252  void
253  JNIHelper :: waitForDebugger(JNIEnv* env)
254  {
255  // Find the System class.
256  jclass cls = 0;
257  jstring jDebugKey = 0;
258  jstring jDebugVal = 0;
259  const char* debugKey = "com.avpkit.ferry.WaitForDebugger";
260  const char* debugVal = 0;
261 
262  try
263  {
264  cls = env->FindClass("java/lang/System");
265  if (!cls)
266  throw std::runtime_error("could not find System class");
267 
268  jmethodID getProperty = env->GetStaticMethodID(cls, "getProperty",
269  "(Ljava/lang/String;)Ljava/lang/String;");
270  if (!getProperty)
271  throw std::runtime_error("could not find getProperty class");
272 
273  jDebugKey = env->NewStringUTF(debugKey);
274  if (!jDebugKey)
275  throw std::runtime_error("could not create java string for the debug key");
276 
277  jDebugVal = (jstring) env->CallStaticObjectMethod(cls, getProperty, jDebugKey);
278  if (jDebugVal)
279  {
280  debugVal = env->GetStringUTFChars(jDebugVal, 0);
281  if (debugVal && *debugVal)
282  {
283  // It's set to some out put value. Sit and spin waiting
284  // for a debugger.
285  int32_t pid = GET_PROCESS_ID();
286 
287  std::cerr << "Process " << pid << " waiting for a debugger. Please attach and set \"JNIHelper::sDebuggerAttached\" to true"
288  << " (" << __FILE__ << ":" << __LINE__+1 <<")" << std::endl;
289  while (!sDebuggerAttached) ;
290  }
291  }
292  }
293  catch (std::exception e)
294  {
295  std::cerr << "Got exception while checking for debugger: "
296  << e.what()
297  << std::endl;
298  }
299 
300  // And do the clean up here.
301  if (jDebugKey)
302  env->DeleteLocalRef(jDebugKey);
303 
304  if (jDebugVal && debugVal)
305  env->ReleaseStringUTFChars(jDebugVal, debugVal);
306 
307  if (cls)
308  env->DeleteLocalRef(cls);
309  }
310 
311  void
312  JNIHelper :: registerInitializationCallback(
313  CallbackFunc callback,
314  void * closure)
315  {
316  if (callback)
317  {
318  if (!mCachedVM)
319  {
320  // we don't have a callback; so save the function
321  // call for later.
322  addCallback(&mInitializationCallbacks, callback, closure);
323  }
324  else
325  {
326  // We have a VM already; just callback immediately.
327  callback(mCachedVM, closure);
328  }
329  }
330  }
331 
332  void
333  JNIHelper :: registerTerminationCallback(
334  CallbackFunc callback,
335  void * closure)
336  {
337  if (callback)
338  {
339  addCallback(&mTerminationCallbacks, callback, closure);
340  }
341  }
342 
343  JavaVM *
344  JNIHelper :: getVM()
345  {
346  return mCachedVM;
347  }
348 
349  void *
350  JNIHelper :: getPointer(jobject pointerRef)
351  {
352  JNIEnv *env = this->getEnv();
353  if (!env)
354  return 0;
355 
356  if (env->ExceptionCheck())
357  return 0;
358 
359  jlong pointerVal = env->CallLongMethod(pointerRef,
360  mJNIPointerReference_getPointer_mid);
361 
362  /*
363  * What's going on here is inherently unsafe, but is designed
364  * to pass pointer values into Java. It is structured like this
365  * to compile without warning on as many OSes as possible
366  *
367  * It'll only work for 64-bit or lower os'es (what you complaining
368  * about -- you got a 65-bit OS you're chomping to try out?).
369  *
370  * Now stop looking. Go back to your day job.
371  */
372  void *retval = (void*)(size_t)pointerVal;
373 
374  return retval;
375  }
376 
377  void *
378  JNIHelper :: setPointer(jobject pointerRef, void *newVal)
379  {
380  JNIEnv *env = this->getEnv();
381  if (!env)
382  return 0;
383  if (env->ExceptionCheck())
384  return 0;
385  if (!pointerRef)
386  return 0;
387 
388  jlong newPointerVal = (jlong)(size_t)newVal;
389 
390  jlong pointerVal = env->CallLongMethod(pointerRef,
391  mJNIPointerReference_setPointer_mid, newPointerVal);
392 
393  void *retval = (void*)(size_t)pointerVal;
394 
395  return retval;
396  }
397 
398  JNIEnv *
399  JNIHelper :: getEnv()
400  {
401  JNIEnv *env=0;
402  if (mCachedVM) {
403  // sometimes libraries will start separate threads; if they do
404  // we try to catch them here. Examples of this is AVPKit
405  // when parsing UDP
406  mCachedVM->GetEnv((void**)(void*)&env, mVersion);
407  if (!env) {
408  mCachedVM->AttachCurrentThread((void**)(void*)&env, 0);
409  }
410  }
411  return env;
412  }
413 
414  jint
415  JNIHelper :: getJNIVersion()
416  {
417  return mVersion;
418  }
419 
420  jobject
421  JNIHelper :: newLocalRef(jobject ref)
422  {
423  jobject retval = 0;
424  JNIEnv *env = this->getEnv();
425  if (!env)
426  return 0;
427  if (env->ExceptionCheck())
428  return 0;
429 
430  retval = env->NewLocalRef(ref);
431  if (!retval)
432  throw std::runtime_error("could not get JVM LocalRef");
433  if (env->ExceptionCheck()) {
434  env->DeleteLocalRef(retval);
435  throw std::runtime_error("could not get JVM LocalRef");
436  }
437  return retval;
438  }
439 
440  void
441  JNIHelper :: deleteLocalRef(jobject ref)
442  {
443  JNIEnv *env = this->getEnv();
444  if (!env)
445  throw std::runtime_error("attempted to delete LocalRef without JVM");
446  env->DeleteLocalRef(ref);
447  }
448 
449  jobject
450  JNIHelper :: newGlobalRef(jobject ref)
451  {
452  jobject retval = 0;
453  JNIEnv *env = this->getEnv();
454  if (!env)
455  return 0;
456  if (env->ExceptionCheck())
457  return 0;
458 
459  retval = env->NewGlobalRef(ref);
460  if (!retval)
461  throw std::runtime_error("could not get JVM GlobalRef");
462  if (env->ExceptionCheck())
463  {
464  env->DeleteGlobalRef(retval);
465  throw std::runtime_error("could not get JVM GlobalRef");
466  }
467  return retval;
468  }
469 
470  void
471  JNIHelper :: deleteGlobalRef(jobject ref)
472  {
473  JNIEnv *env = this->getEnv();
474  if (!env)
475  throw std::runtime_error("attempted to delete GlobalRef without JVM");
476  env->DeleteGlobalRef(ref);
477  }
478 
479  jweak
480  JNIHelper :: newWeakGlobalRef(jobject ref)
481  {
482  if (!ref)
483  return 0;
484  JNIEnv *env = this->getEnv();
485  if (!env)
486  return 0;
487  if (env->ExceptionCheck())
488  return 0;
489 
490  jweak retval = 0;
491  retval = env->NewWeakGlobalRef(ref);
492  if (!retval)
493  throw std::runtime_error("could not get JVM WeakGlobal ref");
494  if (env->ExceptionCheck())
495  {
496  env->DeleteWeakGlobalRef(retval);
497  throw std::runtime_error("could not get JVM WeakGlobal ref");
498  }
499  return retval;
500  }
501 
502  void
503  JNIHelper :: deleteWeakGlobalRef(jweak ref)
504  {
505  if (!ref)
506  return;
507  JNIEnv *env = this->getEnv();
508  if (!env)
509  throw std::runtime_error("attempted to delete WeakGlobalRef without JVM");
510  env->DeleteWeakGlobalRef(ref);
511  }
512 
513  void
515  {
522  JNIEnv *env = this->getEnv();
523  if (!env)
524  return;
525  // exception already pending?
526  if (env->ExceptionCheck())
527  return;
528  jclass cls=0;
529 
530  // let's set up a singleton out of memory error for potential
531  // reuse later
532  cls = env->FindClass("java/lang/OutOfMemoryError");
533  // couldn't find the class?
534  if (cls) {
535  int retval = env->ThrowNew(cls, "out of native memory");
536  env->DeleteLocalRef(cls);
537  if (retval == 0) // success
538  return;
539  }
540 
541  if (!mOutOfMemoryErrorSingleton)
542  return;
543  env->Throw(mOutOfMemoryErrorSingleton);
544  }
545 
546  int32_t
547  JNIHelper :: isInterrupted()
548  {
549  JNIEnv * env = this->getEnv();
550  if (!env)
551  return 0;
552  if (env->ExceptionCheck())
553  return 1; // a pending exception interrupts us
554  if (!mThread_class ||
555  !mThread_isInterrupted_mid ||
556  !mThread_currentThread_mid)
557  // this is an error if these are not set up, but we're
558  // going to assume no interrupt then.
559  return 0;
560 
561  jclass cls = static_cast<jclass>(env->NewLocalRef(mThread_class));
562  if (!cls)
563  return 0;
564 
565  jobject thread = env->CallStaticObjectMethod(
566  cls,
567  mThread_currentThread_mid);
568  env->DeleteLocalRef(cls);
569  if (!thread || env->ExceptionCheck())
570  return 1;
571  jboolean result = env->CallBooleanMethod(thread,
572  mThread_isInterrupted_mid);
573  env->DeleteLocalRef(thread);
574  if (env->ExceptionCheck())
575  result = true;
576  if (result != JNI_FALSE)
577  return 1;
578  return 0;
579  }
580 
581  void
582  JNIHelper :: interrupt()
583  {
584  JNIEnv * env = this->getEnv();
585  if (!env)
586  return;
587  if (env->ExceptionCheck())
588  return; // a pending exception so we can't continue
589  if (!mThread_class ||
590  !mThread_interrupt_mid ||
591  !mThread_currentThread_mid)
592  // this is an error if these are not set up, but we're
593  // going to assume no interrupt then.
594  return;
595 
596  jclass cls = static_cast<jclass>(env->NewLocalRef(mThread_class));
597  if (!cls)
598  return;
599 
600  jobject thread = env->CallStaticObjectMethod(
601  cls,
602  mThread_currentThread_mid);
603  env->DeleteLocalRef(cls);
604  if (!thread || env->ExceptionCheck())
605  return;
606  env->CallVoidMethod(thread,
607  mThread_interrupt_mid);
608  env->DeleteLocalRef(thread);
609  }
610 
611 
612  bool
613  JNIHelper :: isInterruptedException(jthrowable exception)
614  {
615  JNIEnv * env = this->getEnv();
616  if (!env)
617  return false;
618  if (env->ExceptionCheck())
619  return false; // a pending exception interrupts us
620  if (!mInterruptedException_class)
621  // this is an error if these are not set up, but we're
622  // going to assume no interrupt then.
623  return false;
624  jclass cls = static_cast<jclass>(env->NewLocalRef(mInterruptedException_class));
625  if (!cls)
626  return false;
627 
628  jboolean retval = env->IsInstanceOf(
629  exception,
630  static_cast<jclass>(cls)
631  );
632  env->DeleteLocalRef(cls);
633  if (retval != JNI_FALSE)
634  return true;
635  else
636  return false;
637  }
638 }}}
VS_API_FERRY void throwOutOfMemoryError()
Definition: JNIHelper.cpp:514
WARNING: Do not use logging in this class, and do not set any static file variables to values other t...