進程間通信 - 動態鏈接庫實現

引子

前面介紹的幾種用於實現進程之間通信的方法都是比較正統的方式,

像剪貼板,命名管道這些都還是用得比較多的,

而這裏將介紹的是一種比較偏門的方法來實現進程間的通信,

所謂偏門呢,自然就是用的少,能夠不用就不要使用。

其實這種方法就是通過動態鏈接庫來實現進程間的通信。

          

                

動態鏈接庫(DLL)概述              

既然是要通過動態鏈接庫來實現進程間的通信,

那麼這裏如果不來介紹一下動態鏈接庫的話,怎麼也說不過去的。

動態鏈接庫是 Windows 操作系統的基礎,其中 Windows API 基本上都是以動態鏈接庫的形式來提供的,

通常來說動態鏈接庫是不能夠直接運行,也不能夠直接接受消息的,

它們是一些獨立的文件(後綴名一般爲 .dll ,當然還有其他的一些後綴名也是可以的),

其中包含能被可執行程序或其他 DLL 調用來完成某項工作的函數,

也就是說動態鏈接庫也就是由一些函數組成而已。

並且只有在其他模塊調用動態鏈接庫中的函數時,動態鏈接庫才發揮作用,

在實際的編程中,通常可以把完成某種功能的函數放在一個動態鏈接庫中,然後提供給其他函數調用。

當這個訪問了動態鏈接庫的進程被加載時,系統會爲這個進程分配 4GB 的私有地址空間,

然後系統就會分析這個可執行模塊,找到這個可執行模塊中所調用的 DLL ,然後系統就負責搜索這些 DLL

找到這些 DLL 後便將這些 DLL 加載到內存中,併爲它們分配虛擬的內存空間,

最後將 DLL 的頁面映射到調用進程的地址空間中,

DLL 的虛擬內存有代碼頁和數據頁,它們被分別映射到 進程 A 的代碼頁面和數據頁面,

如果這時 進程 B 也啓動了,並且 進程 B 也需要訪問該 DLL

這時,只需要將該 DLL 在虛擬內存中的代碼頁面和數據頁面映射到第二個進程的地址空間即可。

這也表明了在內存中,只需要存在一份 DLL 的代碼和數據,

多個進程共享 DLL 的同一份代碼,很明顯這樣做可以節省內存空間的。

但是在 Windows 下,由於系統會爲每一個進程分配 4GB 的私有地址空間,

DLL 中的代碼和數據也只是映射到了這個私有地址空間中,所以這些應用程序之間還是不能夠相互影響的,

也就是說多個應用程序雖然是可以共享同一個 DLL 中的相同的代碼的,

但是 DLL 爲每一個進程保存的數據都是不相同的,

並且每一個進程都爲 DLL 使用的全部數據分配了自己的地址空間,

舉個最簡單的例子,我的 DLL 中有一個函數爲 int   Add(int    num1 ,   int    num2)

這個函數的作用是實現 num1  num2 相加並返回相加後的結果。

然後我有一個 進程 A  使用了這個 DLL ,並且其調用了函數  Add(10, 20),

然後我還有一個 進程 B 其也使用了這個 DLL ,並且其調用了函數 Add(30, 40),

那麼對於 進程 A 中的數據 10 20 其實是保存在 進程 A 的私有地址空間中的,

而對於 進程 B 中的數據 30 40 則是保存在 進程 B 的私有地址空間中的,

上面這個簡單的例子表明如果單單用這種簡單的使用動態鏈接庫的方式是不能夠實現進程之間的通信的。

      

          

動態鏈接庫中共享內存的實現

如果想利用動態鏈接庫來實現進程間的通信的話,那麼有一種方案可以試一試,

即從系統爲動態鏈接庫分配的那一塊內存(系統需要將動態鏈接庫加載到內存中)下手,

由於在內存中,動態鏈接庫其實只存在一份,

其被所有需要調用該動態鏈接庫中的函數的模塊或者簡單說是可執行程序所共享,

既然是共享的話,如果我在系統給動態鏈接庫分配的這塊內存中保存數據,

那豈不是可以被所有訪問該動態鏈接庫的可執行程序所獲取或者說設置。

這樣的話,我就可以使用 進程 A 來設置好這個共享內存中的數據,

然後 進程 B 就可以讀取這個共享內存中的數據了,這不是也可以實現進程間的通信嘛,

這樣看來的話,其思路其實和使用剪貼板是一模一樣的了。

也是採用一塊兩個進程共享的內存來作爲存放數據的中介。

                  

          

示例:動態鏈接庫實現進程間通信

共享 DLL 實現:

新建動態鏈接庫項目步驟:

image

QQ截圖未命名q

項目結構:

image

ShareDLL.h

#ifndef SHARED_DLL
#define SHARED_DLL
 
//在 DLL 項目中設置 DLL_API 爲導出類型 extern "C" _declspec(dllimport)
//在 Test 項目中則無需設置該 DLL_API , 直接使用這個 CalculateDLL.h 文件即可
 
#ifdef DLL_API
#else 
    #define DLL_API extern "C" _declspec(dllimport)
#endif
 
DLL_API void SetData(int tmpData);
DLL_API int GetData();
 
#endif

         

DLL.cpp

// DLL.cpp : 定義 DLL 應用程序的導出函數。
//
 
#include "stdafx.h"
 
#define DLL_API extern "C" _declspec(dllexport)
 
#include "ShareDLL.h"
 
//使用 #pragma data_seg() 來表明這一段數據爲共享數據
//一定要注意給下面的變量初始化,否則將無法實現數據在多個進程間共 享
#pragma data_seg("SharedDataInDll")
 
    //初始化爲 0
    int data = 0;
 
#pragma data_seg()
 
 
//這裏還需要告訴鏈接器表明 SharedDataInDll 數據段爲可讀可寫可共享
#pragma comment(linker, "/SECTION:SharedDataInDll,RWS")
 
 
//返回共享數據
int GetData()
{
    return data;
}
 
//設置共享數據
void SetData(int tmpData)
{
    data = tmpData;
}

進程 A 實現:(簡單 Console 程序)

項目結構:

image

ShareDLL.h

#ifndef SHARED_DLL
#define SHARED_DLL
 
//在 DLL 項目中設置 DLL_API 爲導出類型 extern "C" _declspec(dllimport)
//在 Test 項目中則無需設置該 DLL_API , 直接使用這個 CalculateDLL.h 文件即可
 
#ifdef DLL_API
#else 
    #define DLL_API extern "C" _declspec(dllimport)
#endif
 
DLL_API void SetData(int tmpData);
DLL_API int GetData();
 
#endif
               

DLLProcessA.cpp

#include <iostream>
#include "ShareDLL.h"
 
using namespace std;
 
//引用 DLL.lib 引入庫
#pragma comment(lib, "DLL.lib")
 
int main(int argc, char * argv)
{
    int data;
 
    cout<<"進程 A 設置數據:  ";
    cin>>data;
 
    //設置共享內存
    SetData(data);
 
    cout<<endl<<endl;
    system("pause");
 
    //讀取共享內存
    cout<<"進程 A 讀取數據:  "<<GetData()<<endl<<endl;
 
    system("pause");
}

進程 B 實現:(簡單 Console 程序)

項目結構:

image

ShareDLL.h

#ifndef SHARED_DLL
#define SHARED_DLL
 
//在 DLL 項目中設置 DLL_API 爲導出類型 extern "C" _declspec(dllimport)
//在 Test 項目中則無需設置該 DLL_API , 直接使用這個 CalculateDLL.h 文件即可
 
#ifdef DLL_API
#else 
    #define DLL_API extern "C" _declspec(dllimport)
#endif
 
DLL_API void SetData(int tmpData);
DLL_API int GetData();
 
#endif
          

DLLProcessB.cpp

#include <iostream>
#include "ShareDLL.h"
 
using namespace std;
 
//引用 DLL.lib 引入庫
#pragma comment(lib, "DLL.lib")
 
int main(int argc, char * argv)
{
    int data;
 
    //讀取共享數據
    cout<<"進程 B 讀取數據:  "<<GetData()<<endl<<endl;
 
    cout<<"進程 B 設置數據:  ";
    cin>>data;
 
    //設置共享數據
    SetData(data);
 
    cout<<endl<<endl;
    system("pause");
}

需要將 DLL 項目中的 DLL . dll 和 DLL . lib 兩個文件,

分別拷貝到項目 DLLProcessA 和 DLLProcessB 的根目錄下。

然後分別編譯 DLLProcessA 和 DLLProcessB 兩個項目,

最後將 Dll . dll 和 DLL . lib 以及 DLLProcesA . exe 和 DLLProcessB . exe 拷貝到同一目錄下面,

比如:(這樣可以確保兩個進程訪問到的是同一個動態鏈接庫)

image

效果展示:

首先運行 DLLProcessA . exe 文件並設置共享數據爲 8 :

image

然後啓動 DLLProcessB . exe 文件(可以看出其讀出的值爲 8 ):

image

然後再在 DLLProcessB . exe 中設置數據爲 16 :

image

然後再在 DLLProcessA . exe 中按下回車鍵(此時可以看到進程 A 讀取到的數據位 16 了):

image

           

          

結束語

從上面的這個效果展示中可以看出,我們確實通過動態鏈接庫實現了 進程 A 進程 B 之間的通信,

前面說過使用動態鏈接庫來實現進程之間的通信是一個偏方,

通過這個 Demo 呢,我們也是可以看出這種方式的侷限性的,

第一,使用動態鏈接庫來實現進程間的通信的話,首先必須要求通信的雙方進程都訪問了這個動態鏈接庫。

第二,這種方式只適用於本地進程之間的通信,其不能實現跨網絡的通信。

關於進程之間通信呢,前前後後介紹了五種方法,其中各有各的優點,

也各有各的侷限性,至於具體要使用那一種的話,那還請各位看官自行斟酌,然後選用合適的方案 !!!

                   

       

版權所有,迎轉載,但轉載請註明:     轉載自    Zachary.XiaoZhen - 夢想的天空

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章