建议方法的想法是使用间接函数调用,以便必须首先计算函数地址,然后再调用。 C 预处理器用于提供一种为实际函数定义代理函数的方法,该代理函数提供确定代理函数提供访问的实际函数的实际地址所需的计算。
See 维基百科文章代理模式 https://en.wikipedia.org/wiki/Proxy_pattern有关代理设计模式的详细信息,其中有这样一句话:
代理设计模式允许您向其他人提供接口
通过创建包装类作为代理来获取对象。包装类,
这是代理,可以向对象添加附加功能
兴趣而不改变对象的代码。
我建议采用一种替代方案,它实现相同类型的间接调用,但它不需要使用 C 预处理器来隐藏实现细节,从而使源代码的阅读变得困难。
C 编译器允许struct
包含函数指针作为成员。这样做的好处是,您可以使用函数指针 a 成员定义外部可见的结构变量,但在定义该结构时,可以在结构变量定义中指定的函数static
这意味着它们仅具有文件可见性(请参阅C程序中的“静态”是什么意思 https://stackoverflow.com/questions/572547/what-does-static-mean-in-a-c-program.)
所以我可以有两个文件,一个头文件 func.h 和一个实现文件 func.c ,它们定义了struct
类型、外部可见结构变量的声明、与 a 一起使用的函数static
修饰符,以及带有函数地址的外部可见结构变量定义。
这种方法的吸引力在于源代码易于阅读,并且大多数 IDE 会更好地处理这种间接代码,因为 C 预处理器不用于在编译时创建源代码,这会影响人们和软件工具(例如作为 IDE。
示例 func.h 文件将使用函数 #included 到 C 源文件中,如下所示:
// define a type using a typedef so that we can declare the externally
// visible struct in this include file and then use the same type when
// defining the externally visible struct in the implementation file which
// will also have the definitions for the actual functions which will have
// file visibility only because we will use the static modifier to restrict
// the functions' visibility to file scope only.
typedef struct {
int (*p1)(int a);
int (*p2)(int a);
} FuncList;
// declare the externally visible struct so that anything using it will
// be able to access it and its members or the addresses of the functions
// available through this struct.
extern FuncList myFuncList;
func.c 文件示例可能如下所示:
#include <stdio.h>
#include "func.h"
// the functions that we will be providing through the externally visible struct
// are here. we mark these static since the only access to these is through
// the function pointer members of the struct so we do not want them to be
// visible outside of this file. also this prevents name clashes between these
// functions and other functions that may be linked into the application.
// this use of an externally visible struct with function pointer members
// provides something similar to the use of namespace in C++ in that we
// can use the externally visible struct as a way to create a kind of
// namespace by having everything go through the struct and hiding the
// functions using the static modifier to restrict visibility to the file.
static int p1Thing(int a)
{
return printf ("-- p1 %d\n", a);
}
static int p2Thing(int a)
{
return printf ("-- p2 %d\n", a);
}
// externally visible struct with function pointers to allow indirect access
// to the static functions in this file which are not visible outside of
// this file. we do this definition here so that we have the prototypes
// of the functions which are defined above to allow the compiler to check
// calling interface against struct member definition.
FuncList myFuncList = {
p1Thing,
p2Thing
};
使用此外部可见结构的简单 C 源文件可能如下所示:
#include "func.h"
int main(int argc, char * argv[])
{
// call function p1Thing() through the struct function pointer p1()
myFuncList.p1 (1);
// call function p2Thing() through the struct function pointer p2()
myFuncList.p2 (2);
return 0;
}
Visual Studio 2005 为上述内容生成的汇编程序main()
如下所示,显示了通过指定地址的计算调用:
; 10 : myFuncList.p1 (1);
00000 6a 01 push 1
00002 ff 15 00 00 00
00 call DWORD PTR _myFuncList
; 11 : myFuncList.p2 (2);
00008 6a 02 push 2
0000a ff 15 04 00 00
00 call DWORD PTR _myFuncList+4
00010 83 c4 08 add esp, 8
; 12 : return 0;
00013 33 c0 xor eax, eax
正如您所看到的,此函数调用现在是通过结构内的偏移量指定的结构的间接函数调用。
这种方法的好处是,您可以对包含函数指针的内存区域执行任何您想要的操作,只要在通过数据区域调用函数之前,正确的函数地址已放在那里即可。因此,您实际上可以有两个函数,一个函数用正确的地址初始化该区域,第二个函数清除该区域。因此,在使用这些函数之前,您将调用该函数来初始化该区域,并在使用该函数之后调用该函数来清除该区域。
// file scope visible struct containing the actual or real function addresses
// which can be used to initialize the externally visible copy.
static FuncList myFuncListReal = {
p1Thing,
p2Thing
};
// NULL addresses in externally visible struct to cause crash is default.
// Must use myFuncListInit() to initialize the pointers
// with the actual or real values.
FuncList myFuncList = {
0,
0
};
// externally visible function that will update the externally visible struct
// with the correct function addresses to access the static functions.
void myFuncListInit (void)
{
myFuncList = myFuncListReal;
}
// externally visible function to reset the externally visible struct back
// to NULLs in order to clear the addresses making the functions no longer
// available to external users of this file.
void myFuncListClear (void)
{
memset (&myFuncList, 0, sizeof(myFuncList));
}
所以你可以做这样的修改main()
:
myFuncListInit();
myFuncList.p1 (1);
myFuncList.p2 (2);
myFuncListClear();
然而,您真正想做的是打电话给myFuncListInit()
位于源代码中不靠近实际使用函数的位置。
另一个有趣的选择是对数据区域进行加密,为了使用该程序,用户需要输入正确的密钥来正确解密数据以获得正确的指针地址。