Windows软件开发中的注意事项

原创 2005年02月26日 17:01:00

----------------------------------------------------------------------------------------------------------
// define _MT so that _beginthread( ) is available
#ifndef _MT
#define _MT
#endif

#include <stdio.h>
#include <windows.h>
#include <process.h>
#include "resource.h"
以上是关于如何使用_beginthread()
-----------------------------------------------------------------------------------------------------------

1。字符集:
 软件的本地化要解决的真正问题,实际上就是如何来处理不同的字符集。

2。单字节与双字节字符集:
 ASCII --- DBCS

3.Unicode: 宽字节字符集

 Unicode字符串中的所有字符都是16位(两个字节)的。

注意:所有的Unicode函数都是以wcs开头,wcs是宽字符串的英文缩写。若要调用Unicode函数,只要用前缀wcs来取代ANSI字符串函数的前缀str即可。

 _UNICODE 宏用于C运行期头文件
  UNICODE 宏用于Windows头文件
 当编译源代码模块时,通常是必须同时定义这两个宏

...称为符合ANSI和Unicode的应用程序:
 .将文本串视为字符数组,而不是chars数组或字节数组
 .将通用数据类型(如TCHAR和PTSTR)用于文本字符和字符串
 .将显式数据类型(如BYTE和PBYTE)用于字节、字节指针和数据缓存
 .将TEXT宏用于原义字符和字符串
 .执行全局性替换(例如将PTSTR替换PSTR)
----------------------------------------------------------------------------------------

注意:

1.
C语言本身没有输入输出语句,输入和输出的操作是由库函数实现的,比如scanf和printf等函数。
C对输入输出实行"函数化"。

2.
C语言中的标示符的长度(字符个数)无统一的规定,随系统不同而不同。许多系统(IBM PC 的 MS C)
取8个字符。因此,写程序时应了解所用系统对标示符长度的规定,以免错误和混肴,特别注意这种
错误编译时无语法错误,但是执行时的结果不对或者是错误的。

3.典型问题:

#include <stdio.h>
void main()
{
 int i = 3;
 int sum = (i++)+(i++)+(i++);
 printf("i= %d, sum= %d", i, sum);
}

结果为 i=6 sum=9
因为i首先进行合并使得sum等于9,再进行自加使得i等于6

如果是 sum = (++i)+(++i)+(++i); 结果sum=18,而i=6
因为首先是i的三次自加得6,然后再合并等于18。

4. continue 和 break

continue: 跳过本次循环,接着进行下一次是否执行循环的判定.

break: 结束循环,不再进行条件判断
-----------------------------------------------------------------------------------------

基本算术运算符:

+ 5+6->11    13.2+17.8->21.0

- 18-4->14    15.18-21.26->-6.11

* 2*3->6    2.0*3.0->6.0

/ 1/2->0   1.0/2.0->0.5

%(求余) -5%3->-2    10%5->0


位运算:

1."按位与"运算符: &

 参加运算的两个运算量,如果两个相应的位都为1,则该位的结果值为1,否则为0。如:

  0&0 = 0; 0&1 = 0; 1&0 = 0; 1&1 = 1;

 例如: 3 & 5 并不等于8,先把3和5以补码表示,再进行按位与运算:

  3的补码: 00000011
  5的补码: 00000101
  ------------------
        &: 00000001

  故而,3 & 5 的值为1。

2."按位或"运算符: |

 两个相应位中只要有一个为1,该位的结果值为1.即:
 
  0|0 = 0; 0|1 = 1; 1|0 = 1; 1|1 = 1;

3."异或"运算符: ^

 参加运算的两个相应位同号,则结果为0(假),异号则为1(真)

  0^0 = 0; 0^1 =1; 1^0 = 1; 1^1 = 0; 

4."取反"运算符: ~

 是一个单目运算符,用来对一个二进制数按位取反,即将0变1,将1变0。

5.左移运算符: <<
 
 用来将一个数的各二进位全部左移若干位.
 
  a = a << 2; 将二进制数左移2位,右补0.
  
  若a=15,即:00 00 11 11, 左移2位得 00 11 11 00,即十进制数60

  高位左移后溢出,舍弃不起作用

6.右移运算符: >>

 移到右端的低位被舍弃,对无符号数,高位补0.

  注意: 不同长度的数据进行位运算的时候,系统会将二者按右端对齐.

    
常用基本数据类型描述

类型   说明   长度(字节)  表示范围  

char   字符型   1   -128~127(-2的7次方~2的7次方减1)

unsigned char    无符号字符型  1   0~255(0~2的7次方减1)

signed char  有符号字符型  1   -128~127

int   整型   2   -32768~32767(-2的15次方~2的15次方减1)

unsigned int  无符号整型  2   0~65535(0~2的16次方减1)

signed int  有符号整型  2   -32768~32767

short int   短整型   2   -32768~32767

unsigned short int 无符号短整型  2   0~65535

signed short int 有符号短整型  2   -32768~32767

long int  长整形   4   -2的31次方~2的31次方减1

signed long int  有符号长整形  4   -2的31次方~2的31次方减1

unsigned long int 无符号长整形  4   0~2的32次方减1

float   浮点型   4   -3.4*10的38次方~3.4*10的38次方(7位有效数字)

double   双精度型  8   -1.7*10的308次方~1.7*10的308次方(15位有效数字)

long double  长双精度型  10   -3.4*10的4932次方~1.1*10的4932次方(19位有效数字) 
-------------------------------------------------------------------------------------------------------------
Data Types
This topic lists the data types most commonly used in the Microsoft Foundation Class Library. Most of the data types are exactly the same as those in the Windows Software Development Kit (SDK), while others are unique to MFC.

Commonly used Windows SDK and MFC data types are as follows:

BOOL   A Boolean value.


BSTR   A 32-bit character pointer.


BYTE   An 8-bit integer that is not signed.


COLORREF   A 32-bit value used as a color value.


DWORD   A 32-bit unsigned integer or the address of a segment and its associated offset.


LONG   A 32-bit signed integer.


LPARAM   A 32-bit value passed as a parameter to a window procedure or callback function.


LPCSTR   A 32-bit pointer to a constant character string.


LPSTR   A 32-bit pointer to a character string.


LPCTSTR   A 32-bit pointer to a constant character string that is portable for Unicode and DBCS.


LPTSTR   A 32-bit pointer to a character string that is portable for Unicode and DBCS.


LPVOID   A 32-bit pointer to an unspecified type.


LRESULT   A 32-bit value returned from a window procedure or callback function.


UINT   A 16-bit unsigned integer on Windows versions 3.0 and 3.1; a 32-bit unsigned integer on Win32.


WNDPROC   A 32-bit pointer to a window procedure.


WORD   A 16-bit unsigned integer.


WPARAM   A value passed as a parameter to a window procedure or callback function: 16 bits on Windows versions 3.0 and 3.1; 32 bits on Win32.
Data types unique to the Microsoft Foundation Class Library include the following:

POSITION   A value used to denote the position of an element in a collection; used by MFC collection classes.


LPCRECT   A 32-bit pointer to a constant (nonmodifiable) RECT structure.------------------------------------------------------------------------------------------------------------
SHORT : Short integer.
WORD : 16-bit unsigned integer

PBYTE : Pointer to a BYTE

BYTE : Byte (8 bits).

UINT : Unsigned INT.

INT  : Signed integer.

LONG : 32-bit signed integer

ULONG : Unsigned LONG

ULONGLONG : 64-bit unsigned integer

LPVOID : Pointer to any type

LPCVOID : Pointer to a constant of any type.

LPCTSTR : An LPCWSTR if UNICODE is defined, an LPCSTR otherwise.

LPCWSTR : Pointer to a constant null-terminated string of 16-bit Unicode characters.

LPCSTR : Pointer to a constant null-terminated string of 8-bit Windows (ANSI) characters.

LPTSTR : An LPWSTR if UNICODE is defined, an LPSTR otherwise

LPWSTR : Pointer to a null-terminated string of 16-bit Unicode characters

LPSTR : Pointer to a null-terminated string of 8-bit Windows (ANSI) characters

TCHAR : For ANSI and DBCS platforms, TCHAR is defined as follows : typedef char TCHAR;

 For Unicode platforms, TCHAR is defined as synonymous with the WCHAR type

Unicode  数据类型

WCHAR WCHAR 16-bit Unicode character  Unicode字符

PWSTR 指向Unicode字符串的指针

PCWSTR 指向一个恒定的Unicode字符串的指针

 _UNICODE 宏用于C运行期头文件
  UNICODE 宏用于Windows头文件
 当编译源代码模块时,通常是必须同时定义这两个宏

=========================================================================================================================================== 

定义成const后的常量,程序中对其只能读不能写(修改),从而防止该值被无意的修改。

 由于无法修改,所以,常量定义时必须初始化。如:

   const float pi;
   pi = 3.1415926;//error

 又如:
   const int size = 100*sizeof(int); //ok
   const int number = max(15,23);  //error


 在c++中,常量定义都用const,不用#define.

 程序内存空间

============================================================================================================================================

 代码区..............
(code area)

 全局数
 据区................
(data area)

 堆区................
(heap area)

 栈区................
(stack area)

一个程序将操作系统分配给其运行的内存块分为4个区域:

1。代码区:存放程序的代码,即程序中的各个函数代码块。

2。全局数据区:存放程序的全局数据和静态数据。

3。堆区:存放程序的动态数据。

4。栈区:存放程序的局部数据,即各个函数中的数据。

=============================================================================================================================================

常用转义字符:
/n 回车换行符,光标移到下一行行首
/r     回车不换行,光标移动到本行行首
/t     横向跳格(8位为一格,光标跳到下一格起始位置,如第9或第17位等)
/b 退一格,光标往左移动一格
/f 走纸换页
// 用于输出反斜杠字符" / "
/' 用于输出单引号字符" ' "
/" 用于输出双引号字符" " "
/ddd 三位八进制数ddd对应的ASCII码字符
/xhh 两位十六进制数hh对应的ASCII码字符

=============================================================================================================================================

.进程和线程:
 32位的Windows操作系统(Windows95以上版本及WinNT4.0)的一个重要特色就是支持一个应用程序能执行多个线程。用户可以在一个应用程序中进行不同类型的处理.

.进程和线程的区别和联系:
 一个进程是一个由Windows系统装载进内存的可执行单元.操作系统将为进程创建一个虚拟地址空间;
 线程是一个用于运行代码的Windows对象,被操作系统当作准备执行的一个执行体。

 进程包括代码、数据和文件、管道之类的资源,以及该进程的线程能够存取的线程同步对象。

 一个应用程序可以有一个以上的进程,一个进程可以有多个线程。

 Windows每创建一个新的进程时,都将为该进程创建一个线程来开始执行实际的程序代码,这个线程称为主线程,一个进程只能有一个主线程。一个进程可以创建一个或者多个线程,并且这些线程都使用同一虚拟地址空间。地址空间是由进程提供的,这使得线程之间共享数据变的容易、不同的线程可以执行同一代码块,也可以执行不同的代码块。当主线程结束时,整个进程也就结束了。

 进程有自己专有的虚拟地址空间。当一个进程内有一个以上的线程时,每个线程都和进程共享统一虚拟地址空间,都能获取进程的系统资源、访问全局变量。线程可以执行进程任何部分,并且可以创建新的线程。

 线程的一个重要特征就是和创建自己的进程共享内存,它可以存取调用它的进程的代码和数据。每个线程拥有自己专有的栈和专有的执行上下文(专有执行上下文是一小块内存,它用于保存CPU处理寄存器的状态和一些与线程执行环境相关的数据)。一般说来线程不占用额外的内存空间。

 :真正运行的程序单位是线程而不是进程,进程为运行的线程提供所要使用的数据、资源和地址空间。

.一个主线程可以创建其他线程,创建出来的新线程又可以创建出其他的线程,以次类推。一个线程的所有线程可以同时进行,Windows操作系统自动在不同的线程之间切换。

------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------
---进程的优先级类:

优先级  优先级类  优先级  描述
Idle  IDLE_PRIORITY_CLASS 4  空闲类
Normal  NORMAL_PRIORITY_CLASS 7-9  正常类
High  HIGH_PRIORITY_CLASS 13  高级类
Realtime REALTIME_PRIORITY_CLASS 24  实时类

---GetPriorityClass、SetPriorityClass:获取、设置给定进程的优先级类。

 DWORD GetPriorityClass(
  HANDLE hProcess //进程句柄
    );

 BOOL SetPriorityClass(
  HANDLE hProcess, //进程句柄
  DWORD  dwPriorityClass //优先级 
    );

---对当前的进程,可以通过函数GetCurrentProcess来获得其句柄:

 HANDLE GetCurrentProcess(VOID);
        此函数返回值是当前进程的句柄,获取了进程句柄后就可以设置进程的优先权。

---线程的优先权:

线程相关优先权  标志    优先级
tpIdle   THREAD_PRIORITY_IDLE  -15
tpLowest  THREAD_PRIORITY_LOWSET  -2
tpBelowNormal  THREAD_PRIORITY_BELOW_NORMAL -1
tpNormal  THREAD_PRIORITY_NORMAL  0
tpAboveNormal  THREAD_PRIORITY_ABOVE_NORMAL 1
tpHighest  THREAD_PRIORITY_HIGHEST  2
tpTimeCritical  THREAD_PRIORITY_TIME_CRITICAL 15

线程的总体优先权由进程的优先权类加上线程的相关优先权决定。整体优先权取值为1-31。

---------------------------------------------------------------------------------------------------------------------------

通过临界区,互斥量,信号灯,事件来进行同步。

由于进程/线程间的操作是并行进行的,所以就产生了一个数据的问题同步

OS提供了多种同步对象供我们使用,并且可以替我们管理同步对象的加锁和解锁。我们需要做的就是对每个需要同步使用的资源产生一个同步对象,在使用该资源前申请加锁,在使用完成后解锁。接下来我们介绍一些同步对象:

临界区:临界区是一种最简单的同步对象,它只可以在同一进程内部使用。它的作用是保证只有一个线程可以申请到该对象,例如上面的例子我们就可以使用临界区来进行同步处理。几个相关的API函数为:

VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection );产生临界区

VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection );删除临界区

VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection );进入临界区,相当于申请加锁,如果该临界区正被其他线程使用则该函数会等待到其他线程释放

BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection );进入临界区,相当于申请加锁,和EnterCriticalSection不同如果该临界区正被其他线程使用则该函数会立即返回FALSE,而不会等待

VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection );退出临界区,相当于申请解锁

下面的示范代码演示了如何使用临界区来进行数据同步处理:

//全局变量
int iCounter=0;
CRITICAL_SECTION criCounter;

DWORD threadA(void* pD)
{
 int iID=(int)pD;
 for(int i=0;i<8;i++)
 {
  EnterCriticalSection(&criCounter);
  int iCopy=iCounter;
  Sleep(100);
  iCounter=iCopy+1;
  printf("thread %d : %d/n",iID,iCounter);
  LeaveCriticalSection(&criCounter);
 }
 return 0;
}
//in main function
{
  //创建临界区
  InitializeCriticalSection(&criCounter);
  //创建线程
  HANDLE hThread[3];
  CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
  CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);
  CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);
  hThread[0]=pT1->m_hThread;
  hThread[1]=pT2->m_hThread;
  hThread[2]=pT3->m_hThread;
  //等待线程结束
  //至于WaitForMultipleObjects的用法后面会讲到。
  WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
  //删除临界区
  DeleteCriticalSection(&criCounter);
  printf("/nover/n");

}

---------------------------------------------------------------------------------------
互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。下面介绍可以用在互斥量上的API函数:
--互斥体是另一个一级的可命名、且安全的内核对象。其主要目的是引导对共享资源的访问。互斥体有点像接力赛跑中的接力棒。要想"跑"的线程应该等待当前正在"跑"的线程将接力棒传递过来。拥有单一访问资源的线程创建互斥体。所有想访问该资源的线程应该在实际执行操作之前获得互斥体,而在访问结束时应立即释放互斥体,这就允许下一个等待线程获得互斥体,然后接着进行下去。
创建互斥量:
HANDLE CreateMutex(
  LPSECURITY_ATTRIBUTES lpMutexAttributes,// 安全信息
  BOOL bInitialOwner,  // 最初状态,
  //如果设置为真,则表示创建它的线程直接拥有了该互斥量,而不需要再申请
  LPCTSTR lpName       // 名字,可以为NULL,但这样一来就不能被其他线程/进程打开
);
打开一个存在的互斥量:
HANDLE OpenMutex(
  DWORD dwDesiredAccess,  // 存取方式
  BOOL bInheritHandle,    // 是否可以被继承
  LPCTSTR lpName          // 名字
);
释放互斥量的使用权,但要求调用该函数的线程拥有该互斥量的使用权:
BOOL ReleaseMutex(//作用如同LeaveCriticalSection
  HANDLE hMutex   // 句柄
);
关闭互斥量:
BOOL CloseHandle(
  HANDLE hObject   // 句柄
);

获取互斥量的使用权需要使用函数:

DWORD WaitForSingleObject(
  HANDLE hHandle,        // 等待的对象的句柄
  DWORD dwMilliseconds   // 等待的时间,以ms为单位,如果为INFINITE表示无限期的等待
);
返回:
WAIT_ABANDONED 在等待的对象为互斥量时表明因为互斥量被关闭而变为有信号状态
WAIT_OBJECT_0 得到使用权
WAIT_TIMEOUT 超过(dwMilliseconds)规定时间

在线程调用WaitForSingleObject后,如果一直无法得到控制权线程讲被挂起,直到超过时间或是获得控制权。

讲到这里我们必须更深入的讲一下WaitForSingleObject函数中的对象(Object)的含义,这里的对象是一个具有信号状态的对象,对象有两种状态:有信号/无信号。而等待的含义就在于等待对象变为有信号的状态,对于互斥量来讲如果正在被使用则为无信号状态,被释放后变为有信号状态。当等待成功后WaitForSingleObject函数会将互斥量置为无信号状态,这样其他的线程就不能获得使用权而需要继续等待。WaitForSingleObject函数还进行排队功能,保证先提出等待请求的线程先获得对象的使用权

现在我们回过头来讲WaitForSingleObject这个函数,从前面的例子中我们看到WaitForSingleObject这个函数将等待一个对象变为有信号状态,那么具有信号状态的对象有哪些呢?下面是一部分:

Mutex
Event
Semaphore
Job
Process
Thread
Waitable timer
Console input
互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态。所以我们可以使用WaitForSingleObject来等待进程和线程退出。(至于信号灯,事件的用法我们接下来会讲)我们在前面的例子中使用了WaitForMultipleObjects函数,这个函数的作用与WaitForSingleObject类似但从名字上我们可以看出,WaitForMultipleObjects将用于等待多个对象变为有信号状态,函数原型如下:

DWORD WaitForMultipleObjects(
  DWORD nCount,             // 等待的对象数量
  CONST HANDLE *lpHandles,  // 对象句柄数组指针
  BOOL fWaitAll,            // 等待方式,
  //为TRUE表示等待全部对象都变为有信号状态才返回,为FALSE表示任何一个对象变为有信号状态则返回
  DWORD dwMilliseconds      // 超时设置,以ms为单位,如果为INFINITE表示无限期的等待
);

返回值意义:
WAIT_OBJECT_0 到 (WAIT_OBJECT_0 + nCount – 1):当fWaitAll为TRUE时表示所有对象变为有信号状态,当fWaitAll为FALSE时使用返回值减去WAIT_OBJECT_0得到变为有信号状态的对象在数组中的下标。
WAIT_ABANDONED_0 到 (WAIT_ABANDONED_0 + nCount – 1):当fWaitAll为TRUE时表示所有对象变为有信号状态,当fWaitAll为FALSE时表示对象中有一个对象为互斥量,该互斥量因为被关闭而成为有信号状态,使用返回值减去WAIT_OBJECT_0得到变为有信号状态的对象在数组中的下标。
WAIT_TIMEOUT:表示超过规定时间。
--------------------------------------------------------------------------------------------------------
这里信号灯有一个初始值,表示有多少进程/线程可以进入,当信号灯的值大于0时为有信号状态,小于等于0时为无信号状态,所以可以利用WaitForSingleObject进行等待,当WaitForSingleObject等待成功后信号灯的值会被减少1,直到释放时信号灯会被增加1。用于信号灯操作的API函数有下面这些:

创建信号灯:
HANDLE CreateSemaphore(
  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,// 安全属性,NULL表示使用默认的安全描述
  LONG lInitialCount,  // 初始值
  LONG lMaximumCount,  // 最大值
  LPCTSTR lpName       // 名字
);
打开信号灯:
HANDLE OpenSemaphore(
  DWORD dwDesiredAccess,  // 存取方式
  BOOL bInheritHandle,    // 是否能被继承
  LPCTSTR lpName          // 名字
);
释放信号灯:
BOOL ReleaseSemaphore(
  HANDLE hSemaphore,   // 句柄
  LONG lReleaseCount,  // 释放数,让信号灯值增加数
  LPLONG lpPreviousCount   // 用来得到释放前信号灯的值,可以为NULL
);
关闭信号灯:
BOOL CloseHandle(
  HANDLE hObject   // 句柄
);

信号灯有时用来作为计数器使用,一般来讲将其初始值设置为0,先调用ReleaseSemaphore来增加其计数,然后使用WaitForSingleObject来减小其计数,遗憾的是通常我们都不能得到信号灯的当前值,但是可以通过设置WaitForSingleObject的等待时间为0来检查信号灯当前是否为0。
-----------------------------------------------------------------------------------------------------------
接下来我们讲最后一种同步对象:事件,前面讲的信号灯和互斥量可以保证资源被正常的分配和使用,而事件是用来通知其他进程/线程某件操作已经完成。

事件对象可以一两种方式创建,一种为自动重置,在其他线程使用WaitForSingleObject等待到事件对象变为有信号后该事件对象自动又变为无信号状态,一种为人工重置在其他线程使用WaitForSingleObject等待到事件对象变为有信号后该事件对象状态不变。例如有多个线程都在等待一个线程运行结束,我们就可以使用人工重置事件,在被等待的线程结束时设置该事件为有信号状态,这样其他的多个线程对该事件的等待都会成功(因为该事件的状态不会被自动重置)。事件相关的API如下:

创建事件对象:
HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,// 安全属性,NULL表示使用默认的安全描述
  BOOL bManualReset,  // 是否为人工重置
  BOOL bInitialState, // 初始状态是否为有信号状态
  LPCTSTR lpName      // 名字
);
打开事件对象:
HANDLE OpenEvent(
  DWORD dwDesiredAccess,  // 存取方式
  BOOL bInheritHandle,    // 是否能够被继承
  LPCTSTR lpName          // 名字
);
设置事件为无信号状态:
BOOL ResetEvent(
  HANDLE hEvent   // 句柄
);
设置事件有无信号状态:
BOOL SetEvent(
  HANDLE hEvent   // 句柄
);
关闭事件对象:
BOOL CloseHandle(
  HANDLE hObject   // 句柄
);

下面的代码演示了自动重置和人工重置事件在使用中的不同效果:

DWORD threadA(void* pD)
{
 int iID=(int)pD;
 //在内部重新打开
 HANDLE hCounterIn=OpenEvent(EVENT_ALL_ACCESS,FALSE,"sam sp 44");

 printf("/tthread %d begin/n",iID);
 //设置成为有信号状态
 Sleep(1000);
 SetEvent(hCounterIn);
 Sleep(1000);
 printf("/tthread %d end/n",iID);
 CloseHandle(hCounterIn);
 return 0;
}

DWORD threadB(void* pD)
{//等待threadA结束后在继续执行
 int iID=(int)pD;
 //在内部重新打开
 HANDLE hCounterIn=OpenEvent(EVENT_ALL_ACCESS,FALSE,"sam sp 44");

 if(WAIT_TIMEOUT == WaitForSingleObject(hCounterIn,10*1000))
 {
  printf("/t/tthread %d wait time out/n",iID);
 }
 else
 {
  printf("/t/tthread %d wait ok/n",iID);
 }
 CloseHandle(hCounterIn);
 return 0;
}

//in main function
{
  HANDLE hCounter=NULL;
  if( (hCounter=OpenEvent(EVENT_ALL_ACCESS,FALSE,"sam sp 44"))==NULL)
  {
   //如果没有其他进程创建这个事件,则重新创建,该事件为人工重置事件
   hCounter = CreateEvent(NULL,TRUE,FALSE,"sam sp 44");
  }

  //创建线程
  HANDLE hThread[3];
  printf("test of manual rest event/n");
  CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
  CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadB,(void*)2);
  CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadB,(void*)3);
  hThread[0]=pT1->m_hThread;
  hThread[1]=pT2->m_hThread;
  hThread[2]=pT3->m_hThread;
  //等待线程结束
  WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
  //关闭句柄
  CloseHandle(hCounter);

  if( (hCounter=OpenEvent(EVENT_ALL_ACCESS,FALSE,"sam sp 44"))==NULL)
  {
   //如果没有其他进程创建这个事件,则重新创建,该事件为自动重置事件
   hCounter = CreateEvent(NULL,FALSE,FALSE,"sam sp 44");
  }
  //创建线程
  printf("test of auto rest event/n");
  pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
  pT2=AfxBeginThread((AFX_THREADPROC)threadB,(void*)2);
  pT3=AfxBeginThread((AFX_THREADPROC)threadB,(void*)3);
  hThread[0]=pT1->m_hThread;
  hThread[1]=pT2->m_hThread;
  hThread[2]=pT3->m_hThread;
  //等待线程结束
  WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
  //关闭句柄
  CloseHandle(hCounter);

从执行结果中我们可以看到在第二次执行时由于使用了自动重置事件threadB中只有一个线程能够等待到threadA中释放的事件对象。

在处理多进程/线程的同步问题时必须要小心避免发生死锁问题,比如说现在有两个互斥量A和B,两个线程tA和tB,他们在执行前都需要得到这两个互斥量,但现在这种情况发生了,tA拥有了互斥量A,tB拥有了互斥量B,但它们同时都在等待拥有另一个互斥量,这时候显然谁也不可能得到自己希望的资源。这种互相拥有对方所拥有的资源而且都在等待对方拥有的资源的情况就称为死锁。关于这个问题更详细的介绍请参考其他参考书。
------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
!准确的理解内核对象对于一名Windows软件开发人员来说是至关重要的。

 内核对象可以供系统和应用程序使用来管理各种各样的资源,比如进程、线程和文件等。

存取符号对象
事件对象
文件对象
文件映射对象
I/O完成端口对象
作业对象
信箱对象
互斥对象
管道对象
进程对象
信标对象
线程对象
等待计时器对象

--内核对象的使用计数:

 内核对象由内核所拥有,而不是由进程所拥有。比如:你的进程调用了一个创建内核对象的函数,然后你的进程终止运行,那么内核对象不一定被撤销。
 内核知道有多少进程正在使用某个内核对象,因为每个对象包含一个使用计数。它是所有内核对象类型常用的数据成员之一。

--跨越进程边界共享内核对象:
 .文件映射对象:同一台机器上运行的两个进程之间共享数据块
 .邮箱和指定的管道:在连网的不同机器上运行的进程之间发送数据块
 .互斥对象、信标和事件:不同进程中的线程能够同步它们的连续运行

软件开发的过程及注意事项

  • 2010年07月17日 18:14
  • 33KB
  • 下载

软件开发注意事项

软件开发注意事项     开发APP 80%的人都容易犯一些致命的错误: ◆以为做个软件很简单,所以描述需求的时候及其模糊。 ◆ 急着想要报价,在自己对需求不确定的时候就要报价。然后就按...

c/c++软件开发的注意事项

第一部分软件编程的时间分配概况    高效率的程序员并不是敲键盘的速度比别人快,而是他有着良好的编程习惯,节省了别人浪费的时间。因此,要想提高自己的编程效率,根本在于怎么少浪费时间。只要能把别人...

手机开发实战190——手机软件开发注意事项4

31嵌入式开发更应该注意资源(内存)的消耗和程序的运行速度;   32具有一定集成度并可以重复使用的软件组成单元称为软构件(Software Component)   33显示...
  • xjbclz
  • xjbclz
  • 2016年06月14日 20:33
  • 302

手机开发实战189——手机软件开发注意事项3

21 setjmp和longjmp的使用 利用这两个函数可以在程序发生严重异常时,直接跳回到idle状态或上级菜单,而不必利用重启返回;   22在许多状况下,连续按键会造成手机出现...
  • xjbclz
  • xjbclz
  • 2016年06月14日 20:32
  • 255

北京软件开发-java技术注意事项

java技术的分享…… 编写Java程序的注意事项,对Java编程思想的忠告。 (1) 类名首字母应该大写。字段、方法以及对象(句柄)的首字母应小写。[北京软件公司]对于所有标识符,其中包...

软件开发SVN的使用方法和注意事项

熟悉软件开发的朋友或者参与过大型项目的朋友都会熟悉SVN这个team团队开发软件,但是在使用过程中是不是遇见过些问题是你很苦恼,那是你没有理解SVN这个软件的运行机制,是不是自己写的代码被别人覆盖,造...

手机开发实战187——手机软件开发注意事项1

手机软件开发注意事项 1在程序的不同状态需考虑的异常事件(开关机): 来电 短信(接收和定时发送) 彩信(手动下载和自动下载) 状态报告 Wap Push (SI ...
  • xjbclz
  • xjbclz
  • 2016年06月14日 20:31
  • 151
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Windows软件开发中的注意事项
举报原因:
原因补充:

(最多只允许输入30个字)