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,库的目标群体也相对单一,所以本着简单够用的原则,我选择了使用指针的方式对外隐藏库内结构体的实现细节。

C语言开发函数库时利用不透明指针对外隐藏结构体细节的更多相关文章

  1. 13-C语言字符串函数库

    目录: 一.C语言字符串函数库 二.用命令行输入参数 回到顶部 一.C语言字符串函数库 1 #include <string.h> 2 字符串复制 strcpy(参数1,参数2); 参数1 ...

  2. Go语言【第十一篇】:Go数据结构之:结构体

    Go语言结构体 Go语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型,结构体是由一系列具有相同类型或不同类型数据构成的集合.结构体表示一项记录,比如:保存图书馆的书籍记 ...

  3. C语言结构体指针(指向结构体的指针)详解

    C语言结构体指针详解 一.前言 一个指向结构体的变量的指针表示的是这个结构体变量占内存中的起始位置,同样它也可以指向结构体变量数组. *a).b 等价于 a->b. "."一 ...

  4. c语言学习之基础知识点介绍(十二):结构体的介绍

    一.结构体的介绍 /* 语法: struct 结构体名{ 成员列表; }; 切记切记有分号! 说明:成员列表就是指你要保存哪些类型的数据. 注意:上面的语法只是定义一个新的类型,而这个类型叫做结构体类 ...

  5. c语言指针数组和结构体的指针

    指向数组的指针,先初始化一个数组,使用传统方式遍历 void main() { ] = { ,,,, }; ; i < ; i++) { printf("%d,%x\n", ...

  6. larave5.6 引入自定义函数库时,报错不能重复定义

    方法一:使用function_exists判断 方法二:使用命名空间 namespace test; function test(){ echo 'test/test'; } namespace te ...

  7. linux驱动开发( 五) 字符设备驱动框架的填充file_operations结构体中的操作函数(read write llseek unlocked_ioctl)

    例子就直接使用宋宝华的书上例子. /* * a simple char device driver: globalmem without mutex * * Copyright (C) 2014 Ba ...

  8. 【C语言入门教程】5.6 函数库和文件

    函数库是为代码复用建立的,将同一类型,需要在不同的程序里使用的函数放置在一起,就组成了一个函数库.如 C 语言的标准库,它集合了开发者常用的函数.开发者自行编写的函数也可以组成函数库,通常称之为自定义 ...

  9. 转:在 C# 中使用 P/Invoke 调用 Mupdf 函数库显示 PDF 文档

    在 C# 中使用 P/Invoke 调用 Mupdf 函数库显示 PDF 文档 一直以来,我都想为 PDF 补丁丁添加一个 PDF 渲染引擎.可是,目前并没有可以在 .NET 框架上运行的免费 PDF ...

随机推荐

  1. 此文章介绍vue-cli脚手架config目录下index.js配置文件

    此配置文件是用来定义开发环境和生产环境中所需要的参数 关于注释 当涉及到较复杂的解释我将通过标识的方式(如(1))将解释写到单独的注释模块,请自行查看 上代码 // see http://vuejs- ...

  2. thinkphp session db配置

    这篇文章主要介绍了ThinkPHP实现将SESSION存入MYSQL的方法,需要的朋友可以参考下   本文以实例讲解了ThinkPHP实现将SESSION存入MYSQL的方法,所采用的运行环境是Thi ...

  3. thinkphp的session用法

    Session方法用于Session 设置.获取.删除和管理操作. Session 用于Session 设置.获取.删除和管理操作 用法 session($name, $value='') 参数 na ...

  4. 使用logback实现http请求日志导入mongodb

    spring boot自带logback作为其日志新系统,但是在实际工作中,常常需要对日志进行管理或分析,如果只是单纯的将日志导入文本文件,则在查询时操作过于繁琐,如果将其导入mysql等关系型数据库 ...

  5. android view、viewgroup 事件响应拦截处理机制

    文章中会用到部分网络资源,首先将原作者的链接附上. 但是还是会附上数量较大的关于此部分内容的自己的思考. ----------------------------------------------- ...

  6. BZOJ 1914 计算几何

    思路: 我们可以算不合法的 如果三个点都在同一侧 就不合法.. 用总方案数减掉就可以了 (有神奇的实现方法...) //By SiriusRen #include <cmath> #inc ...

  7. Jmeter - 服务器性能检测

    在对系统做压力测试时,往往需要对服务的性能进行监控,包括CPU,Memory,IO,还有网络情况进行监控. Jemter有个一插件,能很好的支持这些性能监控.原理是服务器启动服务之后,测试机发起请求, ...

  8. AI:IPPR的数学表示-CNN结构进化(Alex、ZF、Inception、Res、InceptionRes)

    前言: 文章:CNN的结构分析-------:  文章:历年ImageNet冠军模型网络结构解析-------: 文章:GoogleLeNet系列解读-------: 文章:DNN结构演进Histor ...

  9. 【sqli-labs】 less24 POST- Second Order Injections *Real treat* -Stored Injections (POST型二阶注入 *真的好玩?* 存储注入)

    简单登陆浏览一遍后,发现是一个登陆注册修改密码的应用 审查一下代码 登陆页面的username,password使用了转义 注册页面的参数也进行了转义处理 但是在修改password的页面,直接从se ...

  10. EF CodeFirst 基础命令

    PM> enable-migrations 已在项目"EasyWeChat.Data"中启用迁移.若要覆盖现有迁移配置,请使用 -Force 参数. PM> add-m ...