Casting a JObject to a JList

Viewed 269

To cast a JObject to a JList (or anything else, it doesn't matter, this is just an example), is just doing JList(MyJobject) a good way? I don't receive any error, but I'm not sure if it's the correct way to go.

2 Answers

When casting between different object types, you cannot use a plain type-cast. You must cast the JObject to ILocalObject and call its GetObjectID() method, and then pass that result to the Wrap() method of the destination class type, in this case TJList, eg:

For example:

var
  MyJobject: JObject;
  MyJList: JList;

MyJobject := ...;
MyJList := TJList.Wrap((MyJobject as ILocalObject).GetObjectID);

Or simpler (which is just a wrapper for the above):

var
  MyJobject: JObject;
  MyJList: JList;

MyJobject := ...;
MyJList := TJList.Wrap(MyJobject);

See What the purpose of doing (MyJobject as ILocalObject).GetObjectID

There are two possible problems with using plain typecast.

First, if particular Java class has not been initialized with previous Delphi code, its VMT table will not be initialized. Next, references returned by JNI calls are local references and they are only valid for the duration of particular native method.

JNI tips

Every argument passed to a native method, and almost every object returned by a JNI function is a "local reference". This means that it's valid for the duration of the current native method in the current thread. Even if the object itself continues to live on after the native method returns, the reference is not valid.

This applies to all sub-classes of jobject, including jclass, jstring, and jarray. (The runtime will warn you about most reference mis-uses when extended JNI checks are enabled.)

The only way to get non-local references is via the functions NewGlobalRef and NewWeakGlobalRef.

If you want to hold on to a reference for a longer period, you must use a "global" reference. The NewGlobalRef function takes the local reference as an argument and returns a global one. The global reference is guaranteed to be valid until you call DeleteGlobalRef.

Wrap solves both issues. It initializes Java class VMT if not already initialized and converts local JObject reference to global one.

Plain typecast can only work if class is initialized by some previous code and the local reference is not used outside native (Delphi) method that retrieved said reference.

That is why plain typecast used in JStringToString(JString(PurchaseDataList.get(I))) can work properly. JObject reference returned by get is immediately converted to Delphi string and JString VMT is already initialized at that point, being commonly used Java class.

When in doubt, using Wrap is safer, but it also takes more time than plain typecast.

Related