Original link (with code)
Translate original
Introduced
DLL (dynamic-link library) allows a series of function functions to be encapsulated in a separate module and then provided to an external consumer using an explicit list of C functions. In the 80 's, when DLLs was unveiled, only C language was a practical development tool for the vast majority of developers. Therefore, winddows DLLs is naturally exposed to functions in the form of C functions and data. In fact, DLL implementations can be implemented in any language, and DLL interfaces need to be back to the lowest common language--c language in order to be applied to other languages and environments.
Using the C interface does not mean that developers should discard object-oriented development methods. Although this approach may be considered a tedious implementation, the C interface can actually be used for real object-oriented programming. C + + as the second largest programming language can also be used for DLL encapsulation, but unlike the C language, when the caller and the callee between the binary interface is well defined, but can not be recognized by any application Binary interface (ABI) in C + +. This means that binary code generated by the C + + compiler cannot be recognized by other C + + compilers. Also, binary code generated by the same C + + compiler may not be compatible due to a different version of the compiler. All of this makes exporting C + + classes from DLLs a huge adventure.
The purpose of this article is to demonstrate several methods of exporting C + + classes from DLL modules. The example given is to export the class XYZ object, XYZ has only one method FOO,XYZ implementation in the DLL so that it can be called by multiple users. The caller can have the function of azimuth XYZ in different ways:
- Pure C
- General C + + classes
- Abstract C + + class interface
The instance source code consists of two parts: Xyzlibrary + xyzexecutable. Xyzlibrary is a dynamic link library project that exports related code through the following macro definitions:
#if defined(XYALIBRARY_EXPORT) //inside dll#define XYZAPI __declspec(dllexport)#else //outside dll#define XYZAPI __declspec(dllimport)#endif //XYZLIBRARY_EXPORT
The symbol is XYZLIBRARY_EXPORT
defined only in the Xyzlibrary project, so the XYZAPI macro is extended to __declspec(dllexport)
build the DLL, and for the consumer project, the XYZAPI macro is extended to __declspec(dllimport)
.
Pure C method handle
One way to use the C language for object-oriented programming is to use obscure pointers, such as handles. The user invokes a function that internally creates an object that returns a handle to the object. The user can then invoke different functions and use the handle as a parameter to implement different operations on the object. Win32 has a good example of that HWND
. Here, the XYZ object is exported through the C interface:
typedef tagXYZHANDLE{} *XYZHANGLE; XYZAPI XYZHANDLE APIENTRY GetXyz(VOID);//Factory function that creates instances of the Xyz object.//Calls Xyz.Foo method.XYZAPI VOID APIENTRY XyzRelease(XYZHANDLE handle);//Release Xyz instance and frees resources.//APIENTRY is defined as __stdcall in WinDef.h header.
The C code for the corresponding user call DLL is as follows:
#include "XyzLibrary.h".../*Create Xyz instance.*/XYZHANDLE hXyz = GetXyz();if(hXyz) { /*call Xyz.foo method.*/ 42); /*Destroy Xyz instance and release qcquired resources.*/ /*Be defensive.*/}
In this way, the DLL must explicitly provide object creation and deletion functions.
Invoke contract
It is important for all the exported functions to remember that they call the contract. All can run when the consumer's invocation contract matches the call contract of the DLL. However, once the client changes its invocation contract, it will produce an imperceptible error that occurs until run time. The Xyzlibrary project uses a APIENTRY
macro that is defined as __stdcall in the "WinDef.h" header file.
Abnormal
C + + Exceptions are not allowed within the scope of the DLL. C languages do not recognize C + + exceptions for a period of time, and they cannot be handled correctly. If the method of an object needs to report an error, you can set a return code.
Advantages
- The generated DLLs can be used by the widest range of developers. Almost every modern programming language supports the interoperability of pure C functions.
- DLL's C run-time library and its clients are independent of each other. Because the acquisition and release of a resource occurs entirely inside the DLL module, the client is not affected by the DLL's C run-time library selection.
Disadvantages
- The responsibility for invoking the correct method on an object instance falls entirely on the user of the DLL, for example:
/* void* GetSomeOtherObject(void) is declared elsewhere. */XYZHANDLE h = GetSomeOtherObject();/* Oops! Error: Calling Xyz.Foo on wrong object intance. */42);
The compiler could not get the error that was called above.
An explicit function call is required to implement object instance creation and deletion. Deleting instances is especially annoying. The client function must call the Xyzrelease function on exit, and if the call is forgotten, a memory leak occurs because the compiler cannot track the life cycle of the object instance. Those programming languages that support a destructor or a garbage collector may solve this problem by creating a wrapper on the C interface.
If an object method returns or accepts another object as a parameter, the DLL creator needs to provide an appropriate C interface for the object as well. The alternative is to use the underlying data types built into the C language as return values and method parameters (such as: int,double,char*, etc.).
C + + General methods
Almost every C + + compiler now supports the export of C + + classes from DLLs under the Windows platform. Exporting C + + classes is similar to C functions, and all you need to do is to use it before the class name you want to export, __declspec(dllexport/dllimport)
or before the declared method, if you only need a member method of a class to export. For example:
// The whole CXyz class is exported with all its methods and members.//class XYZAPI CXyz{public: int Foo(int n);};// Only CXyz::Foo method is exported.//class CXyz{public: int Foo(int n);};
There is no need to explicitly explicitly export the calling contract for the class and its methods. By default, the C + + compiler uses the calling protocol for class methods __thiscall
. However, because different compilers may use different naming adornment methods, the export class may be used only for the same version of the same compiler. The following is the name decoration instance of the MS Visual C + + compiler:
Notice the difference between the modified name and the original C + + name, the following is the name of the same DLL module that was decoded by the dependency tool:
Only the MS Visual C + + compiler can use this DLL. Both the DLL and the consumer code must be compiled with the same version of the MS Visualc++ compiler to ensure a matching relationship between the name of the caller and the callee.
Note: There is no difference between using a DLL that exports C + + classes and using a static library. All rules that apply to static libraries compiled with C + + code are perfectly suited for exporting DLLs of C + + classes.
What you see is not what you receive
Carefully the reader may have found that the Dependency
tool shows an additional export class method, assignment operator. What we see is the working state of C + + memory, according to the C + + standard, there are four special member functions for each class:
- Default constructor method
- Copy constructor
- Destructors
- Assignment operations
If the class author does not declare or provide implementations of these functions, then the C + + compiler automatically declares and internally defaults the implementation. In this example, the first three functions use the default, but the assignment operation is exported from the DLL.
Note: Use the __declspec(dllexport)
export class to tell the compiler to export everything related to that class. It includes the member data for the class, all class member methods (either explicitly declared or implicitly generated by the compiler), the base class of the class, and all of their members. For example
class Base{ ...};class Data{ ...};// MS Visual C++ compiler emits C4275 warning about not exported base class.class__declspec(dllexport) Derived : public Base{ ...private: Data m_data; // C4251 warning about not exported data member.};
In this example, the compiler warns you that there are no exported base classes and no exported class member data. Therefore, in order to successfully export C + + classes, developers need to export all related base classes and all classes used for data member definitions. This is a very big drawback, which is why it is very difficult to export a derived class of STL template classes or classes that use STL template class member variables. For example, an instance of std::map<> might require a large number of internal class exports.
Exceptional security
The exported C + + class may run out of exception without problems. Remember: Using DLLs to export C + + code is equivalent to using static lib.
Advantages
- The exported C + + classes can be used just like other classes;
- Exception throws can be obtained normally;
- DLL module small changes will not affect other modules, do not need to regenerate other modules;
- It is good for the modularity of the project.
Disadvantages
- DLLs are treated as static libraries;
- The same version of the C run-time library must be used;
- Export all related things of this class, including: base class, define class used by data members, etc.;
- The same exception handling protocol must be observed;
Mature C + + Method: Abstract class Interface
一个C++抽象接口(比如一个拥有纯虚函数和没有数据成员的C++类)设法做到两全其美:对对象而言独立于编译器的规则的接口以及方便的面向对象方式的函数调用。为达到这些要求去做的就是提供一个接口声明的头文件,同时实现一个能返回最新创建的对象实例的工厂函数。只有这个工厂函数需要使用__declspec(dllexport/dllimport)指定。接口不需要任何额外的指定。
// The abstract interface for Xyz object.// No extra specifiers required.struct IXyz{ virtualint Foo(int0; virtualvoid0;};// Factory function that creates instances of the Xyz object.extern"C" XYZAPI IXyz* APIENTRY GetXyz();
In the code snippet above, the factory function getxyz is declared as extern xyzapi. This is done to prevent the name of the function from being decorated (as mentioned above, exporting a C + + class whose member function name is modified after it is exported). In this way, the function is externally represented as a regular C function and is easily recognized by C-compatible compilers. When an abstract interface is used, the client code looks the same as the following:
#include "XyzLibrary.h"...IXyz* pXyz = ::GetXyz();if(pXyz){ pXyz->Foo(42); pXyz->Release(); pXyz = NULL;}
C + + does not have to provide a specific tag for the interface to use in other programming languages (such as C # or Java). However, this does not mean that C + + cannot declare and implement interfaces. The general way to design a C + + interface is to declare an abstract class that does not have any data members. In this way, derived classes can inherit this interface and implement this interface, but this implementation is not visible to the client. interface does not need to know and focus on how the interface is implemented. It just needs to know what functions are available and what they do.
Internal mechanisms
The idea behind this approach is very simple. A class with very few members consisting of pure virtual functions is simply a virtual function table-an array of function pointers. Within the scope of the DLL, this array of function pointers is filled by its author with anything he deems necessary. The use of this pointer array outside the DLL is the actual implementation of the calling interface. The following is a diagram of the usage instructions for the IXYZ interface.
The diagram above shows that the IXYZ interface is used by both the DLL and EXE modules. Inside a DLL module, the Xyzimpl class derives from the IXYZ interface and implements its method. The function call in EXE references the DLL module through the actual implementation of a virtual table.
Why this DLL can be run with other compilers
The short explanation is that COM technology works with other compilers. Now for a detailed explanation, in fact, using a virtual base class with a few members between the modules as an interface is exactly what COM exposes to a COM interface. As we know the concept of a virtual table, we can add tags to COM standards very precisely. This is not a coincidence. The C + + language, as a mainstream development language that spans at least 10 years, has been widely used in COM programming. Because C + + inherently supports object-oriented features. It is no surprise that Microsoft has made it a heavyweight tool for industrial COM development. As the owner of COM technology, Microsoft has ensured that the binary standards of COM and the C + + object models that they have implemented in the Visual C + + compiler can be matched at minimal cost.
No wonder other compiler vendors have implemented the layout of virtual tables in the same way as Microsoft. After all, everyone wants to support COM technology and is compatible with Microsoft's existing solutions. Assuming that a C + + compiler does not support COM effectively, it is destined to be discarded by the Windows Marketplace. That's why today, exporting a C + + class from a DLL through an abstract interface can be reliably run together with a decent compiler on the Windows platform.
Use a smart pointer
To ensure the correct resource release, a virtual interface provides an additional function to clear the object instance. Calling this function manually is annoying and prone to errors. We all know that this error is a common mistake in the C world, where developers have to remember to release the resources obtained by explicit function calls. This is why typical C + + code uses the habit of using RAII (resource acquisition, initialization) with the help of smart pointers. The Xyzexecutable project provides an example of using the Autocloseptr template. The autocloseptr template is a simple smart pointer that calls a class to eliminate the subjective method of an instance instead of the delete operator. Here's a snippet of code that demonstrates the use of a smart pointer with a IXYZ interface:
#include "XyzLibrary.h"#include "AutoClosePtr.h"...typedefvoid, &IXyz::Release> IXyzPtr;IXyzPtr ptrXyz(::GetXyz());if(ptrXyz){ ptrXyz->Foo(42);}// No need to call ptrXyz->Release(). Smart pointer// will call this method automatically in the destructor.
However, using a smart pointer will ensure that the XYZ object is properly resourced. The function exits prematurely because of an error or an internal exception, but the C + + language guarantees that the destructor for all local objects can be called before the function exits.
Exceptional security
As with COM interfaces no longer allow resource leaks due to the occurrence of any internal exceptions, the abstract class interface does not allow any internal exceptions to break through the DLL scope. The function call will use a return code to explicitly indicate the error that occurred. For a particular compiler, the handling of C + + exceptions is specific and cannot be shared. So, in this sense, an abstract class interface behaves like a C function.
Advantages
- An exported C + + class can be used in any C + + compiler through an abstract interface
- A DLL's C run-in library and DLL clients are independent of each other. Because the initialization and deallocation of the resources are completely internal to the DLL, the client is not affected by the DLL's C-run-Library selection.
- True module separation can be achieved with a high degree of perfection. The resulting module can be redesigned and rebuilt without being affected by the remaining modules of the project.
- If required, a DLL module can be easily converted into a real COM module.
Disadvantages
- An explicit function call requires the creation of a new object instance and deletes it. Although a smart pointer eliminates the developer's call
- An abstract interface function cannot return or accept a rule for a C + + object as a parameter. It can only be used as a parameter type with built-in types (such as int, double, char*, and so on) or another virtual interface. It has the same limitations as COM interfaces.
How STL template classes are made
Containers for the C + + Standard Template library (such as vector,list or map) and other templates are not designed as DLL modules (in abstract class interface mode). The C + + standard for DLLs is not, because DLLs are a platform-specific technology. The C + + standard does not need to appear on other platforms that do not use the C + + language. Currently, Microsoft's Visual C + + compiler is able to export and import STL class instances that developers explicitly identify with the __declspec (dllexport/dllimport) keyword. The compiler emits a few annoying warnings, but it can still run. However, you must remember that exporting STL template instances and exporting the rules C + + classes are exactly the same, with the same limitations. So, in that respect, STL is nothing special.
Summarize
This article discusses several different ways to export a C + + object from a DLL module. A detailed discussion of the merits and demerits of each method has also been given. Here are a few of the conclusions drawn:
- Exporting an object with a full C function is compatible with the broadest development environment and development language. However, in order to use modern programming paradigms a DLL user is required to use outdated C techniques to make a layer of additional encapsulation of the C interface.
- There is no difference between exporting a rule's C + + class and providing a separate static library in C + + code. The usage is very simple and familiar, however the DLL and the client have a very tight connection. The DLL and its clients must use the same version and the same type of compiler.
- Defining an abstract class with no data members and implementing it inside the DLL is the best way to export C + + objects. So far, this approach has provided a clear, well-defined object-oriented interface to the DLL and its clients. Such a DLL can be used by any modern C + + compiler on the Windows platform. The use of interfaces together with smart pointers is almost as convenient as the usage of an exported C + + class.
"Ubuntu" Windows link library--How to export C + + classes from DLLs