C++ interfaces and library decoupling

Started by
4 comments, last by SiCrane 8 years, 3 months ago

As stated in the topic, I have a use case where this would be nice, but is it actually possible? If you declare an interface in libraryOne and define an identical interface signature in libraryTwo, can the two libraries use each other through an application without the two libraries having knowledge of each other?

Advertisement

With proper design firewalls that can work. In particular you need to make sure that the libraries are compiled in a compatible manner (ex: both libraries agree what the size of the various types are) and one binary doesn't mess with the implementation details of the other (ex: no deleting something new'd by the other - note that this means that if your interface contains inline functions, in particular template code which is implicitly inlined, then you may have problems). One example of this at work is COM, which allows binaries built by different compilers (and programming languages) to work together on Windows.

One example of this at work is COM, which allows binaries built by different compilers (and programming languages) to work together on Windows.

To be fair, each library still has to know about the other in COM because they have to have access to the interface definition. If two libraries want to talk to each other they have to at least agree on something. For static libraries, you can take advantage of C++ mechanisms. For template classes with mix-ins the "duck typing" templates do let two classes or functions talk to each other using only names. You can also use interfaces and virtual dispatch, but either each library needs to agree on a common interface (that both will compile against) or you'll need to write an adapter class that can convert one library's interface to the other library. For dynamic libraries your options are far more limited, as C++ cannot be used. (Insert arguments about how C++ can be used under special conditions A, B, or C) In most cases you'll be dealing with C APIs and things like function pointers.

To be fair, each library still has to know about the other in COM because they have to have access to the interface definition.

Not necessarily. It's a lot easier if the static types of all the interfaces necessary are available, but a library that implements the IDispatch interface can be dynamically interrogated and manipulated. Both libraries need to know about COM automation, but they don't necessarily need to know about each other. For example, this is how scripting support for new components was implemented in Office (up to around 2003, no idea if later versions of Office changed their inner workings). If you added a new charting widget to your worksheet, Excel wouldn't know about new interfaces it added over the base OLE stuff, but you could still manipulate them through VBA because the charting widget would implement the IDispatch interfaces VBA would use to make the actual calls.

For dynamic libraries your options are far more limited, as C++ cannot be used. (Insert arguments about how C++ can be used under special conditions A, B, or C) In most cases you'll be dealing with C APIs and things like function pointers.

You may want to be a lot more clear about what you mean by "C++ cannot be used" as it's pretty obvious that people do write dynamic libraries in C++. Again, many COM DLLs are written in C++. Right now your statement only makes sense if you already know what it means.

To be fair, each library still has to know about the other in COM because they have to have access to the interface definition.

Not necessarily. It's a lot easier if the static types of all the interfaces necessary are available, but a library that implements the IDispatch interface can be dynamically interrogated and manipulated. Both libraries need to know about COM automation, but they don't necessarily need to know about each other. For example, this is how scripting support for new components was implemented in Office (up to around 2003, no idea if later versions of Office changed their inner workings). If you added a new charting widget to your worksheet, Excel wouldn't know about new interfaces it added over the base OLE stuff, but you could still manipulate them through VBA because the charting widget would implement the IDispatch interfaces VBA would use to make the actual calls.

In which case the "common parent" is IDispatch. A pre-defined interface that both compile against. My point still stands. Someone needs to define a common API somewhere.

For dynamic libraries your options are far more limited, as C++ cannot be used. (Insert arguments about how C++ can be used under special conditions A, B, or C) In most cases you'll be dealing with C APIs and things like function pointers.

You may want to be a lot more clear about what you mean by "C++ cannot be used" as it's pretty obvious that people do write dynamic libraries in C++. Again, many COM DLLs are written in C++. Right now your statement only makes sense if you already know what it means.

Fair enough. C++ does not define a standard binary interface. In other words, C++ compilers are able to produce any binary structures they want to, as long as the produced binary code follows the C++ standard, and any calls to external functions and entry points into your code follow the OS's calling convention. (C++ compilers don't have to follow any calling convention internally - the obvious example being inlined functions which aren't called at all) For example, the C++ standard does not specify how virtual calls are performed. Most compilers will do this using a table of pointers to functions known as a "v-table", but nothing in the standard dictates this. Even if two compilers both use a v-table, no standard says where to put said v-table in the object's memory, or the order functions appear in the v-table. Because of this, code compiled with C++ compiler A cannot talk to code compiled with C++ compiler B. And compiler vendor A could change their mind between versions, changing things again, so you can't even use code compiled with different compilers from the same vendor. In comparison, C defines a standard binary layout for its structs, arrays, and the like, as well as things like not mangling function names. Because of this, C code compiled with any compiler, can talk to C code compiled by any other compiler. Therefore, at the API level, where your library exposes functions and data types to be used by external programs, you cannot use any C++ features. Functions must be defined under C-type calling convention, cannot have any overrides, and cannot be members of structs. Classes cannot be exposed. Exceptions must be caught and turned into error codes or something similar. And so on. Nothing prevents you from using C++ (or C#, or Python, or whatever else you want) to actually implement the internals of your library. You are simply restricted to using C as the public API. Otherwise other code cannot utilize your library. Of course, if you are making a static library, you can use whatever you want, because the end user will be compiling the library themselves, so everything will match up properly.

Because of this, code compiled with C++ compiler A cannot talk to code compiled with C++ compiler B. And compiler vendor A could change their mind between versions, changing things again, so you can't even use code compiled with different compilers from the same vendor.

This is an over-generalization. Again, COM interfaces are quite frequently implemented as C++ classes and work quite well across compilers on Windows. You can get a pointer to a COM object implemented as a MSVC C++ class and directly invoke a member function on it from BCB compiled C++ program and vice versa. You can't freely inter-operate between compilers (in fact, interface classes whose arguments are only primitive and pointer types is about the only C++ feature that works reliably), but that's different from saying it can't work at all.

In comparison, C defines a standard binary layout for its structs, arrays, and the like, as well as things like not mangling function names. Because of this, C code compiled with any compiler, can talk to C code compiled by any other compiler.

Actually, it doesn't. In particular, primitives don't have set sizes (only minimum sizes) and structs may have vendor specific padding between members. It's also entirely possible for one compiler for a given platform to have primitive types with sizes unavailable to some other compilers on the same platform (historically common with 64-bit and 128-bit types). There's also the fun realm of incompatible vendor specific calling conventions (ex: Microsoft C, Turbo C and Watcom C all defined a __fastcall convention but each used a different number of registers). For that matter, some vendors just never bothered implementing broad parts of C99, so any features from that aren't guaranteed to be portable. Usually the only ones that cause problems for library interfaces are flexible array members and the _Bool type.

Functions must be defined under C-type calling convention, cannot have any overrides, and cannot be members of structs. Classes cannot be exposed.

Actually, on x86 Windows most cross-compiler code seems to use the Pascal calling convention (arguments passed left to right with callee cleanup) rather than C calling conventions (arguments passed right to left with caller cleanup). In particular, almost every Win32 API function uses the Pascal calling convention. And again, in COM, C++ interface classes are frequently exposed.

This topic is closed to new replies.

Advertisement