Question about SSWriter::flush

Continuing to work through things for JNI binding. I’ve now come to SSWriter in panda/src/downloader (socketStream.*).

Both ostream (as exported in dtool/src/parser-inc) and SSWriter declare a ‘flush()’ method. Though they define different return types. Void for ostream, bool for SSWriter. C++ is very sketchy on such co-variants. Generally overloading on return type is not meaningful and definitely is not supported in most ABI function name mangling.

Java is even more picky about this, which comes up as an issue for SocketStream and OSocketStream.

Left to my own devices, I’d be tempted to rename the ‘flush’ in SSWriter to something like ‘flush_write’. Then hunt down usage sites.

However, I am open to other ideas.


-Cary

Maybe we could have flush() just match the return type of the base class? All it returns is !is_closed() which can be checked separately anyway.

Out of curiosity, how are you handling multiple inheritance in general? I recall that Java doesn’t really allow inheriting from more than one class. I’m separately working on bindings for the nim language (which also doesn’t have multiple inheritance) so I’m curious if you’re doing things differently than I am.

I was planning to do a detailed write-up for what I ended up doing once it actually functioned end-to-end (as I need to do that for work internally :slight_smile: ). But I’d be happy to give an ad-hoc version right now.

Java, as you note, does not support multiple inheritance in concrete (instantiatable) types. It does, however, allow it in a form for interfaces. I say in a form, because unlike C++, Java will collapse multiple instances of a multiply inherited interface. But this model is close enough, I believe (and has shown to be workable for all of the things I’ve looked at in PANDA thus far).

So what I’ve done is express the type taxonomy, and nesting, in Java interface types.

Next a form is needed that can be instantiated. Similar to how things were done in the early days of interrogate integration I declare concrete types that implement points in the interface taxonomy that are ‘shadow’ types for the C++ and contain only a private (technically, protected, but that’s an implementation detail) holder for a ‘pointer’ to the real C++ entity. Java interface types cannot declare the JNI ‘native’ methods needed to bridge to the C++, so those live in the ‘proxy’ types. The ‘proxy’ types implement the abstract methods declared in the interface type(s), and vector to the appropriate JNI ‘native’ methods.

The interface types also declare static methods which model the constructors, these vector to static methods on the ‘proxy’ types that perform and wrap a proper construction. The proxy types also interact with end-of-live control (during Java GC) and arrange to call the correct end-of-life (delete, or unref_delete) in the C++.

There is one more implementation detail that is (unfortunately) important for the Java space. It is simplest for the C++ side of the JNI if a given method can know what type the object-like parameters are passed in as. Particular in the case of base types. From the Java side, a given ‘proxy’ type contains a pointer for that type. Java knows that the proxy type implements the base interfaces, but not how to adjust this pointer, or how to re-wrap it in an appropriate ‘proxy’. Further, since we are only doing this cast for a function call, we would rather Java’s ref-counting not get overly involved in the lifetime of the pointer in that parameter instance.

So to ‘cheat’ this, I introduced an intermediate instantiatable form between the interface and the ‘proxy’. I call this a ‘param’ form. Ultimately all of the non-construct, non-destruct methods are implemented there. The ‘proxy’ form extends this with construct/destruct. When converting to a form for passing as a parameter, the proxy presents a re-wrapped param form. This fixes all of the otherwise painful lifetime stuff. Though this shim could be thinner for PANDA types that also ref-counted, I wanted a single working model to get things working. Optimization can happen after.

Neither the ‘proxy’ or ‘param’ forms should ever be referenced in user code. Only the interfaces. All the rest is implementation detail.

1 Like

Sure! I’m good with making that change.

FWIW, this only required touching 3 call sites in socketStream.I, 1 in recorder/socketStreamRecorder.I, and 1 in distributed/cConnectionRepository.cxx.

So not a great deal of dependency on this other interface. Yay!


-Cary

Oh, this is clever. I look forward to seeing what the completed bindings will look like.

I apologize for the lack of update. I got pulled onto some other high priority projects at work for a few weeks. I’m picking it back up now.

To keep your curiosity happy, I’ll emit and post here the generated Java for one of the smaller libs so you can get a feel for how it is coming together.

2 Likes