C# 進程間通信

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

 

1、進程間通信的方式html

1)共享內存數據庫

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

2)命名管道及匿名管道windows

3)消息通信api

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

等方式。安全

方法一:通信。多線程

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

2、發送消息實現進程間通信前準備

下面的例子用到一個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() 函數在發出消息後立刻返回,其後語句可以被當即執行,可是沒法獲取接收方的消息處理返回值,便是說此方法是異步的。

3、發送消息實現進程間通信具體步驟

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
相關文章
相關標籤/搜索