在Windows系統中,各個應用程序(進程)之間常常需要交換、傳遞數據,這就要解決進程間的數據通信問題。在最初的16位Windows3.x系統中,所有Windows應用程序共享單一地址,任何進程都能夠對這一共享地址空間的數據進行讀寫操作。隨著Windwos98、WindowsNT、Windows2000等32位的操作系統的出現,規定每個進程都有自己的地址空間,一個Windows進程不能存取另一個進程的私有數據,也就是說,雖然兩個進程可以用具有相同值的指針尋址,但所讀寫的只是它們各自的數據,這樣就減少了進程之間的相互干擾。那麼上述技術的采用是否意味著各個應用程序之間不能進行數據交換了呢?答案當然是否定的,強大的Windows系統早已為我們設計了很多方案來解決進行間的通信問題,這裡我們只探討如何通過動態數據交換(DDE)方法實現進程間的數據通信。
本實例程序功能如下,服務器端有兩個數據項,一個是輸入的字符串,另一個是定時增加的整數。運行該程序的兩個實例後,兩個程序就可以建立DDE連接,實現數據的傳遞,並將另外一個實例傳送過來的數據顯示出來。下圖為程序編譯運行後的效果圖:
圖一、DDE方法實現進程間數據通信程序的界面效果圖
一、實現方法
自從微軟推出Windows操作系統以來,動態數據交換(DDE)就已經成為Windows的部分,並且很多Windwos應用程序都使用了DDE技術來實現進程之間的數據交換。DDE是建立在Windows內部消息系統、全局和共享全局內存基礎上的一種協議,用來協調Windows應用程序之間的數據交換和命令調用,它已經成為應用程序之間通信的一種常用方法。
DDE應用程序可以分為四種類型:客戶類型、服務器類型、客戶/服務器類型和監視器。DDE會話發生在客戶應用程序和服務器應用程序之間。客戶應用程序從服務器應用程序請求數據或服務,服務器應用程序響應客戶應用程序的數據或服務請求。客戶/服務器應用程序是既可以發出請求,又可以提供信息,監視器應用程序則是用語調試的目的。
DDE協議使用三級樹型命名:服務(SERVICE)、主題(TOPIC)和數據項(ITEM)來標識DDE所要傳送的數據單元。服務使應用程序具有了提供給其他程序的數據交換能力;主題類似於目錄,是建立會話連接的參數:ITEM才是DDE具體通信時要傳送的數據內容,比如一個數據或一個字符串。
動態交換管理庫(DDEML)提供了DDE和應用程序級協議。使用DDEML開發的應用程序無論是在運行一致性方面還是在應用程序相互通信方面性能均優於沒有使用DDEML的應用程序。而且DDEML的應用使得開發支持DDE的應用程序容易了許多。
建立DDE會話後,客戶程序和服務器程序可以通過三種鏈接方式進行數據交換,分別是:1、冷鏈接:客戶程序申請數據,服務器程序立即給客戶程序發送數據;2、溫鏈接:服務器程序通知客戶程序數據數據項發生了改變,但是並沒有將已發生的值發送給客戶程序。3、熱鏈接:當數據項發生變化時,服務器程序立即把變化後的值發送給客戶程序,這是最常用、最方便的方法,下面的例子就使用的這種方法。
DDE會話初始化
使用API函數DdeInitialize(),在DDEML中注冊應用。
會話建立
服務器:注冊服務DdeNameService.
客戶:連接DdeConnect.
會話過程
類似於Windows的消息循環,會話的過程就是事務處理的過程。客戶通過DdeClientTransaction()來發出事務請求,通過DDE回調函數,服務器處理客戶事務請求,返回DdeCreateDataHandle來發送數據,同時客戶可以調用DdeGetData()獲取數據。
會話結束
可由服務方或客戶方來終止會話,推出程序時要注消服務,釋放資源,調用DdeUninitialize()。
二、編程步驟
1、啟動Visual C++6.0,新建一個基於對話框的MFC應用程序,取名為DDEdemo,添加兩個Group Box控件並分別在其上放置編輯控件IDC_EDIT、靜態控件ID_STATIC1、ID_STATIC2、 ID_STATIC3,用Wizard添加對應成員變m_edit(CString類型),添加並將其Caption置空,最後的界面如圖一所示;
2、使用CLASSWIZARD添加對話框函數,分別為WM_DESTORY、WM_INITDIALOG、WM_TIMER及IDC_EDIT的EN_CHANGE消息建立對應函數;
3、在DDEdemoDlg.CPP中加入#include "ddel.h"以使用DDEML函數。並添加以下宏定義和全局變量:
#define NITEM 2 //定義ITEM的數量;
const char szApp[]="Server"; //server DDE服務名;
const char szTopic[]="Topic";//Server DDE目錄名;
const char *pszItem[NITEM]={"Item1","Item2"};//SERVER ITEM名稱字符串數組;
int count=0;//記數,在Static1中顯示;
CString ServerData[NITEM];//存放服務器中的數據項內容;
HCONV hConv=0; //會話句柄;
DWORD idlnst=0; //DDEML實例句柄;
HWND hWnd; //窗口句柄;
HANDLE hlnst; //實例句柄;
HSZ hszApp=0; //SERVER服務字符串句柄;
HSZ hszTopic=0; //SERVER目錄字符串句柄;
HSZ hszItem[NITEM]; //Server ITEM字符串句柄;
BOOL bConnect; // 建立連接標志;
4、輸入代碼,編譯運行程序。
三、程序代碼
//////////////////////////////////////DDE回調函數;
HDDEDATA CALLBACK DdeCallback(UINT wType,UINT wFmt,HCONV hConv,HSZ Topic,HSZ Item,
HDDEDATA hData,DWORD lData1,DWORD lData2)
{
int I ;
char tmp[255];
switch(wType)
{
case XTYP_ADVSTART:
case XTYP_CONNECT://請求連接;
return ((HDDEDATA)TRUE);//允許;
case XTYP_ADVDATA: //有數據到來;
for(I=0;I<NITEM;I++)
if(Item==hszItem[I])
{
DdeGetData(hData,(PBYTE)tmp,255,0);//取得數據;
switch(I)
{
case 0:
SetDlgItemText(hWnd,IDC_STATIC2,tmp);
break;
case 1:
SetDlgItemText(hWnd,IDC_STATIC3,tmp);
break;
}
}
return ((HDDEDATA)DDE_FACK);//回執;
case XTYP_ADVREQ:
case XTYP_REQUEST://數據請求;
for(I=0;I<NITEM;I++)
if(Item==hszItem[I])
return(DdeCreateDataHandle(idlnst,(PBYTE)(LPCTSTR)ServerData[I],
ServerData[I].GetLength()+1,0,Item,wFmt,0));
}
return(0);
}
///////////////////////////////////////////////////// CddedemoDlg.cpp
CDdedemoDlg::CDdedemoDlg(CWnd* pParent /*=NULL*/)
: CDialog(CDdedemoDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CDdedemoDlg)
m_edit = _T("");
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CDdedemoDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CDdedemoDlg)
DDX_Text(pDX, IDC_EDIT1, m_edit);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CDdedemoDlg, CDialog)
//{{AFX_MSG_MAP(CDdedemoDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_TIMER()
ON_WM_DESTROY()
ON_EN_CHANGE(IDC_EDIT1, OnChangeEdit1)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
//////////////////////////////////////////CDdedemoDlg message handlers
BOOL CDdedemoDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
hWnd=m_hWnd;
if (DdeInitialize(&idlnst,(PFNCALLBACK)DdeCallback,APPCMD_FILTERINITS|
CBF_FAIL_EXECUTES|CBF_SKIP_CONNECT_CONFIRMS|CBF_FAIL_SELFCONNECTIONS|
CBF_FAIL_POKES,0))
{
MessageBox("DDE SERVER初始化失敗!");
return FALSE;
}
hlnst=AfxGetApp()->m_hInstance;
//創建DDE string
hszApp=DdeCreateStringHandle(idlnst,szApp,0);
hszTopic=DdeCreateStringHandle(idlnst,szTopic,0);
for(int I=0;I<NITEM;I++)
hszItem[I]=DdeCreateStringHandle(idlnst,pszItem[I],0);
//注冊服務;
DdeNameService(idlnst,hszApp,0,DNS_REGISTER);
bConnect=FALSE;
SetTimer(1,1000,NULL);//開始定時;
return TRUE; // return TRUE unless you set the focus to a control
}
void CDdedemoDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CDdedemoDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}
// The system calls this to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CDdedemoDlg::OnQueryDragIcon()
{
return (HCURSOR) m_hIcon;
}
void CDdedemoDlg::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
count++;
ServerData[1].Format("%d",count);
SetDlgItemText(IDC_STATIC1,ServerData[1]);
DdePostAdvise(idlnst,hszTopic,hszItem[1]);//通知更新;
if(!bConnect)//如果沒有建立連接
{
hConv=DdeConnect(idlnst,hszApp,hszTopic,NULL);
//連接服務器端;
if(hConv) //如果建立成功
{
DWORD dwResult;
bConnect=TRUE;
for(int I=0;I<NITEM;I++)
DdeClientTransaction(NULL,0,hConv,hszItem[I],CF_TEXT,XTYP_ADVSTART,
TIMEOUT_ASYNC,&dwResult);
}
}
CDialog::OnTimer(nIDEvent);
}
void CDdedemoDlg::OnDestroy()
{
CDialog::OnDestroy();
// TODO: Add your message handler code here
KillTimer(1);//銷毀定時;
DdeNameService(idlnst,0,0,DNS_UNREGISTER);//注銷服務;
DdeFreeStringHandle(idlnst,hszApp);
DdeFreeStringHandle(idlnst,hszTopic);
for(int I=0;I<NITEM;I++)
DdeFreeStringHandle(idlnst,hszItem[I]);
DdeUninitialize(idlnst);
}
void CDdedemoDlg::OnChangeEdit1()
{
// TODO: If this is a RICHEDIT control, the control will not
// send this notification unless you override the CDialog::OnInitDialog()
// function and call CRichEditCtrl().SetEventMask()
// with the ENM_CHANGE flag ORed into the mask.
// TODO: Add your control notification handler code here
UpdateData();
ServerData[0]=m_edit;
DdePostAdvise(idlnst,hszTopic,hszItem[0]); //通知DDE更新該數據項目;
}
四、小結
Windows提供了很多方法來實現進程之間的通信,相互傳遞數據,如通過系統剪貼板方法、共享DLL方法、管道方法等,這些方法的存在保證了程序的健壯性和魯棒性(穩定性),有興趣的讀者可以自行參考有關資料。