Software Development Kits, Libraries and Components
Sunday, September 3, 2006 1:05:14 AM
Throughout our history, operating systems provided their APIs in C. C was a universal language for developers using any language. For instance, Perl people have written Perl modules consuming C libraries, C++ people did the same, so did Delphi guys. C is taught in universites (I'm not sure whether they still do this) and even a Delphi programmer (should?) knows C, just a little.
Companies have made up their own, internal libraries and managed these as assets. Some companies made products and provided their own APIs to third parties so that they can extend application's abilities or reuse the application in their own package.
Most of C++ libraries are just wrappers on some C functions. Because this seems like the best way, first of all. A native, pure C++ library can be very difficult to maintain and reused by third parties. There are lots of reasons, but let's start with some simple ones.
- C++, in most cases, requires classes' declarations to be shipped (header files, containing class declarations).
- C++ is more type-safe than C, so vendor should provide more information about the class, try to stick on type safety or use casts, while C API can just return a "handle" (void*, may be?) and "implicitly cast it".
- Compiled C++ object code is less portable/compatible across different compilers (name decoration, or, mangling? vtable layout? multiple inheritance layout? exceptions?)
- One little change in a class changes its layout, so vendor should send updates to customers and all customers should rebuild their projects, re-deploy to their customers.
- Everybody has his/her own coding style and may not like others' style.
- There are bunch of well-known techniques that are then named "patterns" (somebody once came and said "so you used Factory pattern?", I didn't know what was factory pattern, I thought that approach fits best to my design).
- Not all compilers support whole ISO/IEC 14882 standard, so vendor should consider obeying standard and compromise some cool features, like exceptions and templates (which could also allow customers to see majority of the library code).
C APIs, on the other hand, have some good sides:
- Return a handle, request a handle - no need any implementation detail.
typedef void* HDATA; // some data "handle" HDATA CreateData(const wchar_t** pwszArguments, int iArgs); int DecodeData(HDATA h, wchar_t* pbuf, int cchbuf); // use handle returned by above function
- No name decoration. So "void foo()" will be "foo", unlike its decorated version C++, like "??QV?foo?WZW". Therefore, it's far more portable, as there is no standard way for name decoration in ISO/IEC 14882.
- No "exception" or run-time polymorphism concern, therefore more portable.
- All languages can access simply, as C is more solid from standard point of view.
- Everybody can read, or at least can use, C code.
- In most cases, installing dynamic library solves problem, without rebuilding everything. It's not always the case, however.
- Few terminologies, no "containment", "aggregation", "inheritance", "some really cool pattern", making C easier to write and reuse.
- Many more.
This is not a C++ bashing, but these are the facts. Object-oriented programming is not the purpose, it's a technique that we use to solve some sort of big problems. We should not mix the purpose with the tools we use to achieve the goal; they are distinct subjects.
A vendor can provide a C API, but use C++ code in its implementation. This is a pretty common technique, which is still being used today.
class DataClass
{
public:
void foo();
int bar();
DataClass* baz();
};
HDATA CreateData(const wchar_t** pwszArguments, int iArgs)
{
HDATA h;
DataClass* pcls;
// do something with pcls
h = (HDATA)pcls;
return h; // return pointer to a DataClass object.
}
int DecodeData(HDATA h, wchar_t* pbuf, int cchbuf)
{
DataClass* pcls = (DataClass*)h;
return pcls->DecodeData(pbuf, cchbuf);
}
This technique prevents any confusions, both in development and consuming a library.
Polymorphism
This is an important subject, as in some cases customer may want to alter some particular behavior of a particular type and API supports polymorphism to allow customer's code to override some behavior (like in bad old MFC).
In C++, we have two types of polymorphism, as you may already know; the run-time polymorphism, through virtual functions and compile time polymorphism through templates.
template<typename T>
class SomeClass
{
public:
bool some_method(T& t)
{
// do something
}
void foo(T& t)
{
// ...
}
};
typedef std::vector<int> MyVectorType;
MyVectorType g_vec;
void foo(int i)
{
SomeClass<MyVectorType::value_type> sc;
some_method(g_vec[0]);
}
The std::vector itself was polymorphic enough, but having one additional delicate sample is harmless.
The well-known inheritance based run-time polymorphism can be slightly complicated, too - never mind how complex will objects look like in memory.
class Base
{
virtual void foo() = 0;
virtual void bar();
public:
void f();
};
class Derived
{
// implicit virtual
void foo()
{
}
public:
void f();
};
void test(Base* pbase)
{
pbase->foo();
pbase->bar();
pbase->f();
}
void polycall()
{
Derived d;
test(&d);
}
Hmmm...
Microsoft has asked me "what is a virtual function" during an interview. I have given the explanation in the ISO/IEC 14882 - simply, call goes to final overrider - but in my opinion, the correct answer is "depends on how 'deep' you know". For above example:
- Base::foo is virtual, so calls can propagate to final overrider, which is Derived::foo, in our case. Note that Derived::foo is an implicit virtual function. If a base class declares a function with exact name and argument list and exact or a covariant return type, the function is virtual in derived classes, regardless of virtual qualifier in declaration of function in derived class.
- Base::bar is virtual, so calls can propagate to final overrider, too, but Derived does not override Base::bar, therefore call does not propagate Derived.
- Base::f is not virtual, so calls can't propagate and all Derived::f does is "hiding" base's method. In this case, test function will call Base::f and not Derived::f.
This technique was quite heavily used in famous MFC library. The former, template based (static?) polymorphism is heavily used in WTL, which generates tighter and smaller code.
Addictions, habits and mentality
There are people who like their wrist watches to be on their left wrist and there are who like otherwise. Some like red cars, some black, some white. Much like different tastes in other areas in our lives, we have habits and "tastes" in software world, too! Not everybody likes templates, not everybody likes multiple-inheritance, etc. When it comes to C++, number of tastes and ways increase mind bogglingly. This is where customer starts disliking, hating and swearing vendor or author, architect of API, and it goes until Bill Gates. Never mind that almost every vendor has its own string class that is not compatible with other string classes.
// Vendor code
class DataAcquirer
{
public:
// can conflict with customer's code?
DataAcquirer& operator<<(int i);
// can customer refuse/modify this data?
virtual void OnDataArrived(const Data& rdata);
// yet another string class... perhaps, this is "more efficient"...
VendorString GetDataSource();
// did they override operator new, too??
};
// too complicated for average C++ programmer.
// may be nightmare for new graduates!
template<typename T, template<class, class> class TSomeContainer, typename TTraits = SomeTraits>
class SomeTemplate : public SomeBase<T, TSomeTraits>
{
TSomeContainer<T, LibraryAllocator<T> > m_arr;
public:
typedef typename SomeBase<T, TSomeTraits>::some_type some_type;
};
As a conclusion, I happen to believe making a C++ SDK and happy customers seems rather difficult.
COMponents
I've always said that COM (or an equivalent way) was a perfect way to develop and use SDK. Vendor provides a (bunch of) DLL(s), customer either plugs component or writes some abstraction over it or just use it as-is, by modifying a few properties. They are cute, nice, solid black boxes. Talking about COM;
- Customer usually does not need a header file, compiler can import type library from DLL.
- There is no non-standard C++ name mangling.
- Uses reference counting, less pain to manage memory (we have smart pointers!).
- It forces "pimpl idiom" on those who want to override particular behavior of component.
- It has a set of standards (well, COM is a set of standards)
- C users can (re)use, too (through vtable?)
Stupidity can be better
I believe that a library better be stupid. With stupid, I mean, it should provide details and not an abstraction salad or trying to do everything for me. Because this may leave small room for enhancements. For example, if you take standard C library, it does not try to do everything, but provides some functions, which you can use together and end up with an abstraction. Library provides low(er)-level processing, customer (or user) uses them together and ends up with an abstraction layer. Alternatively, vendor writes a complicated function that has various parameters and flags that behaves different for each case. This is, however, rather less flexible than lower-level functionality.
Clear documentation and one consistent one baseline
Documentation is what customer needs. Customer has to know which conditions are error, which are successful, who is allocating buffers, whether call is thread-safe or does it acquire any lock, etc. In Win32 API, for example, there are some functions where caller provides a NULL pointer to learn how much memory should be allocated to store information. So customer makes two calls; first, to get buffer size (check return value, too, it may be a completely different error!) and second, to do the actual work. This is not a consistent design, in my humble opinion.
Win32 API is very well documented. There are some COM interfaces, some components that are almost not documented, no sample, no nothing. I remember I have written wrappers around various DLLs to intercept calls and try to see how those particular objects work.
Conclusion
In my humble opinion, C is still the best programming language to write APIs.
, but really cool stuff.







