【C++】DLL内共享数据区在进程间共享数据(重要)
因项目需要,需要在DLL中共享数据,即DLL中某一变量只执行一次,在运行DLL中其他函数时该变量值不改变;刚开始想法理解错误,搜到了DLL进程间共享数据段,后面发现直接在DLL中定义全局变量就行,当时脑袋有点犯2了。但既然接触到DLL进程间共享数据段,觉得还是挺重要的,干脆一不做二不休,就详细了解了下有关知识,进行了一些总结,留作备忘。
全局变量在DLL内使用,在同一进程同一DLL文件中的相互调用是正常的,包括指针的使用;不同进程中参数互不影响。
当C#启动后开始加载DLL文件,文件中的初始代码就会执行,所有全局变量会一直保存实值,直到C#程序运行结束或主动释放加载的DLL文件,这样DLL文件就可以被看作一个伴随C#主进程一直运行的子线程,运行过程中不会释放变量.
默认情况下,同一个程序启动多个进程,它们各自的变量值是不会相互影响的。第二个实例启动后,在修改全局变量的时候,系统会运用内存管理系统copy- on-write的特性来防止修改了第一个实例的数据,即系统会再分配一些内存,并将全局变量复制到这块内存中,每个实例使用自己的内存空间上的数据而互不影响。
下面重点介绍下DLL进程间共享数据段
在多个进程间共享数据,windows提供了这种方法,就是创建自己的共享数据节,并将需要共享的变量放入该内存中。如果是在相同程序的多个实例间共享数据,只要在exe文件创建共享节即可,否则就需要在DLL中创建共享节,其它进程加载该DLL来共享数据。
在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。在访问同一个Dll的各进程之间共享存储器是通过存储器映射文件技术实现的。也可以把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。必须给这些变量赋初值,否则编译器会把没有赋初始值的变量放在一个叫未被初始化的数据段中。
在DLL的实现文件中添加下列代码:
#pragma data_seg("Shared") // 声明共享数据段,并命名该数据段
int SharedData = ; // 必须在定义的同时进行初始化!!!!
#pragma data_seg()
在#pragma data_seg("DLLSharedSection")和#pragma data_seg()之间的所有变量将被访问该Dll的所有进程看到和共享。仅定义一个数据段还不能达到共享数据的目的,还要告诉编译器该段的属性,有三种方法可以实现该目的(其效果是相同的),一种方法是在.DEF文件中加入如下语句:
SETCTIONS
Shared READ WRITE SHARED
另一种方法是在项目设置的链接选项(Project Setting --〉Link)中加入如下语句:
/SECTION:Shared,rws
还有一种就是使用指令:
#pragma comment(linker,"/section:.Shared,rws") // 可读,可写,进程间共享。所有加载此dll的进程共享一份内存
那么这个数据节中的数据可以在所有DLL的实例之间共享了。所有对这些数据的操作都针对同一个实例的,而不是在每个进程的地址空间中都有一份。
当进程隐式或显式调用一个动态库里的函数时,系统都要把这个动态库映射到这个进程的虚拟地址空间里。这使得DLL成为进程的一部分,以这个进程的身份执行,使用这个进程的堆栈。
下面是几个需要注意的语法问题:
()#pragma data_seg()一般用于DLL中。也就是说,在DLL中定义一个共享的,有名字的数据段。最关键的是:这个数据段中的全局变量可以被多个进程共享。否则多个进程之间无法共享DLL中的全局变量.
()数据段的名称为“Shared”,那么在设置该段属性的时候,一定要保证段名称完全与“Shared”相同,而且大小写敏感。一旦两者不同,连接器会警告错误。
>LINK : warning LNK4039: 用 /SECTION 选项指定的节“Shared”不存在。注意是警告错误,所以DLL文件会继续编译连接成功,只是Shared数据段并没有设置为共享段。
()最后一行中的rws之前不能有空格,否则编译器报错。
>main.obj : fatal error LNK1276: 找到无效的指令“rws”; 未以“/”开头。然后停止编译连接。
()共享段中的变量一定要初始化,否则连接器也会报错,也不能正常设置为共享段。
所有在共享数据段中的变量,只有在数据段中经过了初始化之后,才会是进程间共享的。如果没有初始化,那么进程间访问该变量则是未定义的,编译器会把没有初始化的数据放到.BSS段中,从而导致多个进程之间的共享行为失败。
>LINK : warning LNK4039: 用 /SECTION 选项指定的节“Shared”不存在。 但是继续生成dll文件。
这几种错误,最严重的就是()和(),因为虽然没有成功设置共享段,但是仍然编译成功,稍不注意,就会非常危险。对于()则根本不能编译成功,所以只要了解语法修改就可以了,不存在潜在危险。
() 所有的共享变量都要放置在共享数据段中。如果定义很大的数组,那么也会导致很大的DLL。
()不要在共享数据段中存放进程相关的信息。Win32中大多数的数据结构和值(比如HANDLE)只在特定的进程上下文中才是有效地。
()每个进程都有它自己的地址空间。因此不要在共享数据段中共享指针,指针指向的地址在不同的地址空间中是不一样的。
()DLL在每个进程中是被映射在不同的虚拟地址空间中的,因此函数指针也是不安全的。
()当然还有其它的方法来进行进程间的数据共享,比如文件内存映射等,涉及到通用的进程间通信。
特别注意的是:(特别重要)
Dll中共享数据的限制:
、必须初始化共享数据段中的所有变量。
、所有共享变量都存放在编译DLL的指定数据段中。
、永远不要将特定于进程的信息存储在共享数据段中。
、永远不要将指针存储在共享段包含的变量中。
、具有虚拟函数的类总是包含函数指针。因此有虚拟函数的类永远不要存储在共享数据段中。
、静态数据成员以全局变量的等效形式实现。因此每个进程都有他自己的该静态数据成员的副本。不应存储具有静态数据成员的类。
、对于 C++ 类,共享数据段的初始化要求引起一个特定问题。如果共享数据段中有类似 CTest Counter(); 的内容,则当每个进程加载 DLL 时,Counter 对象将在该进程中初始化,
从而有可能每次都将对象的数据清零。这与内部数据类型(由链接器在创建 DLL 时初始化)非常不同。 因此不建议在进程间共享C++对象。
实例测试
在这里,用C++封装DLL,用WPF工程来测试,由于一般项目都是单进程的,所以我们创建两个WPF工程当做两个进程进行测试。
C++封装代码如下:
testdll.h文件
#ifndef TestDll_H_
#define TestDll_H_
#ifdef MYLIBDLL
#define MYLIBDLL extern "C" _declspec(dllimport)
#else
#define MYLIBDLL extern "C" _declspec(dllexport)
#endif
//可以include需要用到的头文件
MYLIBDLL void SetData(int num1, int num2);
MYLIBDLL int GetArray();
MYLIBDLL int getNum();
MYLIBDLL int getInfoAge();
#endif
testdll.cpp文件
#include "testdll.h"
#include <iostream>
using namespace std;
struct Info
{
int num;
int age;
};
////////////////////////////////////////////// 进程共享区 ///////////////////////////////////////
#pragma data_seg("Shared") // 声明共享数据段,并命名该数据段
Info info = {}; // 变量必须在定义的同时进行初始化!!!!
int p[] ={,}; //不能用指针
int num = ;
#pragma data_seg()
#pragma comment(linker, "/section:Shared,rws") // 可读,可写,进程间共享。所有加载此dll的进程共享一份内存 void SetData(int num1, int num2)
{
num = num1 + num2;
info.age = num2;
p[] = num1;
}
int GetArray()
{
return p[];
}
int getNum()
{
return num;
}
int getInfoAge()
{
return info.age;
}
WPF工程引用DLL的接口类:
class Test
{
//相对路径
//[DllImport(@"..\\..\\..\\..\\DLL\\Win32Project1.dll", EntryPoint = "SetData", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
[DllImport(@"D:\\DLL\\Win32Project1.dll", EntryPoint = "SetData", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern void SetData(int m, int n); [DllImport(@"D:\\DLL\\Win32Project1.dll", EntryPoint = "GetArray", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int GetArray(); [DllImport(@"D:\\DLL\\Win32Project1.dll", EntryPoint = "getNum", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int getNum(); [DllImport(@"D:\\DLL\\Win32Project1.dll", EntryPoint = "getInfoAge", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int getInfoAge(); }
WPF工程1:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent(); //第一次执行,启动工程1
Test.SetData(, );
int a = Test.GetArray(); //
int b = Test.getNum(); //11
int c = Test.getInfoAge(); //
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//第四次执行,点击按钮
int a = Test.GetArray(); //
int b = Test.getNum(); //
int c = Test.getInfoAge(); //
}
}
WPF工程2
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//第二次执行,启动工程2
int a = Test.GetArray(); //
int b = Test.getNum(); //
int c = Test.getInfoAge(); //
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//第三次执行,点击按钮
Test.SetData(, );
int a = Test.GetArray(); //
int b = Test.getNum(); //
int c = Test.getInfoAge(); //
}
}
上述第N次执行为2个程序运行顺序,可以看到两者能进行进程间共享数据,
在这里需要特别提醒的是,两个工程引用的DLL必须是同一个,DLL复制一份,就相当于不是同一个dll了。
这里发现一个问题:(共享结构体、类时发生)
struct Info{
int num;
int age;
};
struct Info2{
int num;
int age;
Info2(int a){
num = a;
age = a;
}
};
struct Info3{
int num;
int age;
Info3(int a){
}
};
当我C++封装DLL时结构体分别为上述三种类型,我们会发现在第二次执行时,int c = Test.getInfoAge();的值会有不同,Info和Info3为正常的6,Info会为1。
在这里分析原因可能如下:(注意这里可能是由于调用类的构造函数中初始化赋值操作才导致的)
对于 C++ 类,共享数据段的初始化要求引起一个特定问题。如果共享数据段中有类似 CTest Counter(0); 的内容,则当每个进程加载 DLL 时,Counter 对象将在该进程中初始化,从而有可能每次都将对象的数据清零。这与内部数据类型(由链接器在创建 DLL 时初始化)非常不同。 因此不建议在进程间共享C++对象。
自己理解的: 即对象类的共享数据(结构体、类等)在每个进程加载DLL时都会执行初始化操作,一般是调用构造函数。如果调用默认构造函数 Info(){}; 因为没有对变量进行赋值,所以第二个进程启动时
不会影响第一次的执行结果,Info3同理;而对于Info2,在构造函数中对变量进行了赋值操作,因此会重写共享数据段的数据,造成错误结果。
参考链接:http://www.cnblogs.com/bjguanmu/articles/4398121.html
http://blog.csdn.net/chinabinlang/article/details/17751601
http://blog.csdn.net/trustbo/article/details/11937211
【C++】DLL内共享数据区在进程间共享数据(重要)的更多相关文章
- linux 共享内存shm_open实现进程间大数据交互
linux 共享内存shm_open实现进程间大数据交互 read.c #include <sys/types.h> #include <sys/stat.h> #includ ...
- DLL入门浅析(5)——使用DLL在进程间共享数据
转载自:http://www.cppblog.com/suiaiguo/archive/2009/07/21/90734.html 在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同的 ...
- 【转】VC 利用DLL共享区间在进程间共享数据及进程间广播消息
1.http://blog.csdn.net/morewindows/article/details/6702342 在进程间共享数据有很多种方法,剪贴板,映射文件等都可以实现,这里介绍用DLL的共享 ...
- 使用DLL在进程间共享数据
0x01 DLL在进程间共享数据理论 1.可以在Dll中使用#pragma data_seg建立共享类型的数据段将需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享,从而实现不 ...
- Swoole 中使用 Table 内存表实现进程间共享数据
背景 在多进程模式下进程之间的内存是相互隔离的,在一个工作进程中的全局变量和超全局变量,在另一个工作进程中是无法读取和操作的. 如果只有一个工作进程,则不存在进程隔离问题,可以使用全局变量和超全局变量 ...
- linux多进/线程编程(2)—— fork函数和进程间“共享”数据
参考: 1.博客1:https://www.pianshen.com/article/4305691855/ fork:在原进程的基础上"分叉"出一个子进程,即创建一个子进程. N ...
- python 进程间共享数据 (二)
Python中进程间共享数据,除了基本的queue,pipe和value+array外,还提供了更高层次的封装.使用multiprocessing.Manager可以简单地使用这些高级接口. Mana ...
- 使用 WM_COPYDATA 在进程间共享数据
开发中有时需要进程间传递数据,比如对于只允许单实例运行的程序,当已有实例运行时,再次打开程序,可能需要向当前运行的实例传递信息进行特殊处理.对于传递少量数据的情况,最简单的就是用SendMessage ...
- 进程间共享数据Manager
一.前言 进程间的通信Queue()和Pipe(),可以实现进程间的数据传递.但是要使python进程间共享数据,我们就要使用multiprocessing.Manager. Manager()返回的 ...
随机推荐
- monkeyrunner之控件ID不存在或重复(转载lynnLi)
我们在用monkeyrunner进行Android自动化时,通过获取坐标点或控件ID进行一系列操作.由于使用坐标点时,屏幕分辨率一旦更改,则代码中用到坐标的地方都要修改,这样导致代码的复用率较低.因此 ...
- 黑苹果10.10.3手动开启SSD的TIRM提高硬盘效率
黑苹果10.10.3手动开启SSD的TIRM提高硬盘效率 文章前言 其实开启TIRM的方法有很多,比如用Clover注入的方式或者用其他的工具来方便完成,但是10.10.3刚刚出来有些工具还没有更新的 ...
- Python赋值运算及流程控制
1. 内置函数 1> len:统计元素长度 str1 = 'wonderful' print(len(str1)) result: li = [,,] print(len(li)) result ...
- Linux中文件压缩与解压
压缩与解压 compress 文件名 1 -v //详细信息 2 3 -d //等于 uncompress 默认只识别 .Z 如果使用别的后缀,会导致不识别,解压缩失败.也可以使用 -d -c 压缩包 ...
- FIFO设计思考之一
不管同步FIFO还是异步FIFO,设计难点是full/empty状态flag的正确性. 要保证任何情况 FULL时NO WRITE,EMPTY时NO READ.overflow / underflow ...
- (三)Python3 循环语句——while
while语句的一般形式: while 判断条件: 语句 同样需要注意冒号和缩进.另外,在 Python 中没有 do..while 循环. 以下实例使用了 while 来计算 1 到 100 的总和 ...
- 有关linux的GPG签名验证错误的解决方法。
GPG签名验证错误:由于没有公钥,下列签名无法进行验证: NO_PUBKEY 6AF0E1940624A220 找了下原因,虽然不知道原理,不过大概意思还是能才出来的,解决方法如下: gpg --ke ...
- (转)在Xcode 7上直接使用Clang Address Sanitizer
原文地址: http://www.cocoachina.com/ios/20150730/12830.html WWDC 2015上,除了Swift 2.0外,还有一个令人激动的消息:可以直接在Xco ...
- 【HIHOCODER 1529】 不上升序列
描述 给定一个长度为 n 的非负整数序列 a[1..n]. 你每次可以花费 1 的代价给某个 a[i] 加1或者减1. 求最少需要多少代价能将这个序列变成一个不上升序列. 输入 第一行一个正整数 n. ...
- LeetCode 673. Number of Longest Increasing Subsequence
Given an unsorted array of integers, find the number of longest increasing subsequence. Example 1: I ...