Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

From C++ to Shining C


Conversations: From C++ to Shining C

Wendy must have overhead me muttering, because she gophered up and asked: "What do you mean, 'I just don't see?' "

I leaned back from my workstation, letting my hands drop wearily onto my armrests. "Oh, sorry. I was talking about the programming language C. I just don't C. I'm used to objects and generic programming and everything now, and I hate dropping out of that to just structured programming."

She smiled. "Oh, you must be working on the customer interface layer."

"Right," I sighed, "and it's my first time in there. I don't see -- or C -- why we don't just bite the bullet and expose all the functionality in C++. It would be a lot easier. Everything underneath is written in C++ anyway. Then we could just expose the classes and not have to wrap everything. I'm not even sure how to wrap my objects well in a non-object language, anyway."

This time I saw a shadow approaching out of the corner of my eye, and for once wasn't completely startled when the Guru glided up silently and said: "Easier? For you, perhaps. And what you do not know, you should desire to learn."

We good-morninged her. Then I said: "Say, you'd be a good one to tell us. Why do we write our interface APIs in C? That's so 20th-century, isn't it?"

The Guru looked at me searchingly, as though she were weighing what was in my mind and heart. After a few seconds, it started to feel uncomfortable, and I wondered whether I shouldn't have kept my mouth shut. Too late for that now, though. I braced myself for the consequences.

Finally, she said quietly: "And what, my apprentice, is wrong with C?"

The answer seemed so obvious that I barely dared express it. "Well, uh, it's not object-oriented."

She said cryptically: "A wise prophet once said, and I paraphrase, 'Objects is as objects does.' " [1]

Then she kept looking at me, so I blurted: "And it's not type-safe. It's old. It's... uh, well, it's..."

"C is important and will be so for some time. It is certainly a portable lingua franca like no other," she corrected. "A true master programmer has no room for language bigotry. All languages have advantages and drawbacks, and a master programmer knows many and can learn any. What is important is to choose the right language for the task at hand. In this case, tell me: What are the advantages of C?"

"Uh, well..." I looked at Wendy, who suddenly seemed to remember something urgent she had to do and disappeared back into her cubicle. No help there. But the Guru had said lingua franca, so I took that as a hint. "I suppose it's available on a lot of platforms."

"Good," the Guru nodded. "Indeed, on more platforms than probably any other language. And...?"

"And... uh, er, um..." I just couldn't think of anything else.

"It is a lingua franca, my child," she reminded me. "That applies between different platforms, but it applies also between different languages on even the same platform. When was the last time you spoke with one of our customers?" she added, sharply changing tack.

"Actually, I've been really busy on our code. We have a lot to do in this release, and I haven't had time to talk to customers."

She arched an eyebrow. "Oh, really?"

"Well, yes, but this release is all about delivering great new value to the customer, so my work is still all customer-centric," I defended.

She just looked at me.

"Well... isn't it?" I added weakly.

She sighed. "Apprentice... no, let's skip that," she said, looking around and not seeing anyone in earshot, besides Wendy who didn't need the act. "Look here, how will you know what the customer needs unless you know the customer? Too many programmers work too many hours on too many features that don't do what customers need. 'This too is vanity and a striving after wind,' I could add. I asked you about customers for a reason: many of our customers who will be using our API do not use C++; many use C, others use Fortran, still others use Basic and Java, and nearly all languages can call into C APIs in one way or another. And even the customers we have who do use C++ don't all use the same compiler, which usually means they aren't link-compatible and can't call directly into our C++ binaries anyway. And even those few customers who do use the same compiler as we do often use a different version of the standard library, which means ODR violations because of incompatible implementations of standard library facilities like string and vector. A C API is still the common denominator that lets all of these users use our software, regardless of the programming language or compiler or standard library they're using. It's no accident that shared libraries and DLLs and such most frequently have plain-C entry points."

I absorbed all of this. "Okay, I get it," I nodded, "and that's a huge advantage of C. Okay. But how can I wrap my C++ objects in C without losing fidelity and functionality..." Then I saw Bob coming back from his morning coffee break, and segued into: "..., oh master?"

She smiled beatifically. "You cannot always, my apprentice," she acknowledged, "but very close you can come. Show me the class you are working with."

"Okay, here's a sample C++ class I was working on to get started." I launched my editor and pulled up the following code:

  // x.h: My sample C++ class
  //
  class X {
  public:
   X();
   ~X();
   int Func1( int flags );
   int Func2( std::string s, std::complex<double> c );

  private:
    int member1;
    double member2;
    std::string member3;
    std::vector< boost::shared_ptr<Y> > member4;
  };

  X operator+( const X&, const X& );
"So here are some of my problems," I continued, ticking them off on my fingers. "One: The C caller can't use new or call a constructor, so how does he create an X object? Two: Ditto for delete and destruction. Three: C has no concept of member functions, so how do I expose Func1? Four: Even if I can expose a member function, I have types in the signature that C knows nothing about, like objects and template specializations. Five: What about the overloaded operator, when C has no idea about operator overloading? Six: I have members of types that C knows nothing about, so I can't even mention them in declaring the class where C can see it. ... I guess those are the main questions," I finished.

"Much thought have you given to this," the Guru commended. "The last question is the easiest: The class X shall remain unchanged, and be never seen by the C programmer. May I?" She indicated the keyboard. I passed it to her, and she pulled up a chair and started two files:

  // x_api.h: C API for X
  //
  extern "C" {
    // do everything through an opaque pointer
    typedef void* X_HANDLE;
  }

  // x_api.c: implementing the C API for X
  //
  #include "x_api.h"
  #include "x.h"

  extern "C" {
    // ...initially empty...
  }
"The C-compatible caller only ever uses dynamically created X_HANDLE objects and knows nothing about their internal structure. Now, pray tell, what is the first thing the caller will need to do to create one?"

"Well, I'd say he'd have to write 'new X;'... but he can't."

"Indeed. So we shall simply have to do it for him." She added to the two files:

  // In x_api.h:
    X_HANDLE XCreate();

  // In x_api.c:
    X_HANDLE XCreate() { return new X; }
"Okay, the conversion to void* is implicit," I commented. "That answers my first question. I think I see what destruction would be, by symmetry, although I'll have to explicitly cast from void* to X*. May I answer my own second question?" She pushed the keyboard to me, and I added:
  // In x_api.h:
    void XFree( X_HANDLE );

  // In x_api.c:
    void XFree( X_HANDLE x ) { delete static_cast<X*>( x ); }
"Is that the right cast?" I asked. "I didn't want to use a C-style cast, and I didn't think I needed a dynamic_cast."

"Indeed," the Guru agreed. "You could use dynamic_cast and check for a successful cast, in order to detect errors where the caller passes in a pointer he did not receive from XCreate, but this suffices well for now. Apprentice, you have the keyboard: What about Func1?"

"I'm not sure," I scratched my head. "That's a member function..."

"As was the destructor," the Guru reminded quietly.

"...and has an implicit this parameter that I'd have to figure out how to -- Oh! But I just did that with the destructor, and I made it explicit! I'll just do that again:"

  // In x_api.h:
    int XFunc1( X_HANDLE, int flags );

  // In x_api.c:
    int XFunc1( X_HANDLE x, int flags )
     { return static_cast<X*>( x )->Func1( flags ); }
"How's that?"

"Well done," she nodded graciously. "Note also that this is still all object-oriented, although not in a language with direct support for objects. [2] Now: What of Func2?"

"That's where I always get into trouble," I admitted. "Func2 takes non-C parameters -- a string and a complex<double>. C doesn't know what those are. I suppose in C the equivalent to a string is a char*, but there's no complex -- well, there is in the C99 standard I think, but we don't have a C99 compiler so that doesn't help us."

"More to the point, our customers are unlikely to be able to all use a C99 interface," the Guru agreed. "So consider: Using a char* is the native C idiom for a string. That is correct, and you can write at least that much. Go ahead, try," she prompted.

I took a breath, and wrote:

  // In x_api.h:
    int XFunc2( X_HANDLE x, const char* s,
                /* ??? complex<double> c ??? */ );

  // In x_api.c:
    int XFunc2( X_HANDLE x, const char* s,
                /* ??? complex<double> c ??? */ ) {
     return static_cast<X*>( x )->Func2( s, c );
    }
"That looks right to me, so far," I said.

"It is."

"But I don't see how to generalize it to deal with the complex parameter." I looked at her.

"Consider this," the Guru said. "You can pass the const char* naturally where the string is expected. But it is not the same type, so what is happening there?"

"Obviously the converting constructor is getting called to turn the const char* into a string. But... oh, I think I see! You also have to take out any non-C parameters and replace them with something that makes sense, usually something that the parameter's constructor can take. I'll also need an explicit conversion, where I could get away with an implicit conversion for the string." I edited the code to read:

  // In x_api.h:
    int XFunc2( X_HANDLE x, const char* s,
                double re, double im );

  // In x_api.c:
    int XFunc2( X_HANDLE x, const char* s,
                double re, double im ) {
     return static_cast<X*>( x )->
              Func2( s, std::complex<double>( re, im ) );
    }
"I think that's it!" I enthused.

"Indeed, it is," the Guru smiled. "It is possible that you have a parameter type that cannot be constructed from C builtin types only, however, and only from one or more other class types. But in that case you should either not expose that part of the interface, or also expose those other class types the same way you are exposing X."

"That answers all of my questions, then, except for the operator. If I overload operators like +, they can't be accessed by the calling C code, can they?"

"Not directly, no," she agreed.

"Then what?"

She looked at me, waiting for me to get it on my own. There must be something we'd already covered that would work here. But what? The only thing we'd done is represent constructors and member functions in a C-callable way...

Then the penny dropped. I'd represented those functions as free functions, with new names. I just had to wrap the operators in uniquely named functions too. I wrote:

  // In x_api.h:
    void XAdd( X_HANDLE dest, const X_HANDLE lhs,
               const X_HANDLE rhs );

  // In x_api.c:
    void XAdd( X_HANDLE dest, const X_HANDLE lhs,
               const X_HANDLE rhs ) {
     *static_cast<X*>( dest ) = *static_cast<const X*>( lhs ) +
                                *static_cast<const X*>( rhs );
    }
"Good," the Guru agreed. I saved the code, and admired the completed result:
  // x_api.h: C API for X
  //
  extern "C" {
    typedef void* X_HANDLE;

    X_HANDLE XCreate();
    void XFree( X_HANDLE );
    int XFunc1( X_HANDLE, int flags );
    int XFunc2( X_HANDLE x, const char* s,
                double re, double im );
    void XAdd( X_HANDLE dest, const X_HANDLE lhs,
               const X_HANDLE rhs );
  }

  // x_api.c: implementing the C API for X
  //
  #include "x_api.h"
  #include "x.h"
  extern "C" {
    X_HANDLE XCreate() { return new X; }

    void XFree( X_HANDLE x ) { delete static_cast<X*>( x ); }

    int XFunc1( X_HANDLE x, int flags )
     { return static_cast<X*>( x )->Func1( flags ); }

    int XFunc2( X_HANDLE x, const char* s,
                double re, double im ) {
     return static_cast<X*>( x )->
              Func2( s, std::complex<double>( re, im ) );
    }

    void XAdd( X_HANDLE dest, const X_HANDLE lhs,
               const X_HANDLE rhs ) {
     *static_cast<X*>( dest ) = *static_cast<const X*>( lhs ) +
                                *static_cast<const X*>( rhs );
    }
  }
I added a quick test mainline:
  int main() {
    X_HANDLE x = XCreate();
    XFunc1( x, 0 );
    XFunc2( x, "xyzzy", 3.14159, 2.71828 );
    XFree( x );
  }
I compiled it, and it worked. I sat back, satisfied.

"Your time has come," the Guru said, "to go code many things..." She stood up, smiling. And as she drifted away, I happily turned to providing object-oriented C wrappers to my C++ types -- and, to my surprise, not really minding it at all.

Wendy must have overhead me muttering, because she gophered up again and asked mischievously: "What do you mean, 'Now I see?' "

"Now I C..." I repeated to myself and coded on, ignoring Wendy's teasing.

References

[1] F. Gump. Robert Zemeckis and the ILM (Paramount, 1994).

[2] H. Sutter. Exceptional C++, Item 32 (Addison-Wesley, 2000).

About the Authors

Herb Sutter (<www.gotw.ca>) is convener of the ISO C++ standards committee, author of the acclaimed books Exceptional C++ and More Exceptional C++, and one of the instructors of The C++ Seminar (<www.gotw.ca/cpp_seminar>). In addition to his independent writing and consulting, he is also C++ community liaison for Microsoft.

Jim Hyslop is a senior software designer with over 10 years programming experience in C and C++. Jim works at Leitch Technology International Inc., where he deals with a variety of applications, ranging from embedded applications to Windows programs. He can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.