The Nightstar Zoo

Nightstar IRC Network -
It is currently Sun Apr 21, 2019 9:05 pm

All times are UTC - 6 hours [ DST ]

Post new topic Reply to topic  [ 2 posts ] 
Author Message
 Post subject: OO vs. C-style APIs
PostPosted: Fri Sep 09, 2005 1:17 pm 
Nightstar Graveyard Daemon
User avatar

Joined: Mon Jun 03, 2002 8:30 pm
Posts: 1071
Location: Wouldn't you rather observe my Velocity?
Just to mix things up a little, I thought I'd actually post a message in here. :-)

Python and Ruby are languages that feature powerful OO semantics. Both languages are slower than compiled languages, however, and both of them work around this by making it easy to import and use binary libraries. Every time I think about writing a C library for Python or Ruby, however, I am struck by the fact that the libraries are inherently procedural rather than object-oriented. Today I have been giving thought to the problem of making a C-style API act a little more OO.

So far about the best thing I can think of is a "handle" approach: a manual ctor/dtor/object* semantic. Imagine we have a class Foo that has integer count and string m_string members, and a method "bar" that returns a m_string duplicated m_count times.

CFoo* create_foo(const char *new_string="", int new_count=1);
void destroy_foo(CFoo *pThis);
void set_count(CFoo *pThis, int new_count);
int get_count(CFoo *pThis);
void set_string(CFoo *pThis, const char *new_string);
const char * get_string(CFoo *pThis);
const char* bar(void);

There are SO many problems with this approach; chief among them being that the library is leaving resource cleanup to the user. In fact, the library is completely detached from the garbage collector and other convenient subsystems that the scripting language might employ. We could write a gc() method and have the garbage collector call it, I suppose. Another common approach is to have init() and shutdown() methods; init could setup any precached objects or calculate lookup tables and the like, while shutdown() could garbage collect any outstanding objects.

The next option is to build a class in the scripting language that utilizes the library, and interact exclusively with that class.

class Foo
  def initialize(string="", count=1)
    @foo_ptr = create_foo(string, count)
  def string
    return get_string(@foo_ptr).to_s
  def string=
    return set_string(@foo_ptr).to_s
  # ...etc.

This method seems to be the only way to attach the library to the OO properties of the language but to my mind it seems like so much extra overhead. Especially if the library wasn't written to support OO in the first place. It's almost as if the library is designed to exist in a separate process, needing to clean up its own resources and handle everything on its own, but without any kind of clues from the compiler or OS about when to go about it.


 Post subject:
PostPosted: Fri Sep 09, 2005 2:57 pm 
There are a number of libraries that I've found which do use the handle approach, along with explicit ctor/dtor type functions. The Win32 API uses it extensively, such as anything that exposes kernel objects. Where do you think the HANDLE and H??? syntax came from.

object = HFILE
ctor = CreateFile()
dtor = CloseHandle()
All of the methods take your HFILE handle as the 'this' pointer.

The biggest implementation (bug-causing) problem with this is (as you correctly pointed out) that resource cleanup is still explicitly the responsibility of the caller. This is the chief advantage to managed code, as well.

The biggest semantic problem with the handle method is that you've just taken a beautiful object model, and proceduralized it. You've taken the idea of "Ask object X to go Y itself" and forced it into "Do Y to object X", even if only by moving the 'this' pointer from the outside of the call to the inside of the parameter list.

How does one get around this in a C-style API? Well, one solution would be a kind of wrapper, where the actual C-style name called is decorated to include class details, but outside the wrapper, the semantic details (class and this) are separated. Your 'this' parameter is put on the parameter list only during wrapping, and is removed during unwrapping. If you wanted to use function overloads, you could even go so far as to decorate the wrapped C-style function name with the parameter types in addition to the class semantics and hidden 'this' parameter.

If you look carefully, you are now looking at a C++ style API.

There's nothing stopping you from rolling your own name-mangling scheme (quite simple if you don't allow overloading) and using what C++ does right for object passing. I suspect the only reason that we still use C-style APIs for binary code compatibility (and not C++) is the difference in name-mangling schemes. If name-mangling had been standardized between compilers way back when C++ was first being thought up, then we would have binary API compatibility with all of the shiny OOness you ask for. Where I work, all code is built with the same compiler (and thus the same name-mangling scheme), and internal interops are often and easily done with C++ calls. 'Extern "C"' is just a tag you drop onto stuff for third-party compatibility.

By the way, C++ isn't the only OO binary format in the game. COM is another OO binary compatibility format that is quite well knwon, and wraps the C-style APIs. The interesting problem isn't the format, though, but the implementation of the pieces using it. There's still one hurdle left, and it's the much more difficult one when you're working with Ruby or Python. BOTH ends have to agree on the binary format. Then BOTH ends have to apply the wrapper and sweep all of the details under the carpet, and pretend like what you're really doing is passing fully-formed objects around and doing everything a happy OO environment should.

The python interop doesn't do this, and I suspect (from your complaints) that the ruby one doesn't either.

I find no small amount of irony in the fact that a language (I'm talking about python from experience) that prides itself on being completely OO, still forces you into the procedural mindset in order to get binary interoperability. This is understandable given the environment in which it was developed. A huge percentage of the native unmanaged code in the *nix environment is written completely procedurally in C.

There seems to be a culture that equates "unmanaged" with "procedural", throughout the *nix development landscape that just doesn't exist in Windows. This is unfortunate because the two ideas (managed/unmanaged and procedural/OO) are really orthogonal concepts. As you learned from your rubyquiz experience, it's quite possible to write procedural managed code. I also contend that it's quite possible to write OO unmanaged code; I do it every day in C++.

You want to write unmanaged OO libraries to interop with Ruby or Python? I suspect the first step is to get Ruby and Python to stop forcing you into the procedural mindset just to get a library to work. And nothing you can do in your role as a lowly library developer can fix that.


Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 2 posts ] 

All times are UTC - 6 hours [ DST ]

Who is online

Users browsing this forum: No registered users and 1 guest

You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group