#pragma section指令可用于创建一个自定义分区,可以将全局变量或者函数放在这个自定义分区内部,实现各个模块之间的数据共享。
对于GNU C/C++编译器来说,直接使用__attribute__((__section__("xxx")))对变量或函数进行修饰即可自动创建好分区,将变量和函数放入对应分区。而在windows 的VC编译器下,必须结合#pragma section和__declspec(allocate("xxx"))两个指令才能实现该功能。
gcc/g++编译器会自动生成分区的起始和结束边界,只需要extern 引入边界变量即可,具体生成的边界变量名可能因编译器版本的区别有差异,需要自行通过链接器查看确认。
#include
#include
#include
typedef void(*calc_t)(int, int);
typedef struct
{
const char* name;
calc_t fn;
} node_t;
// 仅 gcc/g++ 支持__attribute__, VC中有对应的__declspec也可以实现该功能
#define section "ss"
#define SEC __attribute__((__section__(section), aligned(sizeof(node_t))))
#define REGISTER_FUNC(Func) SEC node_t sec_##section_##Func = {.name=#Func,.fn=Func};
// 当section ss不存在时,链接器将会找不到变量__start_ss和__stop_ss,进而报错
// 如果不确认section的起始和终止变量名的时候,可以在编译阶段结束后
// 使用ld -verbose查看编译器生成的section起始和终止的变量名
extern size_t __start_ss;
extern size_t __stop_ss;
void f1(int a, int b)
{
printf("%s %d %d %d\n", __func__, __LINE__, a, b);
}
REGISTER_FUNC(f1)
void f2(int a, int b)
{
printf("%s %d %d %d\n", __func__, __LINE__, a, b);
}
REGISTER_FUNC(f2)
void* get_func(const char* name)
{
size_t i=0;
node_t* p;
for (char* ptr = (char*)(&__start_ss); ptr < (char*)(&__stop_ss);)
{
printf("node id=%d, node address=%#x, nodebytes=[%#x,%#x,%#x,%#x,%#x,%#x,%#x,%#x].\n",i++,ptr,*ptr,*(ptr+1),*(ptr+2),*(ptr+3),*(ptr+4),*(ptr+5),*(ptr+6),*(ptr+7));
p = (node_t*)ptr;
if (!strcmp(p->name, name))
{
return (void*)p->fn;
}
ptr+=sizeof(node_t);
}
return NULL;
}
int main(int argc, char** argv)
{
printf("f1 adddress = %#x,f2 address = %#x.\n",f1,f2);
calc_t ff=(calc_t)get_func("f2");
if(ff!=NULL)
ff(1,2);
return 0;
}
VC编译器没有提供section边界变量,因此需要借助他的section合并功能,自行维护两个边界变量。具体来说,VC链接器在链接的过程中,会将各编译单元中的section按照name的从小到大顺序排列,name的比较规则遵循strcmp的字符串比较逻辑。并且当name中有$符号时,还会执行相应的合并操作,比如创建了3个分区,分区名为 "name$a" "name$b" "name$c",则这3个分区经过排序后,发现它们可以合并,于是3个小分区就合并成为了一个大分区。
借助这个特性,我们就很容易构造出分区的边界。参考如下代码(section中的每个元素需要内存对齐到2的n次幂):
// section_test.h
__declspec(align(16)) struct node
{
const char* name;
const void* func;
};
#pragma section("F1")
#pragma section("F1$z")
__declspec(allocate("F1")) __declspec(selectany) node section_start[];
__declspec(allocate("F1$z")) __declspec(selectany) node section_end[];
// module1.cpp
void Module1Func()
{}
#pragma section("F1$a")
__declspec(allocate("F1$a")) node section_node_created_by_module1={"Module1Func",Module1Func};
// module2.cpp
void Module2Func()
{}
#pragma section("F1$m")
__declspec(allocate("F1$m")) node section_node_created_by_module2={"Module2Func",Module2Func};
// ...
// main.cpp
#include
#include "section_test.h"
int main(int argc, char* argv[])
{
node* ss = §ion_start[0];
node* ee = §ion_end[0];
// 创建的section大小以及可以存放的node数量由编译器相关参数确定
printf("section size = %d, element count=%d.\n",(ee-ss)*sizeof(node),ee-ss);
int id=0;
for(;ssfunc != NULL)
printf("find a valid element in this section, id=%d, name=%s.\n",id,ss->name);
//section被初始化的时候全部置0,所以ss->func为0的时候表明此处未使用
}
return 0;
}
之前写过一篇关于C/C++反射博文,当时是用宏定义实现的静态注册(c++ 单例模式+反射机制_c++ 单例模式怎么实现反射_MOONICK的博客-CSDN博客)。现在通过section这个功能,实现反射会更加容易,代码扩展性也更高。
如果在dll动态链接库中创建section,将变量放到section内,加载同一个dll链接库的进程可以共享这些变量。如下图App1和App2都加载了如同一份dll库,其中放在SharedData分区中的g_cont实现了跨进程共享。(补充一点:在windows操作系统中,当一个dll被加载时,操作系统会自动调用一次DLLMain函数,当进程正常退出释放dll时,会再一次调用DLLMain函数,可以在该函数中做一些初始化和资源清理的动作。windows查看进程信息: wmic process where ProcessId="1234" get /format:value)。
//DLLTEST.cpp 将该文件编为动态链接库DLLTEST.dll
#include
#include
#pragma section("SharedData",read,write,shared)
#pragma comment(linker,"/SECTION:SharedData,RWS")//告知链接器,这个分区的而数据将被共享
__declspec(allocate("SharedData")) int g_cont = 0;
__declspec(dllexport) BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
printf("DllMain called for reason = %d.\n", ul_reason_for_call);
return TRUE;
}
extern "C"
{
__declspec(dllexport) int __stdcall GetSharedVariable()
{
g_cont++;
printf("GetSharedVariable called %d times.\n", g_cont);
return g_cont;
}
}
// App1
#include
#include
//#pragma comment(lib, "C:\\Users\\projects\\Vsproj\\DLLTEST\\x64\\Release\\DLLTEST.lib")
//__declspec(dllimport) BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved);
//__declspec(dllimport) int __stdcall GetSharedVariable();
//int AutoLoadMain(int argc, char* argv[])
//{
// //BOOL res = DllMain(0,0,0);
// int result = GetSharedVariable();
// printf("App1 shared count = %d.\n", result);
// getchar();
// return 0;
//}
typedef int(__stdcall *FuncType)();
int LoadByhandMain(int argc, char* argv[])
{
HMODULE handle = LoadLibrary("C:/Users/projects/Vsproj/DLLTEST/x64/Release/DLLTEST.dll");
FuncType GetSharedVariableLoaded = (FuncType)GetProcAddress(handle,"GetSharedVariable");
int result = GetSharedVariableLoaded();
printf("App1 shared count = %d.\n", result);
getchar();
FreeLibrary(handle);
return 0;
}
int main(int argc, char* argv[])
{
LoadByhandMain(argc,argv);
return 0;
}
// App2
#include
#include
typedef int(__stdcall* FuncType)();
int main(int argc, char* argv[])
{
HMODULE handle = LoadLibrary("C:/Users/projects/Vsproj/DLLTEST/x64/Release/DLLTEST.dll");
FuncType GetSharedVariableLoaded = (FuncType)GetProcAddress(handle, "GetSharedVariable");
int result = GetSharedVariableLoaded();
printf("App2 shared count = %d.\n", result);
FreeLibrary(handle);
getchar();
return 0;
}