• C语言开发函数库时利用不透明指针对外隐藏结构体细节


    1 模块化设计要求库接口隐藏实现细节

    作为一个函数库来说,尽力降低和其调用方的耦合。是最主要的设计标准。

    C语言,作为经典“程序=数据结构+算法”的践行者,在实现函数库的时候,必定存在大量的结构体定义,接口函数须要对这些结构体进行操作。同一时候,程序设计的模块化要求库接口尽量少的暴露事实上现细节,接口參数尽量使用基本数据类型。尽量避免在形參中暴露库内结构体的定义。

    2 隐藏结构体的两种方法

    以笔者粗浅的认识,有两种最经常使用的方法。可以实现库内结构体定义的隐藏:接口函数形參使用结构体指针,接口函数形參使用句柄。

    2.1 通过结构体指针引用结构体

    为了说明方便。先给出使用VC++写的一段样例代码。

    库接口头文件 MySDK.h

    #pragma once
    
    #ifdef MYSDK_EXPORT
    #define MYSDK_API __declspec(dllexport)
    #else
    #define MYSDK_API __declspec(dllimport)
    #endif
    
    typedef struct _Window Window; /*预先声明*/
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
        MYSDK_API Window* CreateWindow();
        MYSDK_API void ShowWindow(Window* pWin);
    
    #ifdef __cplusplus
    }
    #endif
    
    

    库实现文件MySDK.c

    #define MYSDK_EXPORT
    
    #include "MySDK.h"
    #include <stdlib.h>
    
    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;
    }

    库使用者代码

    #include <stdio.h>
    #include "../myDll/MySDK.h"
    #pragma comment(lib, "../Debug/myDll.lib")
    
    int main(int argc, char** argv)
    {
        Window* pWin = CreateWindow();
        ShowWindow(pWin);
    
        return 0;
    }
    

    当中MySDK.h和MySDK.c是库的实现; main.cpp是调用方程序实现。两方使用了相同的接口头文件MySDK.h。

    可是从使用者角度,main.cpp里面仅仅知道库中有名为Window的一种结构体类型,可是却不能知道此机构体的实现细节(定义)。因为C/C++编译器是延迟依赖型编译器,仅仅要源代码中没有涉及到Window结构体内存布局的代码。编译时不须要知道Window的完整定义。可是仍然可以检查类型名称的正确性,比方假设client代码例如以下则会被编译器检查出问题:

        int* p = 0;
        ShowWindow(p);

    编译器尽管不知道ShowWindow(pWin)中pWin指向的结构体的实现细节,可是仍然可以确保实參类型为Window*。这也方便了调用方检查错误。

    2.2 通过“句柄”(handle)来引用结构体

    最先接触句柄的概念。是在Win32API中。可以断定Windows系统的内部定义了大量的结构体,如线程对象、进程对象、窗体对象、….。可是编程接口Win32API中却非常少提供这些结构体的定义。调用者通过一个称为“句柄”的值来间接引用要使用的结构体对象。

    Win32API 中的句柄
    比如,例如以下Win32API

       HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
          CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
    
      ShowWindow(hWnd, nCmdShow);
      UpdateWindow(hWnd);

    窗体类型在Windows中一定是一个非常复杂的结构体,为了隐藏事实上现细节,微软採取了窗体句柄的概念来间接引用窗体结构体对象。为了实现这样的相应关系。库内部必须维护句柄和结构体对象的相应关系。

    Linux API中的句柄
    句柄的概念也广泛的应用在Linux平台API中。如

     int open(const char *pathname, int flags);
     ssize_t read(int fd, void *buf, size_t count);

    在Linux内部。文件一定是通过一个复杂的结构体来表示,可是在API中使用了一个简单整数对其进行引用,避免了向调用者暴露文件结构体的细节。

    OpenGL API中的句柄
    句柄相同应用到了OpenGL库中。

    void WINAPI glGenTextures(
       GLsizei n,
       GLuint  *textures
    );
    void WINAPI glBindTexture(
       GLenum target,
       GLuint texture
    );

    纹理在OpenGL库内部也是一个复杂的结构体,相同使用句柄的概念对外隐藏了实现细节。

    3 句柄和指针的比較

    3.1 句柄的优势与不足

    句柄看起来真的不错。那么局部究竟是怎样映射到相应的结构体的呢?一个最easy想到的答案就是:直接把结构体对象的内存地址作为句柄。

    然而实际上,大多数的库实现都不是这么做的。之所以不直接把内存地址作为句柄的值,我个人觉得有例如以下几个原因:

    • 从源代码保护角度,内存地址更easy被Hack。知道了结构体的内存地址,就行读取这块内存的内容,从而为推測结构体细节提供了方便。

    • 从程序稳定性角度,对于库内部维护的对象,调用者仅仅应该通过接口函数来訪问。假设调用者得到了对象的内存地址,那么就有可能有意或无意的进行直接改动,从而影响库的稳定执行。

    • 从可移植性角度,指针类型在32位和64位系统中具有不同的长度,这样就须要为定义两个名称反复的接口函数,造成各种不便。而比如OpenGL,使用int型作为句柄类型。则可以一个接口函数跨越多个平台。

    • 从简化接口头文件角度,使用指针至少须要事先声明结构体类型,如 struct Window; 而使用基本数据类型作为句柄,无需这样做。

    句柄存在的不足有:

    • 编译器无法识别详细的结构体类型
      因为句柄的数据类型实际上是基本数据类型,所以编译器仅仅能进行常规的检查,不能识别详细的结构体类型。

       SECURITY_ATTRIBUTES sa;
       HANDLE h = CreateMutex(&sa, TRUE, L"Mutex");
       ReadFile(h, NULL, 0, 0, 0);

    上述代码编译器并不会报错。因为相互排斥体对象和文件对象都是使用相同的句柄类型。

    • 效率可能稍差
      毕竟存在一个 依据句柄值-查找内存指针的过程,可能会稍稍影响执行效率。

    3.2 指针的优势与不足

    事实上指针和句柄是相对的,句柄的不足就是指针的优势,句柄的优势也是指针的不足。

    4 怎样选择

    对于大型跨平台库的设计,採用句柄;对于专用小型库。採用指针。

    就我眼下的项目而言,是一个小型的C库project,库的目标群体也相对单一,所以本着简单够用的原则,我选择了使用指针的方式对外隐藏库内结构体的实现细节。

  • 相关阅读:
    神鬼传奇客户端解包图片(ui\common)
    电子书下载:Pro ASP.NET MVC 3 Framework 3rd Edition
    DDS工具
    Delphi2010中TResourceStream流使用
    电子书下载:Professional C# 4 and .NET 4
    电子书下载:Professional NoSQL
    电子书下载:C# Database Basics
    FancyCache要怎样设置才最大发挥硬盘的性能?
    完美时空Cube Engine 3D引擎
    DELPHI获取CPU信息
  • 原文地址:https://www.cnblogs.com/gavanwanggw/p/7105254.html
Copyright © 2020-2023  润新知