C# 進程間通訊


擴展閱讀:http://www.cnblogs.com/joye-shen/archive/2012/06/16/2551864.html

 

一、進程間通訊的方式

1)共享內存

包括:內存映射文件,共享內存DLL,剪切板。

2)命名管道及匿名管道

3)消息通訊

4)利用代理方法。例如SOCKET,配置文件,注冊表方式。

等方式。

方法一:通訊。

進程間通訊的方式有很多,常用的有共享內存(內存映射文件、共享內存DLL、剪切板等)、命名管道和匿名管道、發送消息等幾種方法來直接完成,另外還可以通過socket口、配置文件和注冊表等來間接實現進程間數據通訊任務。以上這幾種方法各有優缺點,具體到在進程間進行大數據量數據的快速交換問題上,則可以排除使用配置文件和注冊表的方法;另外,由於管道和socket套接字的使用需要有網卡的支持,因此也可以不予考慮。這樣,可供選擇的通訊方式只剩下共享內存和發送消息兩種。

二、發送消息實現進程間通訊前准備

下面的例子用到一個windows api 32函數

[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr wnd,int msg,IntPtr wP,IntPtr lP);

要有此函數,需要添加using System.Runtime.InteropServices;命名空間

此方法各個參數表示的意義

wnd:接收消息的窗口的句柄。如果此參數為HWND_BROADCAST,則消息將被發送到系統中所有頂層窗口,包括無效或不可見的非自身擁有的窗口、被覆蓋的窗口和彈出式窗口,但消息不被發送到子窗口。

msg:指定被發送的消息類型。

wP:消息內容。

lP:指定附加的消息指定信息。

用api參考手冊查看SendMessage用法時,參考手冊則提示

SendMessage與PostMessage之間的區別:SendMessage和PostMessage,這兩個函數雖然功能非常相似,都是負責向指定的窗口發送消息,但是SendMessage() 函數發出消息后一直等到接收方的消息響應函數處理完之后才能返回,並能夠得到返回值,在此期間發送方程序將被阻塞,SendMessage() 后面的語句不能被繼續執行,即是說此方法是同步的。而PostMessage() 函數在發出消息后馬上返回,其后語句能夠被立即執行,但是無法獲取接收方的消息處理返回值,即是說此方法是異步的。

三、發送消息實現進程間通訊具體步驟

1.新建windows應用程序

(1)打開VS2008,新建一個“windows 應用程序”,主窗口為Form1,項目名稱:ProcessCommunication
(2)在Form1上添加一個標簽為textBox1的文本框,並為Form1添加KeyDown事件,當Form1接收到KewDown消息時,將接收到的數據顯示在label1上。

public Form1()
{
InitializeComponent();

this.KeyDown+=new KeyEventHandler(Form1_KeyDown);

}

private void Form1_KeyDown(object sender, KeyEventArgs e)
{
this.textBox1.Text = Convert.ToString(e.KeyValue);
}
(3)編譯運行,生成ProcessCommunication.exe

2.新建windows應用程序

 

 

(1)打開VS2008,新建一個“windows 應用程序”,主窗口為Form1,項目名稱:ProcessCommunication1,
並在Form1上添加一個按鈕和一個文本框

namespace ProcessCommunication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//Win32 API函數:
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr wnd,int msg,IntPtr wP,IntPtr lP);

private void button1_Click(object sender, EventArgs e)
{
Process[] pros = Process.GetProcesses(); //獲取本機所有進程
for (int i = 0; i < pros.Length; i++)
{
if (pros[i].ProcessName == "ProcessCommunication") //名稱為ProcessCommunication的進程

{
IntPtr hWnd = pros[i].MainWindowHandle; //獲取ProcessCommunication.exe主窗口句柄
int data = Convert.ToInt32(this.textBox1.Text); //獲取文本框數據
SendMessage(hWnd, 0x0100, (IntPtr)data, (IntPtr)0); //點擊該按鈕,以文本框數據為參數,向Form1發送WM_KEYDOWN消息
}

}

}
}

3.啟動ProcessCommunication.exe可執行文件,彈出Form1窗體稱為接受消息窗體。

啟動ProcessCommunication1.exe可執行文件,在彈出的窗體中的文本框中輸入任意數字,點擊button1按鈕,接受消息窗體textBox1即顯示該數字。

到此結束。

 

方法二:IPC通訊機制Remoting

=======

 

最近一直糾結與使用多進程還是多線程來構建程序。多線程的方法似乎不錯,但是一個進程可承受的線程數有有限的,並且由於每個線程都與UI有着些許關系,線程的工作大多數時間浪費在阻塞上了,效率實在不是很高。

筆者遂在google上搜索進程間通訊的方案。發現有很多種,其中IPC通道似乎是個不錯的選擇,支持本機的進程間通訊,可以作為備選方案之一,下面介紹以下基本的編程方法,以作備忘。

首先建立一個IPC通訊中使用的對象,其中MarshalByRefObject 是必須的

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
 
namespace Ipctest
{
     public class test:MarshalByRefObject
     {
         private int iCount = 0;
         public int count()
         {
             iCount++;
             return iCount;
         }
 
         public int Add( int x)
         {
             iCount += x;
             return iCount;
         }
     }
}

接着建一個服務端控制台程序

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
 
namespace Ipctest
{
     class Program
     {
         static void Main( string [] args)
         {
            IpcChannel serverchannel = new IpcChannel( "testchannel" );
             ChannelServices.RegisterChannel(serverchannel, false );
             RemotingConfiguration.RegisterWellKnownServiceType( typeof (test), "test" , WellKnownObjectMode.Singleton);
             Console.WriteLine( "press Enter to exit" );
             Console.ReadLine();
             Console.WriteLine( "server stopped" );
         }
     }
}

最后是客戶端控制台程序

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
 
namespace Ipctest
{
     class Program
     {
         static void Main( string [] args)
         {
             IpcChannel tcc = new IpcChannel();
             ChannelServices.RegisterChannel(tcc, false );
             WellKnownClientTypeEntry remotEntry = new WellKnownClientTypeEntry( typeof (test), "ipc://testchannel/test" );
             RemotingConfiguration.RegisterWellKnownClientType(remotEntry);
 
             test st = new test();
             Console.WriteLine( "{0},{1}" ,st.count(),st.Add(1));
             Console.ReadLine();
         }
     }
}

在測試的過程中會發現第一次調用客戶端輸出結果:

1,2

第二次輸出結果

3,4

……

結果是比較符合要求的。

方法三:管道

 

 

最近在做一個數據庫同步軟件.!!

程序 服務端為 一個winform + windows Service 二大模塊.!

由於程序功能的需求. 需要winform 與windows Service進程通訊. 因此使用了 命名管道 來實現功能需求.!

 

以此記下筆記 , 並付上一Demo

有關 NamedPipeServerStream  類 官方MSDN文檔說明

服務端主要代碼
 1 NamedPipeServerStream pipeServer = new NamedPipeServerStream("testpipe", PipeDirection.InOut, 4, PipeTransmissionMode.Message, PipeOptions.Asynchronous);
2
3 void Form1_Load(object sender, EventArgs e)
4 {
5 ThreadPool.QueueUserWorkItem(delegate
6 {
7 pipeServer.BeginWaitForConnection((o) =>
8 {
9 NamedPipeServerStream server = (NamedPipeServerStream)o.AsyncState;
10 server.EndWaitForConnection(o);
11 StreamReader sr = new StreamReader(server);
12 StreamWriter sw = new StreamWriter(server);
13 string result = null;
14 string clientName = server.GetImpersonationUserName();
15 while (true)
16 {
17 result = sr.ReadLine();
18 if (result == null || result == "bye")
19 break;
20 this.Invoke((MethodInvoker)delegate { lsbMsg.Items.Add(clientName+" : "+result); });
21 }
22 }, pipeServer);
23 });
24 }



有關 NamedPipeClientStream 類 官方MSDN文檔說明

客戶端主要代碼
 1   NamedPipeClientStream pipeClient =
2 new NamedPipeClientStream("192.168.1.100", "testpipe",
3 PipeDirection.InOut, PipeOptions.Asynchronous,
4 TokenImpersonationLevel.None);
5 StreamWriter sw = null;
6 void Form2_Load(object sender, EventArgs e)
7 {
8 pipeClient.Connect();
9 sw = new StreamWriter(pipeClient);
10 sw.AutoFlush = true;
11 }
12
13 private void button1_Click_1(object sender, EventArgs e)
14 {
15 sw.WriteLine(textBox1.Text);
16 }

 

 

經發現,命名管道, 其實是基於TCP/IP 來連接. 且端口為 445

 

當然, 我這里只是 傳輸一個字符串做為信息而已.! 其實仍然 可以傳輸自己所定義的 對象 等.(記得序列化喲..)

源碼

方法四:共享內存

 

 

次發了利用發消息實現的C#進程間的通訊,這次又使用共享內存了,他們應用范圍是不同的,共享內存適用於共享大量數據的情況。

復制代碼
const int INVALID_HANDLE_VALUE = -1;
const int PAGE_READWRITE = 0x04;
  //共享內存
  [DllImport("Kernel32.dll",EntryPoint="CreateFileMapping")]
  private static extern IntPtr CreateFileMapping(IntPtr hFile, //HANDLE hFile,
   UInt32 lpAttributes,//LPSECURITY_ATTRIBUTES lpAttributes,  //0
   UInt32 flProtect,//DWORD flProtect
   UInt32 dwMaximumSizeHigh,//DWORD dwMaximumSizeHigh,
   UInt32 dwMaximumSizeLow,//DWORD dwMaximumSizeLow,
   string lpName//LPCTSTR lpName
   ); 

  [DllImport("Kernel32.dll",EntryPoint="OpenFileMapping")]
  private static extern IntPtr OpenFileMapping(
   UInt32 dwDesiredAccess,//DWORD dwDesiredAccess,
   int bInheritHandle,//BOOL bInheritHandle,
   string lpName//LPCTSTR lpName
   ); 

  const int FILE_MAP_ALL_ACCESS = 0x0002;
  const int FILE_MAP_WRITE = 0x0002; 

  [DllImport("Kernel32.dll",EntryPoint="MapViewOfFile")]
  private static extern IntPtr MapViewOfFile(
   IntPtr hFileMappingObject,//HANDLE hFileMappingObject,
   UInt32 dwDesiredAccess,//DWORD dwDesiredAccess
   UInt32 dwFileOffsetHight,//DWORD dwFileOffsetHigh,
   UInt32 dwFileOffsetLow,//DWORD dwFileOffsetLow,
   UInt32 dwNumberOfBytesToMap//SIZE_T dwNumberOfBytesToMap
   ); 

  [DllImport("Kernel32.dll",EntryPoint="UnmapViewOfFile")]
  private static extern int UnmapViewOfFile(IntPtr lpBaseAddress); 

  [DllImport("Kernel32.dll",EntryPoint="CloseHandle")]
  private static extern int CloseHandle(IntPtr hObject);
 然后分別在AB兩個進程中定義如下兩個信號量及相關變量;
 

  private Semaphore m_Write;  //可寫的信號
  private Semaphore m_Read;  //可讀的信號
  private IntPtr handle;     //文件句柄
  private IntPtr addr;       //共享內存地址
  uint mapLength;            //共享內存長
復制代碼
復制代碼
定義這兩個信號量是為讀寫互斥用的。
在A進程中創建共享內存:
 

m_Write = new Semaphore(1,1,"WriteMap");
m_Read = new Semaphore(0,1,"ReadMap");
mapLength = 1024;
IntPtr hFile = new IntPtr(INVALID_HANDLE_VALUE);   
handle = CreateFileMapping(hFile,0,PAGE_READWRITE,0,mapLength,"shareMemory");
addr = MapViewOfFile(handle,FILE_MAP_ALL_ACCESS,0,0,0);
 
然后再向共享內存中寫入數據:
 

m_Write.WaitOne();
byte[] sendStr = Encoding.Default.GetBytes(txtMsg.Text + '/0');
//如果要是超長的話,應另外處理,最好是分配足夠的內存
if(sendStr.Length < mapLength)
      Copy(sendStr,addr);
m_Read.Release();
 

這是在一個單獨的方法中實現的,可多次調用,但受信號量的控制。其中txtMsg是一個文本框控件,實際中可用任意字符串,加最后的'/0'是為了讓在共享內存中的字符串有一個結束符,否則在內存中取出時是以'/0'為准的,就會出現取多的情況。
Copy方法的實現如下:
 

static unsafe void Copy(byte[] byteSrc,IntPtr dst)
  {
   fixed (byte* pSrc = byteSrc)
   {
    byte* pDst = (byte*)dst;
    byte* psrc = pSrc;
    for(int i=0;i<byteSrc.Length;i++)
    {
     *pDst = *psrc;
     pDst++;
     psrc ++;
    }
   }
  }
 注意unsafe 關鍵字,在編譯時一定要打開非安全代碼開關。
最后不要忘了在A進程中關閉共享內存對象,以免內存泄露。
 

   UnmapViewOfFile(addr);
   CloseHandle(handle);
 
要在B進程中讀取共享內存中的數據,首先要打開共享內存對象:
 

m_Write = Semaphore.OpenExisting("WriteMap");
m_Read = Semaphore.OpenExisting("ReadMap");
handle = OpenFileMapping(0x0002,0,"shareMemory");
 讀取共享內存中的數據:
 

   m_Read.WaitOne();
   string str = MapViewOfFile(handle,FILE_MAP_ALL_ACCESS,0,0,0);
   txtMsg.Text = str;
   m_Write.Release();
 這里獲取了字符串,如果要獲取byte數組,請參考上面的Copy函數實現。
復制代碼
原文轉至:http://blog.csdn.net/wlanye/article/details/8552150


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM