Occasionally, when extending the functionality of an existing class, you also need to store some additional data along with the object. Unlike a language like JavaScript, Java does not allow users to treat an object as an extensible key/value store: fields must be specified when the class is compiled.

In Objective-C, developers can use objc_setAssociatedObject and objc_getAssociatedObject. Unfortunately, Java does not come with a similar primitive; developers thereby often use a WeakHashMap (where the map itself is the "key", and the map's key is the object) to simulate this functionality.

As this is a key use case of Substrate, an API is provided to efficiently store these values on objects. Developers are able to provide an arbitrary key to later find their value, in addition to a function that cleans up whatever resources may be needed by the value after the object is garbage collected.

The key used with this function (and for later calls to MSJavaGetObjectKey) is entirely opaque: to obtain one, developers must call MSJavaCreateObjectKey. This is conceptually similar to the pthread_key_create API used for thread-local storage (which has a similar API to this one, pthread_setspecific).

void MSJavaSetObjectKey(JNIEnv *jni, jobject object, MSJavaObjectKey key, void *value,
    void (*clean)(void *data, JNIEnv *jni, void *value), void *data);
Parameter Description
jni JNI environment used to interpret object argument.
object JNI reference to object storing the keyed value.
key Opaque pointer to use as a lookup key for value.
value Opaque pointer to store on this object for this key.
clean Address of a function to call when this value is either replaced or the object is garbage collected. This will be passed data, the JNI environment of the thread on which the value became obsolete, and the now-obsolete value.
data Opaque argument passed through to clean function.
JNIEnv *jni = /* ... */;
jobject object = /* ... */;
MSJavaObjectKey key = MSJavaCreateObjectKey();

std::allocator<void> *pool = /* ... */;
void *extra = pool->allocate(1024);

void clean(void *data, JNIEnv *jni, void *value) {
    std::allocator<void> *pool =
        reinterpret_cast<std::allocator<void> *>(data);
    pool->deallocate(value, 1024);

MSJavaSetObjectKey(jni, object, &key, extra, &clean, pool);