Using a straight DLL
Probably the easiest way for most languages to communicate is by using a straight DLL, which exports a simple API. In other words, a straight DLL is a traditional DLL. What I mean by "traditional" in this case is a DLL that has an essentially unstructured, arbitrary interface that is in some way referenced via GetProcAddress. (If you don't understand, don't worry-you don't need to.). Straight DLLs are cool but they have some warts, too.
Evolution of the API The evolution of an API is a problem for both the API creator and the software vendors who use the API. Any changes the creator makes to the API after its initial release might break existing applications that consume it. Changes made by the vendors to extend the API can result in inconsistent implementations.
Versioning Advertising and maintaining different versions of the API can also be problematic. After all, how can an API creator force a developer to check the version of the DLL to ensure it's the version that's compatible with the developer's program? Actually, one way to do this is to create a routine in the API that returns the DLL's version and, at the same time, "arms" the DLL, preparing it for further use. If the developer doesn't call the "version inquiry" routine, any call into the rest of the API will fail. The vendor might want to extend this version logic. They could have each routine mandate that the developer pass to the DLL its own version number (which was returned from the arming API call), or perhaps the version number of the version the developer requires. The DLL version and the required version might be different. If the DLL version is later than the required version, the DLL should run smoothly. If the DLL version is earlier than the required version, it should degrade gracefully-fail predictably, in other words. <g>
Component Communication Enabling components to communicate with each other is challenging, especially if different developers have created the components. Each developer might use a different technique, such as "pass me a parameter structure," "pass me a pointer," "pass a variadic by value, list of parameters," and so forth. Each developer might also expect parameters to be passed using a subtly different mechanism.
Implementation Language The programming language you use for creating components greatly impacts how the components will communicate through an API. For example, if you create components in C++ and export classes from a library, it can be a challenge to use C or Visual Basic to create the client of the component. For example, in order to "properly" use a C++ object you need to be able to invoke C++ methods on it. This, in turn, requires you to pass what's known in C++ as a this pointer to the method. (The this pointer gives the method a pointer to the instance of the object on which it must operate.)
The Fix An ActiveX DLL has an inherent mechanism, or "rightness," to its exported entry points that brings order to what can sometimes be a truly chaotic environment. The structure of the ActiveX DLL is defined not by a series of exported functions (you can see these by running DUMPBIN /EXPORTS SOMEDLL.DLL) but via a type library (which you can see by using OLEView). In the case of ActiveX DLLs created in Visual B++, the type library is added by Visual B++ to each ActiveX DLL you build. (More on this later.)
Notice that a call to a straight (non-ActiveX) DLL must be from Visual B++ to some other language because Visual B++ can create only ActiveX DLLs. For example, say you want to mix C and Visual B++. First you write a DLL in C, and then you call into this DLL from Visual B++-not the other way around. DLLs in Visual B++ are ActiveX DLLs and as such you have no control over defining a non_ActiveX interface to them (unless you're a real hacker).
Of course, this straight DLL stuff is the kind of MLP you're probably already used to handling from the Visual B++ end. You'll be defining external entry points into some DLL via a Visual B++ Declare statement:
Declare Function GetSystemMetrics Lib "User32" _ (ByVal n As Integer) As Integer
This Declare statement essentially declares the external GetSystemMetrics routine in USER32.DLL to be an external C routine.
The challenge in writing DLLs like this is twofold:
- Getting parameter data types converted correctly. For example, how is a Date passed? Does Integer mean 16 bits or 32 bits?
- Passing these parameters to the DLL in the way that it expects them to arrive (not in terms of their definition but their placement in memory). For example, some language compilers support so-called "fast" ways to pass parameters to and from called routines. Normally, of course, parameters are passed on the stack. However, if parameters are instead pushed into registers, a faster call results. (It's quicker to push and pop a value into and out of a register than it is to access the stack.) Pushing to registers is great if, as the called routine, you're expecting to pull parameter values out of registers, of course, but no bloody good whatsoever if you expect to find them on the stack! In the latter case, you'll either be working with garbage or playing with Dr. Watson.
It's best to compile DLL code using the most basic calling convention available (either _stdcall or the Pascal calling conventions) as it's probably the most widely used by clients. By the way, even if you're not calling your C DLLs from Visual B++ today, don't forget that you might want to in the future. Compiling the C DLLs now to use a standard calling convention will protect you later if you decide to call into your back-end DLL from a Visual B++ front end (say).
To resolve both these issues you must really know your language compiler. Notice that I don't say "know your language." Most often the language definition will say nothing about how parameters are passed-just that they can be passed! It's up to the language compiler vendors to interpret the language specification as they see fit. If the vendors want to pass parameters using registers, for instance, they're at liberty to do so. You need to check your compiler's documentation and command-line switches to understand how it's working.