因项目需要,需要在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内共享数据区在进程间共享数据(重要)的更多相关文章

  1. linux 共享内存shm_open实现进程间大数据交互

    linux 共享内存shm_open实现进程间大数据交互 read.c #include <sys/types.h> #include <sys/stat.h> #includ ...

  2. DLL入门浅析(5)——使用DLL在进程间共享数据

    转载自:http://www.cppblog.com/suiaiguo/archive/2009/07/21/90734.html 在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同的 ...

  3. 【转】VC 利用DLL共享区间在进程间共享数据及进程间广播消息

    1.http://blog.csdn.net/morewindows/article/details/6702342 在进程间共享数据有很多种方法,剪贴板,映射文件等都可以实现,这里介绍用DLL的共享 ...

  4. 使用DLL在进程间共享数据

    0x01 DLL在进程间共享数据理论 1.可以在Dll中使用#pragma data_seg建立共享类型的数据段将需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享,从而实现不 ...

  5. Swoole 中使用 Table 内存表实现进程间共享数据

    背景 在多进程模式下进程之间的内存是相互隔离的,在一个工作进程中的全局变量和超全局变量,在另一个工作进程中是无法读取和操作的. 如果只有一个工作进程,则不存在进程隔离问题,可以使用全局变量和超全局变量 ...

  6. linux多进/线程编程(2)—— fork函数和进程间“共享”数据

    参考: 1.博客1:https://www.pianshen.com/article/4305691855/ fork:在原进程的基础上"分叉"出一个子进程,即创建一个子进程. N ...

  7. python 进程间共享数据 (二)

    Python中进程间共享数据,除了基本的queue,pipe和value+array外,还提供了更高层次的封装.使用multiprocessing.Manager可以简单地使用这些高级接口. Mana ...

  8. 使用 WM_COPYDATA 在进程间共享数据

    开发中有时需要进程间传递数据,比如对于只允许单实例运行的程序,当已有实例运行时,再次打开程序,可能需要向当前运行的实例传递信息进行特殊处理.对于传递少量数据的情况,最简单的就是用SendMessage ...

  9. 进程间共享数据Manager

    一.前言 进程间的通信Queue()和Pipe(),可以实现进程间的数据传递.但是要使python进程间共享数据,我们就要使用multiprocessing.Manager. Manager()返回的 ...

随机推荐

  1. cgi_and_fastcgi

    CGI 来自维基百科 In computing, Common Gateway Interface (CGI) offers a standard protocol for web servers t ...

  2. chm文件帮助功能全解

    在winform中点击某个按钮弹出关于这个窗体的功能的具体解释文档方法如下: 第一步,使用chm编译工具修改chm每个文档的url 修改完成后保存确认能否打开, 如果不能就使用这个软件的转换功能把ch ...

  3. Swift REPL入门介绍

    Xcode 6.1 引入了一个新特性用来辅助Swift开发,即Read Eval Print Loop(“读取-求值-输出”循环,简称REPL).熟悉解释型语言的开发者将会对这个命令行环境感到舒适,而 ...

  4. python基础一 day11 装饰器复习

    # 复习# 讲作业# 装饰器的进阶 # functools.wraps # 带参数的装饰器 # 多个装饰器装饰同一个函数# 周末的作业 # 文件操作 # 字符串处理 # 输入输出 # 流程控制 # 装 ...

  5. 安装VC++6.0实验环境

    安装VC++6.0步骤:(1)下载一个压缩包进行解压(2)点击打开解压后的文件(3)找到文件里的程序进行安装(4)等待安装完成该程序后可以试着运行一下此程序,在此我们需要了解编写程序的步骤和注意事项. ...

  6. 洛谷 P3958 奶酪

    谨以此题来纪念我爆炸的NOIp2017 这个题虽然很多人说是并查集,但是搜索也是毫无压力的,考场搜索细节写挂,爆了个不上不下的80分.今天无意看到这道题,终于AC 首先这道题要考虑一下精度问题,虽然出 ...

  7. javaEE(2)_http协议

    一.HTTP协议简介 1.客户端连上web服务器后,若想获得web服务器中的某个web资源,需遵守一定的通讯格式,HTTP协议用于定义客户端与web服务器通迅的格式.dos环境下可直接通过telnet ...

  8. 使用dmidecode在Linux下获取硬件信息

    dmidecode命令可以让你在Linux系统下获取有关硬件方面的信息.dmidecode的作用是将DMI数据库中的信息解码,以可读的文本方式显示.由于DMI信息可以人为修改,因此里面的信息不一定是系 ...

  9. laravel模型关联与列表展示

    上面这个是一个模型关联的图,其实我们很容易去理解 比如说,一对一,也就是说一个用户对应的是一个手机号. 一对多,比如说一篇文章可以有多条评论 一对多反向:如一篇文章可以有多条评论,但对应每条评论也只针 ...

  10. PAT Basic 1033

    1033 旧键盘打字 旧键盘上坏了几个键,于是在敲一段文字的时候,对应的字符就不会出现.现在给出应该输入的一段文字.以及坏掉的那些键,打出的结果文字会是怎样? 输入格式: 输入在 2 行中分别给出坏掉 ...