Java and Postgres95
You must store the value returned by makeCString in a variable. If you pass the return value directly to a function, the garbage collector may free it at any time. The same is not true of makeJavaString.) Lines 30-34 in Listing 2 show the use of makeCString and we use makeJavaString first at line 51. Lines 41-42 in Listing 2 show our call into the libpq library. It is called exactly as normal, and the resulting pointer is stored in the variable tmpConn. You may notice that we don't do any error-checking here: we do that in the Java code for our constructor, where it is easier to throw exceptions.
As I mentioned above, PGConnection needs to keep the PGconn pointer around, so that it can use it in later calls—all later calls, in fact. In order to do this, we will store the 32 bit pointer in a data member with Java type int after casting it to a C long to avoid warnings (see Table 1 for a list of type conversions).
To access this member, we must use Java's “handles”. Handles are used to access data in a Java object. When you want to access a data member, you simply use unhand(ptr)->member rather than ptr->member (where ptr is the handle). We do this on line 42 of PGConnectionNative.c (Listing 2) to save the pointer returned by setDB in a Java int (note: if you forget the unhand() macro, you will get a warning about incompatible pointer types).
This function has covered almost all you need to know to call C functions from Java (calling Java methods from C is possible, but the interface is clumsy at best at this point, and where possible, I'd avoid it). Most of the rest of the methods (host, options, port, etc.) simply convert the data and make the C call. We'll just take a look at one of these, PGConnection.db().
The only significant portion of the C function PGConnection_db() is its first line (Listing 2, line 46). It needs a PGconn to pass to PQdb(), so it must get it out of the PGConnection member, PGconnRep. It uses cw[unhand() to get the pointer as a long, then casts that to a (PGconn *). Since this line is so messy (and is starting to look like lisp!) I created a macro, thisPGconn, to clean up the code a little. It is used in the remainder of the file, and its definition is at the top of the file (don't put it in PGConnection.h, since that is machine-generated).
All of the native methods in the Java class PGResult follow the same basic structure, and there is no reason to go over them.
There are some places where Java and C just don't get along. The rest of this section will touch on the few I found, and how I avoided them.
The exec() method (see, I told you I'd get to it) needs to return a PGResult object. This is in keeping with libpq's structure, and the OO nature of Java. However, returning an object from a native method can get pretty hairy. The “official” way to do it is to call the function:
HObject *execute_java_constructor(ExecEnv *, char *classname, ClassClass *cb, char *signature, ...);
and return the HObject * it returns. Personally, I find this interface extremely clumsy, and have managed to avoid it. However, for completeness, the actual call in our case would be:
return execute_java_constructor(EE(), "classPGResult", 0, "(I)LclassPGResult;", (long)tmpResult);
I found it far easier to create a buffer between the call to exec() and the call to PQexec() that could call the constructor from Java. This is where the nativeExec() method comes from. exec() simply passes the string to nativeExec(), which returns an int (the PGresult pointer that PQexec() returned). Then it calls PGResult's constructor with that int.
The extra layer will also come in handy when we add the asynchronous notification system.
PQgetline() expects the user to continually call it while it fills in a static buffer. This is simply not needed in Java. A much nicer interface is to just have getline() return a String. However, building the String (appending each return value from PQgetline()) required calling Java methods from C—which, as we saw in Hoop #1, is very messy. By using a StringBuffer (a String that can grow) and doing the work in the Java code, it's much easier to understand, if a little slower.
The flip side of this is that the return value is now the String, so there must be another way to tell if an error has occurred or an EOF has been reached. One solution (I'm looking for a better one), and the one we use, is to set a data member flag. If the flag has been set to EOF, we simply return a Java null String. So once again, an extra layer saves us from a lot of truly gross code!
Editorial Advisory Panel
Thank you to our 2014 Editorial Advisors!
- Jeff Parent
- Brad Baillio
- Nick Baronian
- Steve Case
- Chadalavada Kalyana
- Caleb Cullen
- Keir Davis
- Michael Eager
- Nick Faltys
- Dennis Frey
- Philip Jacob
- Jay Kruizenga
- Steve Marquez
- Dave McAllister
- Craig Oda
- Mike Roberts
- Chris Stark
- Patrick Swartz
- David Lynch
- Alicia Gibb
- Thomas Quinlan
- Carson McDonald
- Kristen Shoemaker
- Charnell Luchich
- James Walker
- Victor Gregorio
- Hari Boukis
- Brian Conner
- David Lane