We've had
blog posts on how integrating PeopleCode with various other languages before, but one of the things that I punted on previously was showing how to do really deep integration with C. Or more accurately, .DLLs/shared objects.
Strangely enough, I've actually had occasion recently to dig back into some of this for some work that we've been doing to integrate with another product. I'll spare you the details of that (for now - since the work isn't done yet :-), but I thought I'd share how the technical bits work. Most folks can feel safe to skip this entry - we're digging into the weeds here. But sometimes you have to really get your hands dirty to accomplish something (or so my wife says to me), so that's what we're going to do here.
In a nutshell, the problem that we're trying to solve is that when we want to access various functionality that is exposed in .DLL files (they're called shared objects on Linux/Unix, but I'm just going to refer to .DLL files here - the concepts translate well though) there are numerous occasions when the function that we're calling wants to deal with a structure (struct). The easiest way to think of a structure is that you have a single "thing" that has a bunch of fields in it.
Unfortunately, the i
ntegration that PeopleCode has for .DLLs does not support structures. What to do? Roll your own, of course!
To provide a concrete example that everyone can try on their own, I'm using the Windows API call for getting the local system time. The MSDN documentation (a great resource) shows that the
GetLocalTime function returns a pointer to a
SYSTEMTIME structure.
PeopleCode will support getting back the return pointer to the struct (it just ends up as a Number in PeopleCode), but we need to do some extra work to dig out the specific data values that we want. The trick is realizing that a struct is really all sequential in memory. So, since we know where the beginning of the struct is (from the return value) and we know the definition of the struct (from the doc), we can calculate how far away from the beginning of the struct the data that we want is. We can then use the Windows RtlMoveMemory function to grab the data from the appropriate place (we can also use this same function to write data into a struct, but here we're just reading data).
Let's take a look at some sample code.
We start off by declaring the actual Windows API functions that we're going to use to get our work done. This is all standard stuff and documented in PeopleBooks. The only wrinkle here is that we declare the RtlMoveMemory function with the name CopyPtrToInt so that we can leverage the existing PeopleCode infrastructure for passing data back and forth. In the other code that I'm working on, there are multiple "Declare Function" statements that all reference RtlMoveMemory with different names (CopyPtrToLong, CopyLongToPtr, etc).
Next we have a little function that just maps a word that describes a struct datatype (e.g. "long", "integer", "word", etc) with how many bytes that datatype will occupy. We need this so that if we want to, say, grab the 5th field from a struct, we know how many bytes in it starts.
We follow that with a function that exists purely for sanity checking our structure definitions in PeopleCode. When you call into .DLLs, there is practically no error handling, so if you pass it bad data, then you'll quickly get to meet
Doctor Watson.
Next up is a function that will tell us how large our strucuture is. We need to know this for when we actually allocate memory for the structure. Speaking of which, allocation and deallocation of memory are what the next two function handle. Very important to always deallocate any memory that you allocate (which is why we have all of these wrapper functions), but you can only deallocate it once.
OK, we're in the home stretch in now. Only two more infrastructure functions define and then we can actually do something. The first one is used to know where inside a struct a "field" is. We need this so that we can access data in the struct by fieldname, while under the covers it gets mapped to a specific memory offset in the structure.
The last infrastructure function we need is something that will return an actual PeopleCode value to us from the structure. Whew! The demo code just has something to return an Integer. If the structure had other field types in it (Longs, Strings, etc. ), then you'd want to mimic this function for those data types.
Now we're finally down to the meat of it. Or the main course of it if you're a vegetarian. We're ready to define what our SYSTEMTIME structure is, allocate the memory for it, call the GetLocalTime API function, and display our results.
Our function creates two array definitions. One is the list of field names that are in the struct. These can actually be anything that you want to refer to the fields as, but it's always good to keep them somewhat in sync with whatever "C level" documentation that you have regarding the struct. The second array has the same number of values as the first array, but it is a list of all of the data types for each field in the first array. We combine these into a single array for passing around to all of our infrastructure functions, and then we're good to go.
When we run this program, we get output similar to "2006-6-20 14:36:16". A lot of work just to get the time, eh? True enough that there are better ways of getting the time, but there are lots of other uses for this type of strategy that it makes a nice example.
Ok, now it's time for
Ketan or
Chili Joe to catch any of my typos :-)
Labels: PeopleCode