How to hide the Implementation Details of struct in a library through external interfaces when using C language to develop a function library
1. modular design requires the library interface to hide Implementation Details
As a function library, it is the most basic design standard to minimize coupling with its callers. C language, as the practitioner of the classic "program = Data Structure + algorithm", there must be a large number of struct definitions when implementing the function library, and interface functions need to operate these structs. At the same time, the modularization of the program design requires that the library interface expose its implementation details as little as possible. The interface parameters should use basic data types as much as possible, and avoid exposing the struct definitions in the library in the form parameters.
2. Two methods to hide struct
Based on my rough understanding, there are two most common methods to hide the struct definition in the database: the struct pointer is used for interface function parameters, and the handler is used for interface function parameters.
2.1 reference a struct using a struct pointer
For ease of instruction, we first provide an example code written using VC ++.
Library interface header file MySDK. h
# Pragma once # ifdef MYSDK_EXPORT # define MYSDK_API _ declspec (dllexport) # else # define MYSDK_API _ declspec (dllimport) # endiftypedef struct _ Window; /* pre-declare */# ifdef _ cplusplusextern "C" {# endif MYSDK_API Window * CreateWindow (); MYSDK_API void ShowWindow (Window * pWin ); # ifdef _ cplusplus} # endif
Library implementation file MySDK. c
#define MYSDK_EXPORT#include "MySDK.h"#include
struct _Window{ int width; int height; int x; int y; unsigned char color[3]; int isShow;};MYSDK_API Window* CreateWindow(){ Window* p = malloc(sizeof(Window)); if (p) { p->width = 400; p->height = 300; p->x = 0; p->y = 0; p->color[0] = 255; p->color[1] = 255; p->color[2] = 255; p->isShow = 0; } return p;}MYSDK_API void ShowWindow(Window* pWin){ pWin->isShow = 1;}
Library User code
#include
#include "../myDll/MySDK.h"#pragma comment(lib, "../Debug/myDll.lib")int main(int argc, char** argv){ Window* pWin = CreateWindow(); ShowWindow(pWin); return 0;}
Here, MySDK. h and MySDK. c are the implementation of the library; main. cpp is the implementation of the caller program. Both parties use the same interface header file MySDK. h.
But from the user's point of view, main. cpp only knows that there is a struct type named Window in the library, but does not know the implementation details (definitions) of this mechanism ). Since the C/C ++ compiler is a latency-dependent compiler, as long as the source code does not involve the memory layout of the Window struct, you do not need to know the complete definition of the Window during compilation, however, you can still check the correctness of the type name. For example, if the client code is as follows, the compiler will check the problem:
int* p = 0; ShowWindow(p);
Although the compiler does not know the implementation details of the struct pointed to by pWin in ShowWindow (pWin), it can still ensure that the real parameter type is Window *, which facilitates the caller to check for errors.
2.2 use the "handle" (handle) to reference the struct
The concept of First Touching handle is in Win32API. It can be concluded that a large number of structs, such as thread objects, process objects, window objects, and so on, are defined in the Windows system ,..... However, the programming interface Win32API rarely provides the definitions of these struct. The caller indirectly references the struct object to be used through a value called "handle.
Handle in Win32API
For example, the following Win32API
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd);
The window type is a very complex struct in Windows. to hide its implementation details, Microsoft uses the window handle concept to indirectly reference the struct object. To implement this correspondence, the database must maintain the correspondence between the handle and the struct object.
Handle in Linux API
The concept of handle is also widely used in Linux platform APIs. For example
int open(const char *pathname, int flags); ssize_t read(int fd, void *buf, size_t count);
In Linux, a file must be represented by a complex struct, but a simple integer is used in the API to reference it, avoiding exposing the details of the file struct to the caller.
Handles in OpenGL APIs
The handle is also applied to the OpenGL library. For example
void WINAPI glGenTextures( GLsizei n, GLuint *textures);void WINAPI glBindTexture( GLenum target, GLuint texture);
Texture is also a complex struct In the OpenGL library, and the Implementation Details are hidden externally using the handle concept.
3. Comparison of handle and pointer 3.1 Advantages and Disadvantages of handle
The handle looks really good, so how do I map the local to the corresponding struct? The easiest way to think of it is to directly use the memory address of the struct object as the handle. However, most library implementations do not. I personally think there are several reasons for not directly using the memory address as the handle value:
From the perspective of source code protection, the memory address is more easily Hack. By knowing the memory address of the struct, you can read the content of the memory, which provides convenience for guessing the details of the struct.
From the perspective of program stability, the caller should only access objects maintained inside the database through interface functions. If the caller obtains the memory address of the object, therefore, it is possible to directly modify the database intentionally or unintentionally, thus affecting the stable operation of the database.
From the perspective of portability, the pointer type has different lengths in 32-bit and 64-bit systems. Therefore, we need to define two interface functions with duplicate names, resulting in various inconveniences. For example, if OpenGL uses the int type as the handle type, an interface function can span multiple platforms.
From the perspective of simplified interface header files, you must declare at least the struct type in advance, such as struct Window, to use the basic data type as the handle.
Handle limitations include:
The compiler cannot identify the specific struct type.
Because the data type of the handle is actually the basic data type, the compiler can only perform regular checks and cannot identify the specific struct type. For example
SECURITY_ATTRIBUTES sa; HANDLE h = CreateMutex(&sa, TRUE, L"Mutex"); ReadFile(h, NULL, 0, 0, 0);
The above code compiler does not report an error because the mutex object and the file object both use the same handle type.
Efficiency may be slightly lower
After all, there is a process of finding memory pointers Based on the handle value, which may slightly affect the running efficiency. 3.2 Advantages and Disadvantages of pointers
In fact, the pointer is relative to the handle. The disadvantage of the handle is the advantage of the pointer, and the advantage of the handle is also the deficiency of the pointer.
4. How to choose
Handle is used for the design of large cross-platform databases, and pointer is used for specialized small databases.
As for my current project, it is a small C-Library Project, and the target group of the library is also relatively single, so in line with the principle of simple enough, I chose to use pointers to hide the Implementation Details of struct in the library.