Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ namespace jni

#ifdef __cpp_lib_any
template <class T>
static void add(JNIEnv * env, const char * className)
static void add(JNIEnv * env, const char * className, const char * handleClass)
{
std::unordered_map<std::type_index, std::any> & map = getFactoryMap();

map.emplace(std::type_index(typeid(T)), std::make_any<JavaFactory<T>>(JavaFactory<T>(env, className)));
map.emplace(std::type_index(typeid(T)), std::make_any<JavaFactory<T>>(JavaFactory<T>(env, className, handleClass)));
}

template <class T>
Expand Down Expand Up @@ -87,11 +87,11 @@ namespace jni
}
#else
template <class T>
static void add(JNIEnv * env, const char * className)
static void add(JNIEnv * env, const char * className, const char * handleClass)
{
std::unordered_map<std::type_index, unique_void_ptr> & map = getFactoryMap();

map.emplace(std::type_index(typeid(T)), make_unique_void(new JavaFactory<T>(env, className)));
map.emplace(std::type_index(typeid(T)), make_unique_void(new JavaFactory<T>(env, className, handleClass)));
}

template <class T>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ namespace jni
class JavaFactory
{
public:
JavaFactory(JNIEnv * env, const char * className) :
JavaFactory(env, className, "()V")
// The created objects hold their native object in the `nativeHandle` field
// declared by `handleClass`.
JavaFactory(JNIEnv * env, const char * className, const char * handleClass) :
JavaFactory(env, className, "()V", handleClass)
{
}

Expand All @@ -34,7 +36,7 @@ namespace jni
jobject object = env->NewObject(javaClass, javaCtor);
ExceptionCheck(env);

SetHandle(env, object, nativeObject);
SetHandle(env, object, "nativeHandle", nativeObject, handleClass);

return JavaLocalRef<jobject>(env, object);
}
Expand All @@ -45,9 +47,12 @@ namespace jni
}

protected:
JavaFactory(JNIEnv * env, const char * className, const char * ctorSignature) :
// A subclass that overrides create() binds no handle and may pass no `handleClass`.
JavaFactory(JNIEnv * env, const char * className, const char * ctorSignature,
const char * handleClass) :
javaClass(nullptr),
javaCtor(nullptr)
javaCtor(nullptr),
handleClass(handleClass)
{
loadClass(env, className, ctorSignature);
}
Expand All @@ -63,6 +68,7 @@ namespace jni
protected:
JavaGlobalRef<jclass> javaClass;
jmethodID javaCtor;
const char * handleClass;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ bool ExceptionCheck(JNIEnv * env);
jclass FindClass(JNIEnv * env, const char * name);
jmethodID GetMethod(JNIEnv * env, jclass cls, const char * name, const char * sig);
jmethodID GetStaticMethod(JNIEnv * env, jclass cls, const char * name, const char * sig);
// Specify the declaring class when subclasses may hide a handle field.
// Resolves a handle field on the class that declares it, never on the dynamic class of `obj`,
// so that a subclass field of the same name cannot hide it. Returns null with a pending
// IllegalArgumentException when `obj` is no instance of `declaringClass`.
jfieldID GetHandleField(JNIEnv * env, jobject obj, const std::string & fieldName,
const char * declaringClass = nullptr);
const char * declaringClass);
jfieldID GetFieldID(JNIEnv * env, jobject obj, const std::string & fieldName, const char * type);
jfieldID GetFieldID(JNIEnv * env, jclass cls, const std::string & fieldName, const char * type);

Expand All @@ -69,22 +71,9 @@ auto make_unique_void(T * ptr) -> unique_void_ptr
});
}

template<typename T>
jlong GetHandleLong(JNIEnv * env, jobject obj)
{
jfieldID field = GetHandleField(env, obj, "nativeHandle");

if (!field) {
ExceptionCheck(env);
return 0;
}

return env->GetLongField(obj, field);
}

template<typename T>
T * GetHandle(JNIEnv * env, jobject obj, const std::string & handleName,
const char * declaringClass = nullptr)
const char * declaringClass)
{
jfieldID field = GetHandleField(env, obj, handleName, declaringClass);

Expand All @@ -96,15 +85,9 @@ T * GetHandle(JNIEnv * env, jobject obj, const std::string & handleName,
return reinterpret_cast<T *>(env->GetLongField(obj, field));
}

template<typename T>
T * GetHandle(JNIEnv * env, jobject obj)
{
return GetHandle<T>(env, obj, "nativeHandle");
}

template<typename T>
void SetHandle(JNIEnv * env, jobject obj, const std::string & handleName, T * t,
const char * declaringClass = nullptr)
const char * declaringClass)
{
jfieldID field = GetHandleField(env, obj, handleName, declaringClass);

Expand All @@ -118,12 +101,6 @@ void SetHandle(JNIEnv * env, jobject obj, const std::string & handleName, T * t,
env->SetLongField(obj, field, handle);
}

template<typename T>
void SetHandle(JNIEnv * env, jobject obj, T * t)
{
SetHandle<T>(env, obj, "nativeHandle", t);
}

// Replaces the native observer stored in the `handleName` field on `obj`
// with `observer`, deleting whatever was stored there before. Use this
// together with ClearNativeObserver() wherever a Java object registers a
Expand All @@ -132,26 +109,28 @@ void SetHandle(JNIEnv * env, jobject obj, T * t)
// forgotten and leaked -- as opposed to reimplementing the same
// get-old/delete/store-new sequence by hand in each JNI_Foo.cpp.
template<typename T>
void ReplaceNativeObserver(JNIEnv * env, jobject obj, const std::string & handleName, T * observer)
void ReplaceNativeObserver(JNIEnv * env, jobject obj, const std::string & handleName, T * observer,
const char * declaringClass)
{
T * old = GetHandle<T>(env, obj, handleName);
T * old = GetHandle<T>(env, obj, handleName, declaringClass);

delete old;

SetHandle(env, obj, handleName, observer);
SetHandle(env, obj, handleName, observer, declaringClass);
}

// Deletes and clears the native observer stored in the `handleName` field
// on `obj`, if any. Safe to call when no observer is currently registered
// (e.g. from an unregisterObserver()/dispose() implementation that isn't
// sure whether one was ever set).
template<typename T>
void ClearNativeObserver(JNIEnv * env, jobject obj, const std::string & handleName)
void ClearNativeObserver(JNIEnv * env, jobject obj, const std::string & handleName,
const char * declaringClass)
{
T * observer = GetHandle<T>(env, obj, handleName);
T * observer = GetHandle<T>(env, obj, handleName, declaringClass);

if (observer) {
SetHandle<std::nullptr_t>(env, obj, handleName, nullptr);
SetHandle<std::nullptr_t>(env, obj, handleName, nullptr, declaringClass);
delete observer;
}
}
Expand Down
92 changes: 86 additions & 6 deletions webrtc-jni/src/main/cpp/dependencies/jni-voithos/src/JavaUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
#include "JavaWrappedException.h"

#include <exception>
#include <functional>
#include <ios>
#include <map>
#include <mutex>

bool ExceptionCheck(JNIEnv * env)
{
Expand Down Expand Up @@ -118,21 +121,98 @@ jmethodID GetStaticMethod(JNIEnv * env, jclass cls, const char * name, const cha
return method;
}

namespace
{
struct HandleClass
{
jclass cls;
std::map<std::string, jfieldID> fields;
};

struct HandleClasses
{
std::mutex mutex;
std::map<std::string, HandleClass, std::less<>> classes;
};

HandleClasses & GetHandleClasses()
{
// Never freed: the global class references keep the field IDs valid, and native
// threads may still look up handles while the process exits.
static HandleClasses * handleClasses = new HandleClasses();

return *handleClasses;
}
}

jfieldID GetHandleField(JNIEnv * env, jobject obj, const std::string & fieldName,
const char * declaringClass)
{
if (declaringClass == nullptr) {
return GetFieldID(env, obj, fieldName, "J");
HandleClasses & handles = GetHandleClasses();
jclass cls = nullptr;
jfieldID field = nullptr;

{
std::lock_guard<std::mutex> lock(handles.mutex);

auto entry = handles.classes.find(declaringClass);

if (entry != handles.classes.end()) {
cls = entry->second.cls;

auto id = entry->second.fields.find(fieldName);

if (id != entry->second.fields.end()) {
field = id->second;
}
}
}

auto cls = jni::ClassLoaderGetClass(env, declaringClass);
// Resolved without holding the lock, as loading a class calls into Java.
if (cls == nullptr) {
auto localCls = jni::ClassLoaderGetClass(env, declaringClass);

if (cls.get() == nullptr) {
ExceptionCheck(env);
if (localCls.get() == nullptr) {
ExceptionCheck(env);
return nullptr;
}

jclass globalCls = static_cast<jclass>(env->NewGlobalRef(localCls.get()));

std::lock_guard<std::mutex> lock(handles.mutex);

auto entry = handles.classes.emplace(declaringClass, HandleClass { globalCls, {} });

if (!entry.second) {
env->DeleteGlobalRef(globalCls);
}

cls = entry.first->second.cls;
}

if (field == nullptr) {
field = GetFieldID(env, cls, fieldName, "J");

if (field == nullptr) {
return nullptr;
}

std::lock_guard<std::mutex> lock(handles.mutex);

handles.classes.find(declaringClass)->second.fields.emplace(fieldName, field);
}

// The field ID is only valid on instances of the class that declares it.
if (obj == nullptr) {
ThrowJavaException(env, "java/lang/NullPointerException", "Object is null");
return nullptr;
}
if (!env->IsInstanceOf(obj, cls)) {
ThrowJavaException(env, "java/lang/IllegalArgumentException", "Object holds no native handle");
return nullptr;
}

return GetFieldID(env, cls.get(), fieldName, "J");
return field;
}

jfieldID GetFieldID(JNIEnv * env, jobject obj, const std::string & fieldName, const char * type)
Expand Down
43 changes: 43 additions & 0 deletions webrtc-jni/src/main/cpp/include/NativeHandle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2026 Alex Andres
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef JNI_WEBRTC_NATIVE_HANDLE_H_
#define JNI_WEBRTC_NATIVE_HANDLE_H_

#include "JNI_WebRTC.h"
#include "JavaUtils.h"

#include <jni.h>

// The class declaring the field that binds a native object to a Java object. A handle is
// always looked up on its declaring class, as a subclass may declare a field of the same name.
#define NATIVE_OBJECT_CLASS PKG_INTERNAL "NativeObject"

// Returns the native object bound to the NativeObject `obj`.
template<typename T>
T * GetHandle(JNIEnv * env, jobject obj)
{
return GetHandle<T>(env, obj, "nativeHandle", NATIVE_OBJECT_CLASS);
}

// Binds `t` to the NativeObject `obj`.
template<typename T>
void SetHandle(JNIEnv * env, jobject obj, T * t)
{
SetHandle<T>(env, obj, "nativeHandle", t, NATIVE_OBJECT_CLASS);
}

#endif
1 change: 1 addition & 0 deletions webrtc-jni/src/main/cpp/src/JNI_AudioConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "api/audio/audio_processing.h"
#include "media/audio/AudioConverter.h"
#include "JavaUtils.h"
#include "NativeHandle.h"

#include <memory>
#include <stdexcept>
Expand Down
1 change: 1 addition & 0 deletions webrtc-jni/src/main/cpp/src/JNI_AudioDeviceModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "JavaError.h"
#include "JavaRef.h"
#include "JavaUtils.h"
#include "NativeHandle.h"
#include "WebRTCContext.h"
#include "api/audio/create_audio_device_module.h"

Expand Down
1 change: 1 addition & 0 deletions webrtc-jni/src/main/cpp/src/JNI_AudioDeviceModuleBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "JavaRef.h"
#include "JavaString.h"
#include "JavaUtils.h"
#include "NativeHandle.h"
#include "WebRTCContext.h"
#include "api/audio/create_audio_device_module.h"
#include "media/audio/AudioDevice.h"
Expand Down
1 change: 1 addition & 0 deletions webrtc-jni/src/main/cpp/src/JNI_AudioProcessing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "JavaRef.h"
#include "JavaString.h"
#include "JavaUtils.h"
#include "NativeHandle.h"
#include "WebRTCContext.h"

#include "media/audio/AudioProcessing.h"
Expand Down
1 change: 1 addition & 0 deletions webrtc-jni/src/main/cpp/src/JNI_AudioResampler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "JavaObject.h"
#include "JavaRef.h"
#include "JavaUtils.h"
#include "NativeHandle.h"

#include "common_audio/resampler/include/push_resampler.h"

Expand Down
1 change: 1 addition & 0 deletions webrtc-jni/src/main/cpp/src/JNI_AudioTrack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "api/AudioTrackSink.h"
#include "JavaNullPointerException.h"
#include "JavaUtils.h"
#include "NativeHandle.h"

#include "api/media_stream_interface.h"

Expand Down
1 change: 1 addition & 0 deletions webrtc-jni/src/main/cpp/src/JNI_AudioTrackSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include "JNI_AudioTrackSource.h"
#include "JavaUtils.h"
#include "NativeHandle.h"

#include "api/media_stream_interface.h"

Expand Down
1 change: 1 addition & 0 deletions webrtc-jni/src/main/cpp/src/JNI_CustomAudioSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include "JNI_CustomAudioSource.h"
#include "JavaUtils.h"
#include "NativeHandle.h"

#include "media/audio/CustomAudioSource.h"

Expand Down
Loading
Loading