C 语言面向接口编程和多态使用
数组指针
数组类型的语法
C 语言中的数组有自己特定的类型,可以通过 typedef
关键字定义数组类型,语法格式为:typedef type(name)[length];
,例如:
typedef int(MyIntArray)[3];
typedef char(MyCharArray)[3];
数组指针类型的语法
- 数组指针类型用于指向一个数组
- 可以直接定义数组指针类型:
typedef type(*name)[length];
数组类型与数组指针类型的使用
1 |
|
程序运行的输出结果如下:
1 | array[0] = 1 |
函数指针
函数类型的语法
C 语言中的函数有自己特定的类型,可以通过 typedef
关键字定义函数类型,语法格式为:typedef type (name)(parameter list);
,例如:
typedef int (f)(int, int);
typedef void (p)(int);
函数指针类型的语法
- 函数指针类型用于指向一个函数
- 函数有三大要素:名称、参数、返回值,函数名是函数体的入口地址
- 可以通过函数类型定义函数指针类型:
FuncType* pointer;
- 也可以直接定义函数指针类型:
typedef type (*pointer)(parameter list);
pointer
:函数指针变量名type
:指向函数的返回值类型parameter list
:指向函数的参数类型列表
函数类型与函数指针类型的使用
1 |
|
程序运行的输出结果如下:
1 | 1 + 3 = 4 |
函数类型与函数指针作为函数参数
函数类型作为函数参数
当函数类型做为函数的参数传递给一个被调用函数,被调用函数就可以通过这个函数类型调用外部的函数,这就形成了回调函数
。C 语言回调函数的本质是,提前做了一个协议的约定(把函数的参数、函数的返回值类型提前约定)。
1 |
|
程序运行的输出结果如下:
1 | result = 9 |
函数指针作为函数参数
当函数指针做为函数的参数传递给一个被调用函数,被调用函数就可以通过这个指针调用外部的函数,这就形成了回调函数
。C 语言回调函数的本质是,提前做了一个协议的约定(把函数的参数、函数的返回值类型提前约定)。
- a) 将 “函数的调用” 和 “函数的实现” 解耦
- b) 可以模拟 C++ 的多态机制(提前布局 VPTR 指针和虚函数表,找虚函数入口地址来实现函数调用)
1 |
|
程序运行的输出结果如下:
1 | result = 7 |
上述的 add
、mult
函数都是写在同一个源文件当中,假如 add
函数是一个库中的函数,此时就只有使用回调了,通过函数指针参数将外部函数地址传入来实现调用。日后如果库里面的 add
函数的代码作了修改,也不必改动函数调用方的代码,就可以正常实现调用,便于程序的维护和升级。
DLL 动态链接库的使用
下面将介绍 C/C++ 如何开发和调用一款 Socket 客户端的 DLL 动态链接库,该 DLL 主要实现了 Socket 客户端的初始化、报文发送、报文接收、资源释放等功能,第三方可以直接调用该 DLL 实现 Socket 通信。值得一提的是,本案例并没有真正完整地实现 Socket 客户端的底层代码,更多的是使用伪代码来模拟 Socket 客户端的通信。
运行环境说明
这里给出的案例代码和操作步骤,只适用于 Windows 系统的 C/C++ 开发,且依赖 Visual Studio 开发工具,不适用于 Linux 系统的 C/C++ 开发。
DLL 项目的创建
新建 DLL 项目
值得一提的是,通过 Visual Studio 创建 DLL(动态链接库)项目,源文件默认的后缀是 .cpp
,如果项目使用的是 C 语言,则需要将自动生成的 dllmain.cpp
、pch.cpp
的文件名改为 dllmain.c
、pch.c
。
编写 DLL 代码
- 创建
socketclient.c
源文件
socketclient.c
源文件的代码如下,其中__declspec(dllexport)
的作用是将函数导出给 DLL 的调用方
1 |
|
生成 DLL 文件
DLL 项目执行编译后,会自动在项目所在的文件夹内生成 .dll
与 .lib
文件,例如 socket-client.dll
与 socket-client.lib
。在 VS Studio 的 Developer Command Prompt
命令窗口中,使用 dumpbin /exports socket-client.dll
命令,查看得到 socket-client.dll
动态链接库的详细信息如下:
1 | Microsoft (R) COFF/PE Dumper Version 14.29.30136.0 |
案例代码下载
- 点击下载完整的案例代码
DLL 的两种调用方式
DLL 动态调用
在本案例中,实现了通过函数指针类型,动态调用 DLL 里的函数,点击下载 使用到的 socket-client.dll
。值得一提的是,日后如果 DLL 里面的函数体代码作了修改,也不必改动函数调用方的代码(如下代码),就可以正常实现函数的调用,这样非常便于程序的维护和升级。特别注意,动态调用 DLL 里的函数时(动态加载 DLL),不需要 .h
和 .lib
文件,只需要 .dll
文件,同时要知道所要调用的函数的参数类型以及返回值类型(用于定义函数指针类型)。
1 |
|
程序运行的输出结果如下:
1 | initResult = 0 |
DLL 静态调用
静态调用 DLL 里的函数(静态加载 DLL),需要同时使用 .h
、.lib
以及 .dll
文件,具体的操作步骤如下:
- a) 将
.h
、.lib
以及.dll
文件分别拷贝到项目所在的文件夹内,必须与.c
源文件处于同一个文件夹 - b) 在需要调用 DLL 的
.c
源文件中,通过#pragma comment(lib "xxx.lib")
指令引入.lib
文件 - c) 在需要调用 DLL 的
.c
源文件中,通过#include "xxx.h
,引入.h
头文件 - d) 正常编写代码,并调用 DLL 里的函数
若使用的开发工具是 Visual Studio,则可以不通过 #pragma comment(lib "xxx.lib")
指令引入 .lib
文件。右键项目,选择 属性
,导航到 配置属性
-> 链接器
-> 输入
-> 附加依赖项
,添加对应的 .lib
文件名即可,如下图所示:
.h
头文件里一般定义了 DLL 动态链接库里的函数原型,例如 socket-client.h
头文件的代码如下:
1 |
|
静态调用 DLL 里的函数(main.c
)的代码如下:
1 |
|
程序运行的输出结果如下:
1 | the result of socketclient_init() is 0 |
案例代码下载
- 点击下载完整的案例代码(DLL 静态调用)