C 语言DLL跨平台调用时,支持多线程TLS方法的使用

来源:互联网 发布:金庸x 知乎 编辑:程序博客网 时间:2024/06/14 04:18

在java多线程调用C语言生成的dll动态库时,遇到问题,多个线程之间共享一份全局和静态变量,导致结果异常甚至运行错误。经过研究发现需要使用TLS技术来进行多线程本地存贮,为每一个线程开辟一块空间来存储C语言生成的dll动态库中需要保存的全局变量,这样来保证多线程调用dll中的方法时,dll中的全局变量相互独立,不受其他线程干扰。
看了一个博客来介绍TLS的使用: http://blog.csdn.net/bingge1022/article/details/73274229
要想在C语言跨平台调用时,能够契合TLS方法,需要在C语言中指定需要在跨语言调用时需要使用TLS技术的变量。主要操作分为4种:分配空间、取数、存数、释放空间。
1:分配空间,上面那篇博客中提到MSDN主页上介绍TLS的内容,其中分配TLS空间的函数为DllSet(),分配空间使用命令:DllSet(DLL_PROCESS_ATTACH)
但该函数体内包含具体的在TLS.cpp中声明的全局变量

int threadId;bool DllSet(int fdwReason){ ... if ((threadId = TlsAlloc()) == TLS_OUT_OF_INDEXES); ... fIgnore = TlsSetValue(threadId, lpvData); ...  lpvData = TlsGetValue(threadId);...}

对此我十分的费解,一个功能函数怎么会对某个具体变量(该变量是全局变量,而非 输入参数)进行一些操作呢?由于对TLS机制不是很了解,只能自以为对的解释为这样是一个线程中进行某一个变量的操作,就可以完成该线程全部全局变量的TLS分配。结果却说明根本不是这样,后面会讲到。
2:取值,GetData(int *piv, int intTlsVar)//将intTlsVar的值赋给piv,当通过DllSet对某个全局变量进行TLS空间分配后,就不能像在C程序中那样简单使用变量了,因为该变量被分配进了TLS空间(为方便说明,本文称之为TLS变量),所以每次获取TLS变量的值时都需要通过GetData()函数来获取TLS变量的值,且无法直接获取TLS变量的值,只能新声明一个与TLS变量同类型的变量,调用GetData()将TLS变量的值取出来,例如,变量int a为全局变量,已经调用DLLSet进行TLS空间分配:使用时应参照以下方式,

 int temp_a = 0; temp_a = GetData(temp_a, a);


用temp_a 进行运算。。。。。。
3: 赋值

在return 之前,利用temp_a 的值赋给a:

 StoreData(temp_a, a);

4:释放TLS空间:
当TLS变量不再使用时,将TLS变量所占的空间释放掉。

DllSet(DLL_PROCESS_DETACH)

多线程测试单TLS变量,没有问题,通过。
单线程测试多TLS变量,这就触碰到了一开始觉得很疑惑的地方,DllSet()中对具体的某个变量名进行操作,那结果是只对该变量进行了空间开辟还是说DllSet.cpp中的变量相当于形参,对某个变量开辟TLS空间就是对该线程开辟出了空间,在该线程中的其余变量自动分配TLS空间?
结果就是,只对DllSet()中操作的那个变量开辟了空间,更准确的说,只是因为DllSet()对那个具体的int变量的操作,为该线程分配了一个int 大小的空间,多个变量共用这一个变量的空间,得到这个结论也是做了很多的测试,

int a;int b;int c;//注意,如果a、b、c是你想要的TLS变量,不要在定义时进行初始化。int main(){    Init();    int a_temp ;    int b_temp ;   int c_temp ;for (int i = 0; i < 3; i++){    GetData(a_temp,a);    GetData(b_temp,b);    GetData(c_temp,c);    printf( "%d\t%d\t%d\n",a_temp,b_temp,c_temp);    a_temp = 2;    b_temp = 1;    c_temp = 0;   StoreData(a_temp ,a);   StoreData(b_temp ,b);   StoreData(c_temp ,c);}return 0;}int Init(){    StoreData(0,a);//a 初始值设为0,之前简单的int a = 0,现在需要两部才能完成。    StoreData(0,b);    StoreData(0,c);    return 0;}

得到的结果是:
0 0 0
0 0 0
0 0 0
0 0 0
开始怀疑初始化的问题(因为初始化全为0),单步调试到库函数也未果,初始化也没有在循环体内,为何StoreData的时候都是正确的,当再次循环GetData()时却全变成0?
茫茫的测试,茫茫的茫然,将C的赋值由 StoreData(c_temp ,c); 改为 StoreData(3,c);
那么得到的结构是:
3 3 3
3 3 3
3 3 3
改为StoreData(55,c);
结果为:
55 55 55
55 55 55
55 55 55
恍然大悟:因为StoreData(3,c);是在最后执行的,循环调用时取到的值都是最后一次调用StoreData()的结果。也就是DllSet()只分配了一个int 大小的空间作为TLS变量,无论程序中声明多少个变量,都是公用这一个空间,这就解释了,对全体变量取值,取到的都是最后一次StoreData的结果。

可是DllSet是对某个具体变量的操作,难不成要每声明一个变量就要赋值一份DllSetVarName(),然后用函数名区分,一个变量声明对应一个DllSetVarName?
那就太笨了,程序很冗余,也不优雅。那就想想办法!

能不能将变量名作为输入参数,那么只需要一个函数,就可以完成所有变量的TLS空间分配?按照这个思路,进行了实验,将原来的

int threadId;bool DllSet(int fdwReason){...}

改为

bool DllSet(int fdwReason,int &threadId){...}

结果是:对于所有的int型变量,在Init()时,调用DllSet(int fdwReason,int &threadId)对其进行TLS空间分配,这样就一切正常了!经测试,各个变量之间独立不干涉。 当然对于short型和其他型的变量,对DllSet(int fdwReason,int &threadId)进行调整就可以啦。

原创粉丝点击