windows编程原理
程序的入口地址
计算机从内存读取指令顺次执行。存储数据的内存首地址即是变量的指针,函数代码段在内存的首地址编号称为函数地址,也叫函数指针。C++语言编写的窗体程序是其入口函数名为_tWinMain,C#愿意那边写的窗体程序其入口函数是在Program.cs 文件中的main函数。通过地址直接访问内存便捷的同时也带来了巨大的风险,不合理或非法的地址访问给计算机带了巨大的破坏,直接通过地址访问内存是非常危险的,.NET平台避免直接访问内存地址。
程序进程和线程
资源是程序运行的基础。
OS按需创建和分配资源并负责资源的回收和再分配。
进程与程序不同,它是程序执行时的标志,不仅是程序的静态版本。
用户创建一个进程后,操作系统就将程序的一个副本装入计算机中,然后启动一个线程执行该程序。
操作系统中的进程与用户进程并发运行,用户进程是由操作系统创建和调用的
用户进程也可以创建和调用别的进程
被创建的进程与创建者就构成了父子关系。
32位的Windows系统中每个进程有4GB的虚地址空间,一个以上线程对象,他们共享进程的虚地址空间。
程序时编译成功的静态的二进制文件,进程是程序在OS中的一次运行是程序的复制品,一个程序可执行多次产生多个进程实例。进程是非常复杂的对象,随运行动态变化,进程共享计算机资源、互相通信。OS管理多个进程的运行,分配与释放资源,提供进程间的通信服务,进程的启动与终结。
Windows平台的进程是分配和拥有资源的单位,线程是最小的执行单位。
线程划分位主线程与工作线程,控制台线程或窗体线程都可以是主线程,OS先启动主线程,主线程再启动工作线程,工作线程与主线程并发地在机器中运行。
Windows是一个多任务的系统,它能够同时运行多个程序,其中的每一个正在运行的程序就称为一个“进程”。
对于同一个进程,又可以分成若干个独立的执行流,这样的流则被称为“线程”。线程是操作系统向其分配处理器时间的基本单位,它可以独立占用处理器的时间片,同一进程中的线程可以共享其进程的资源和内存空间。每一个进程至少包含一个线程。
进程是计算机分配资源的单位,线程是运行调度单位。
进程中的线程也具有线程控制块,包含内容有所属进程ID,创建和退出时间,线程启动地址等
进程和线程技术的引入,为实现系统或应用程序的并行性提供了重要的技术基础。
“并发”,是指系统或应用程序在某一时间段内同时处理多个事务的运行过程。对于单处理器的计算机系统来说,由于单个CPU在任何时刻只能执行一个线程,所以,这种计算机系统的并发,实际上是通过操作系统在各个正在执行的线程之间切换CPU,以分时处理的方式实现表面形式上的并发,只是因为其切换的速度快且处理能力强时,用户直观感觉不到而已。
当然,对于多处理器的计算机系统,其多个CPU之间既有相互协作,又有独立分工,所以,它们在各自执行一个相应线程时可以互不影响,同时进行,实现并行处理。
进程
创建进程过程
- 打开文件映像(.exe)。
- 创建windows进程对象。
- 创建初始线程对象,包括上下文,堆栈。
- 通知内核系统为进程运行作准备。
- 执行初始线程。
- 导入需要的DLL,初始化地址空间,由程序入口地址开始执行进程。
进程的创建与启动代码-c#
C#的System.Diagnostics命名空间下的Process类专门用于完成系统的进程管理任务,通过实例化一个Process类,就可以启动一个独立进程。
Process cmdP = new Process();
cmdP.StartInfo.FileName = "cmd.exe";
cmdP.StartInfo.CreateNoWindow = true;
cmdP.StartInfo.UseShellExecute = false;
cmdP.StartInfo.RedirectStandardOutput = true;
cmdP.StartInfo.RedirectStandardInput = true;
cmdP.Start();
ProcessStartInfo类,则可以为Process定制启动参数
比如RedirectStandardInput、RedirectStandardOutput、RedirectStandardError,分别重定向了进程的输入、输出、错误流
进程的其它操作-c#
打开应用程序
- [ ] Process.Start("calc"); //计算器
Process.Start("mspaint"); // 画图工具
Process.Start("notepad");// 记事本
Process.Start("iexplore","http://www.baidu.com");
关闭应用程序
// 得到程序中所有正在运行的进程
Process[] preo = Process.GetProcesses();
foreach (var item in preo){
Console.WriteLine(item);
item.Kill(); //杀死进程
}
进程间通信机制简介
自计算程序诞生起,进程在运行的时候就有与其它进程通信的需求,windows 操作系统提供进程间数据共享和通信的机制,称为 interprocess communications (IPC),IPC经常使用C/S模式。
高级通信(IPC)
传输的数据量大,超过几十个字节
低级通信(同步控制)
传输的数据量小,少于数个字节,或仅是位单位
进程间通信方法分类
1.共享内存(剪贴板、COM、DLL、DDE、文件映射)
2.消息WM_COPYDATA
3. 邮槽
4. 管道,分有名管道与无名管道、进程重定向
5. Windows套接字
6. NetBIOS特殊的网络应用
IPC需要考虑内容
- 进程是否会通过网络与其它机器上的进程通信,仅使用本机通信机制是否满足应用需求;
- 通信中的进程是否是处于不同的操作系统平台例如Windows与UNIX平台;
- 有些进程通信机制是只用于图形化窗体界面的,而不适用于控制台程序;
- 通信目的是用于同步控制还是数据的传送;
- 数据传输量考虑;
IPC是否需要网络
发送消息实现进程通讯:SendMessage?PostMessage
#region 定义结构体
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;
}
#endregion
[DllImport("User32.dll", EntryPoint = "SendMessage")]private static extern int SendMessage(IntPtr wnd,int msg,IntPtr wP,IntPtr lP);
或
[DllImport("User32.dll", EntryPoint = "SendMessage")]private static extern int SendMessage(IntPtr wnd,int msg,IntPtr wP, ref COPYDATASTRUCT lParam);
[DllImport("User32.dll", EntryPoint = “PostMessage")]private static extern int PostMessage(IntPtr wnd,int msg,IntPtr wP, ref COPYDATASTRUCT lParam);
SendMessage和PostMessage,这两个函数虽然功能非常相似,都是负责向指定的窗口发送消息,
但是SendMessage() 函数发出消息后一直等到接收方的消息响应函数处理完之后才能返回,并能够得到返回值,在此期间发送方程序将被阻塞,SendMessage() 后面的语句不能被继续执行,即是说此方法是同步的。
而PostMessage() 函数在发出消息后马上返回,其后语句能够被立即执行,但是无法获取接收方的消息处理返回值,即是说此方法是异步的
只能由SendMessage()来发送,而不能使用PostMessage()。
因为系统必须管理用以传递数据的缓冲区的生命期,如果使用了PostMessage(),数据缓冲区会在接收方(线程)有机会处理该数据之前,就被系统清除和回收。此外如果lpData指向一个带有指针或某一拥有虚函数的对象时,也要小心处理。
如果传入的句柄不是一个有效的窗口或当接收方进程意外终止时,SendMessage()会立即返回,因此发送方在这种情况下不会陷入一个无穷的等待状态中
进程重定向实现进程通讯
概述
普通进程从键盘接收输入,输出到屏幕
使用文件作为进程的输入称为输入重定向
使用重定向符方法
dir > list.txt
cmd >> file
cmd < file
进程重定向意义
调用控制台进程
Ping远程主机
获取MAC地址getmac
关机shutdown
服务管理
重定向的两种方式
- 同步
Process process = new Process();
process.StartInfo.FileName = "cmd.exe";
// 是否使用外壳程序
process.StartInfo.UseShellExecute = false;
// 是否在新窗口中启动该进程的值
process.StartInfo.CreateNoWindow = true;
// 重定向输入流
process.StartInfo.RedirectStandardInput = true;
// 重定向输出流
process.StartInfo.RedirectStandardOutput = true;
//使ping命令执行九次
string strCmd = "ping www.whu.edu.cn -n 9"; process.Start();
process.StandardInput.WriteLine(strCmd);
process.StandardInput.WriteLine("exit");
// 获取输出信息
textBox2.Text = process.StandardOutput.ReadToEnd();
process.WaitForExit();
process.Close();
- 造成窗体没有响应的问题
- 不得在窗体线程中构造耗时的操作
- 窗体控件时间都属于窗体线程
特殊的BackGroundWorker控件 - 造成窗体没有响应的"问题"
- 得在窗体线程中构造耗时的操作
- 窗体控件事件函数都属于窗体线程
- 异步方式
重定向回调函数
回调函数编写与设置 窗体消息处理函数重载
管道机制
定义
管道机制是计算机提供的一种进程间通信 (IPC) 方式,由操作系统创建管道对象,发送进程可以向管道写入数据,接收进程由管道中读出数据。管道还可进行跨计算机的通信,可使用网络,也可使用文件等,它屏蔽低层实现机制提供给进程通信机制。有两种形式管道,有名管道与无名管道。
类
AnonymousPipeClientStream
AnonymousPipeServerStream
NamedPipeClientStream
NamedPipeServerStream
命名管道通信模式
- 字节模式
- 消息模式
- 管道通信程序示例
线程
线程创建过程
- 在进程的地址空间中为线程创建用户态堆栈。
- 初始化线程硬件上下文。
- 创建线程对象。
- 通知内核系统为线程运行准备。
- 新创建线程handle和线程ID值返回到调用者。
- 线程进入调度准备执行。
线程状态
- 初始化–线程处于创始中。
- 就绪–等待由CPU执行。
- 待命–只能由一个线程处于待命状态,离执行状态最近。
- 运行–在CPU的当前时间片内执行。
- 等待–线程同步需要等待,
- 接转–准备执行,但是它的内核堆栈不在内存,需要内存页面调入,调入后进入就绪状态。
- 终止–线程执行完。
线程的生命期
工作线程的结束
- 线程正常结束
自动消亡,OS清理 - 线程非正常结束,被KILL
os无法控制,造成系统损失或破坏 - 控制线程正常终止的方法
低级事件对象ManualResetEvent
线程非正常结束的后果
内存无法回收-内存泄漏
文件缓冲没写入-文件被破坏
文件句柄未回收-被占用
共享资源的占用(网络端口,管道,DLL)
线程的创建与启动代码
线程执行代码的编写
void workThread(){
}
设定函数名为线程入口
ThreadStart s = new ThreadStart(workThread);
线程委托对象(委托的实质是函数指针或叫函数地址)
Thread thread1=new Thread(s);
设定线程优先级等属性
线程启动 thread1.Start();
线程参数传递 thread1.Start(paraObject);
C#的System.Threading命名空间下的Thread类和ThreadStart类用于完成的线程创建和管理
使用Thread类创建线程时,只需要提供线程入口,线程入口告诉程序让这个线程做什么。通过实例化一个Thread类的对象就可以创建一个线程。创建新的Thread对象时,将创建新的托管线程。Thread类接收一个ThreadStart委托或ParameterizedThreadStart委托的构造函数,该委托包装了调用Start方法时由新线程调用的方法
//创建无参数方法的托管线程
//创建线程
Thread thread1=new Thread(new ThreadStart(method));
//启动线程
thread1.Start();
//定义无参方法
static void method() {
Console.WriteLine(“这是无参的静态方法");
}
class ThreadTest
{
public void MyThread()
{
Console.WriteLine(“这是一个实例方法”);
}
}
ThreadTest test=new ThreadTest();
//创建线程
Thread thread2=new Thread(new ThreadStart(test.MyThread()));
//启动线程
thread2.Start();
还可以通过匿名委托或Lambda表达式来创建线程
//通过匿名委托创建
Thread thread1 = new Thread(delegate() { Console.WriteLine(“我是通过匿名委托创建的线程”); });
thread1.Start();
//通过**Lambda**表达式创建
Thread thread2 = new Thread(() => Console.WriteLine("我是通过Lambda表达式创建的委托"));
thread2.Start();
还可以利用有参的委托ParameterizedThreadStart来创建线程
class Program
{
static void Main(string[] args)
{
//通过ParameterizedThreadStart创建线程
Thread thread = new Thread(new ParameterizedThreadStart(Thread1));
//给方法传值
thread.Start(“这是一个有参数的委托”);
Console.ReadKey();
}
/// 创建有参的方法,方法里面的参数类型必须是Object类型
static void Thread1(object obj)
{
Console.WriteLine(obj);
}
}
线程的其它操作-c# System.Threading.Thread的方法
线程的常用属性
前台线程与后台线程
前台线程:只有所有的前台线程都结束,应用程序才能结束。默认情况下创建的线程都是前台线程
后台线程:只要所有的前台线程结束,后台线程自动结束。
通过Thread.IsBackground设置后台线程。
且必须在调用Start方法之前设置线程的类型,否则一旦线程运行,将无法改变其类型
一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。
而前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序,或是定时对某些系统资源进行扫描的程序
线程的优先级与线程调度
windows中的线程按照优先级进行调度
具有最高优先权的线程一直被执行
相同优先级的线程 按时间片轮转执行,时间片在windows XP系统中相当于2个时钟周期。
当更高优先级的线程就绪时,高优先的线程会抢占执行低优先级的线程。
多线程出现原因和优点
多线程出现原因
1、CPU运行速度太快,硬件处理速度跟不上,所以操作系统进行分时间片管理。这样,从宏观角度来说是多线程并发的,因为CPU速度太快,察觉不到,看起来是同一时刻执行了不同的操作。但是从微观角度来讲,同一时刻只能有一个线程在处理。
2、目前电脑都是多核多CPU的,一个CPU在同一时刻只能运行一个线程,但是多个CPU在同一时刻就可以运行多个线程
多线程的优点:
线程机制使可以同时完成多个任务;可以使程序的响应速度更快;可以让占用大量处理时间的任务或当前没有进行处理的任务定期将处理时间让给别的任务;可以随时停止任务;可以设置每个任务的优先级以优化程序性能
程序具有异步执行能力以充分发挥机器计算能力,程序还可以利用其他计算机的处理能力。
合理的线程分工使得数据计算与用户交互得到均衡。
线程应用场合
- 网络通信程序。
- 与Web服务器和数据库操作。
- 执行占用大量时间的操作。
- 有不同优先级的任务。
- 用户响应效能与数据运算均衡。
线程缺点
线程上下文信息消耗计算机资源。
线程上下文切换过程,线程会带来资源特殊要求和潜在冲突。如果线程过多,系统管理线程的负担会加大,则其中大多数线程都不会产生明显的进度。
线程控制代码非常复杂,并可能产生许多bug。
线程的非正常终结会造成资源浪费影响系统的运行性能
线程跨域访问
界面中的控件(textBox1等)是由主线程创建的,thread线程是另外创建的一个线程,在.NET上执行的是托管代码,C#强制要求这些代码必须是线程安全的,即不允许跨线程访问Windows窗体的控件;
在遵守.NET安全标准的前提下,实现从一个线程成功地访问另一个线程创建的空间,要使用C#的方法回调机制C#的方法回调机制,也是建立在委托基础上的,下面给出它的典型实现过程。
线程同步与异步调用
线程的异步与同步实例
- 同步方法执行是有序的,异步方法执行是无序的
- 异步方法无序包括启动无序和结束无序
启动无序是因为同一时刻向操作系统申请线程,操作系统收到申请以后,返回执行的顺序是无序的,所以启动是无序的
结束无序是因为虽然线程执行的是同样的操作,但是每个线程的耗时是不同的,所以结束的时候不一定是先启动的线程就先结束 - 同步方法由于主线程忙于计算,所以会卡住界面。
- 异步方法由于主线程执行完了,其他计算任务交给子线程去执行,所以不会卡住界面,用户体验性好。
- 同步方法由于只有一个线程在计算,所以执行速度慢。
- 异步方法由多个线程并发运算,所以执行速度快,但并不是线性增长的(资源可能不够)。多线程也不是越多越好,只有多个独立的任务同时运行,才能加快速度。
如何解决线程的异步无序问题?
使用回调来解决异步线程的无序问题
在BeginInvoke的参数中指定回调函数
线程间同步模式
工作线程可以很容易用SendMessage来发消息
窗体线程可以发送ManualResetEvent事件给工作线程
工作线程间的通信
WaitOne方法等待当前事件(信号)有效
WaitAny方法等待事件(信号)数组中任一事件有效,对应或关系实现同步
WaitAll方法等待事件(信号)数组中所有事件有效,对应与关系实现同步
工作线程响应前打发时间的两种方式
采用IsOut + Sleep打发时间的方式
ManualResetEvent.WaitOne打发时间的方式
事件对象可实现并发执行中的前趋控制。当线程调用Wait方法时,如果等待对象状态没有激活,则调用线程暂停。对象被激活则线程继续执行。
线程同步与死锁
多个线程对共享资源访问会造成冲突。为了避免冲突,必须对共享资源进行同步或控制对共享资源的访问。如果在相同或不同的应用程序域中未能正确地使访问同步,则会导致出现一些问题,这些问题包括死锁和争用条件等,其中死锁是指两个线程都停止响应,并且都在等待对方完成;争用条件是指由于意外地出现对两个事件的执行时间的临界依赖性而发生反常的结果。
需要同步的资源
- 系统资源(如通信端口)。
- 多个进程所共享的资源(如文件句柄)。
- 由多个线程访问的单个应用程序域的资源(如全局、静态和实例字段)。
互斥量Mutex
最多只能有一个线程可以获取并拥有它,它适合不同线程对同一共享资源互斥访问的应用场合,例如对全局变量,同一个文件或者是数据库同一个对象的访问等,设置程序先获得互斥量再访问共享资源。
线程可调用多次的 WaitOne 方法重复对其所有,使用 ReleaseMutex 方法释放对互斥量所属权,而每一个成功的 WaitOne 方法对应一次 ReleaseMutex。
WaitOne方法不仅等待互斥量的状态,还使线程拥有它。
互斥量最好不要使用WaitAny,WaitAll方法
线程运行终止 mutex 被放弃,互斥量仍可被其它线程取得所属权,但会获得 AbandonedMutexException 异常。