C#进程间通讯技术-整理。
⼀、进程间通讯的⽅式
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)
{
  Box1.Text = Convert.ToString(e.KeyValue);
}
(3)编译运⾏,⽣成
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; //获取主窗⼝句柄
          int data = Convert.Box1.Text); //获取⽂本框数据
          SendMessage(hWnd, 0x0100, (IntPtr)data, (IntPtr)0); //点击该按钮,以⽂本框数据为参数,向Form1发送WM_KEYDOWN消息
        }
      }
    }
  }
}
3.启动可执⾏⽂件,弹出Form1窗体称为接受消息窗体。
启动可执⾏⽂件,在弹出的窗体中的⽂本框中输⼊任意数字,点击button1按钮,接受消息窗体textBox1即显⽰该数字。
到此结束。
⽅法⼆:IPC通讯机制Remoting
=======
最近⼀直纠结与使⽤多进程还是多线程来构建程序。多线程的⽅法似乎不错,但是⼀个进程可承受的线程数有有限的,并且由于每个线程都与UI有着些许关系,线程的⼯作⼤多数时间浪费在阻塞上了,效率实在不是很⾼。
笔者遂在google上搜索进程间通讯的⽅案。发现有很多种,其中IPC通道似乎是个不错的选择,⽀持本机的进程间通讯,可以作为备选⽅案之⼀,下⾯介绍以下基本的编程⽅法,以作备忘。
⾸先建⽴⼀个IPC通讯中使⽤的对象,其中MarshalByRefObject 是必须的
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;
}
}
}
接着建⼀个服务端控制台程序
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" );
}
}
}
最后是客户端控制台程序
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        }
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函数实现。
转载来源:
⽣活不⽌眼前的苟且,还有诗和远⽅。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。