(转载)VC2005中MFC程序的部署问题

原文:http://rain.newegg.cn/user4/sunliguang/archives/2007/216734.html

 

 

    在VC6中,Release版本的程序的部署,除了程序自身关联的各个Dll之外,只需添加MFC42.dll即可。

在VC2005中,发生的变化包括:

1.MFC的版本发生变化,最新的版本为8.0,所有应该包括MFC80.dll

2.在VC2005的架构下,采用manifest进行dll的版本确认,因此需要添加MFC程序所需的manifest文件。

在VC2005的安装目录下:D:"Program Files"Microsoft Visual Studio 8"VC"下有一个文件夹为redist专用于

程序的部署和发布。在其中的x86文件夹用于Release版本的程序发布,其中的Microsoft.VC80.MFC文件

夹用于发布MFC程序,包括混合有Unicode以及CLR的程序,可以根据程序需要选择copy。对于我个人的

单纯MFC的程序,只需要复制Microsoft.VC80.MFC.manifest和mfc80.dll即可。其余几个,文件名称中带有

“u”的表示兼容unicode编码,带有“m”表示使用托管代码生成规则。

       还有另一个解决manifest文件的办法:

     在VC2005的开发环境下,选择项目属性-->清单工具-->输入和输出,在潜入清单位置,选择“否”,这

样VC2005将会直接在Release目录下,exe文件的旁边为您生成一个同名的manifest文件,文件内容和

Microsoft.VC80.MFC.manifest有关联。直接复制这个文件也可以起到效果。

      对不太熟练的开发人员,可以直接将这些dll统统复制到自己的exe文件所在目录下,应该可以确保万无

一失,只不过有的文件没有被利用而已。

      最简单的程序发布方法莫过于直接复制文件,对依赖的dll文件,可以直接和exe文件放在同一个文件夹下,

而不将其复制到系统文件夹下。   

 

标签:软件开发 | 浏览数(637) | 评论数(0) | 2007-06-15

Sockets/Windows Sockets错误码
Windows Sockets在头文件winsock.h中定义了所有的错误码,它们包括以“WSA”打头的
Windows Sockets实现返回的错误码和Berkeley Sockets定义的错误码全集。定义Berkeley
Sockets错误码是为了确保原有软件的可移植性。
WSAEACCES (10013) Permission denied.
试图使用被禁止的访问权限去访问套接字。例如,在没有使用函数setsockopt()的
SO_BROADCAST命令设置广播权限的套接字上使用函数sendto()给一个广播地址发送数据。
WSAEADDRINUSE (10048) Address already in use.
正常情况下每一个套接字地址(协议/IP地址/端口号)只允许使用一次。当应用程序试图
使用bind()函数将一个被已存在的或没有完全关闭的或正在关闭的套接字使用了的IP地址/
端口号绑扎到一个新套接字上时,该错误发生。对于服务器应用程序来说,如果需要使用
bind()函数将多个套接字绑扎到同一个端口上,可以考虑使用setsockopt()函数的
SO_REUSEADDR命令。客户应用程序一般不必使用bind()函数——connect()函数总是自动
选择没有使用的端口号。当bind()函数操作的是通配地址(包括ADDR_ANY)时,错误
WSAEADDRINUSE可能延迟到一个明确的地址被提交时才发生。这可能在后续的函数如
connect()、listen()、WSAConnect()或WSAJoinLeaf()调用时发生。
WSAEADDRNOTAVAIL (10049) Cannot assign requested address.
被请求的地址在它的环境中是不合法的。通常地在bind()函数试图将一个本地机器不合法的
地址绑扎到套接字时产生。它也可能在connect()、sendto()、WSAConnect()、WSAJoinLeaf()
或WSASendTo()函数调用时因远程机器的远程地址或端口号非法(如0地址或0端口号)而
产生。
WSAEAFNOSUPPORT (10047) Address family not supported by protocol family.
使用的地址与被请求的协议不兼容。所有的套接字在创建时都与一个地址族(如IP协议对
应的AF_INET)和一个通用的协议类型(如SOCK_STREAM)联系起来。如果在socket()调
用中明确地要求一个不正确的协议,或在调用sendto()等函数时使用了对套接字来说是错误
的地址族的地址,该错误返回。
WSAEALREADY (10037) Operation already in progress.
当在非阻塞套接字上已经有一个操作正在进行时,又有一个操作试图在其上执行则产生此错
误。如:在一个正在进行连接的非阻塞套接字上第二次调用connect()函数;或取消一个已经
被取消或已完成的异步请求(WSAAsyncGetXbyY())。
WSAECONNABORTED (10053) Software caused connection abort.
一个已建立的连接被你的主机上的软件终止,可能是因为一次数据传输超时或是协议错误。
WSAECONNREFUSED (10061) Connection refused.
因为目标主机主动拒绝,连接不能建立。这通常是因为试图连接到一个远程主机上不活动的
服务,如没有服务器应用程序处于执行状态。
WSAECONNRESET (10054) Connection reset by peer.
存在的连接被远程主机强制关闭。通常原因为:远程主机上对等方应用程序突然停止运行,
或远程主机重新启动,或远程主机在远程方套接字上使用了“强制”关闭(参见setsockopt
(SO_LINGER))。另外,在一个或多个操作正在进行时,如果连接因“keep-alive”活动检测
到一个失败而中断,也可能导致此错误。此时,正在进行的操作以错误码WSAENETRESET
失败返回,后续操作将失败返回错误码WSAECONNRESET。
WSAEDESTADDRREQ (10039) Destination address required.
在套接字上一个操作所必须的地址被遗漏。例如,如果sendto()函数被调用且远程地址为
ADDR_ANY时,此错误被返回。
WSAEFAULT (10014) Bad address.
系统检测到调用试图使用的一个指针参数指向的是一个非法指针地址。如果应用程序传递一
个非法的指针值,或缓冲区长度太小,此错误发生。例如,参数为结构sockaddr,但参数的
长度小于sizeof(struct sockaddr)。
WSAEHOSTDOWN (10064) Host is down.
套接字操作因为目的主机关闭而失败返回。套接字操作遇到不活动主机。本地主机上的网络
活动没有初始化。这些条件由错误码WSAETIMEDOUT指示似乎更合适。
WSAEHOSTUNREACH (10065) No route to host.
试图和一个不可达主机进行套接字操作。参见WSAENETUNREACH。
WSAEINPROGRESS (10036) Operation now in progress.
一个阻塞操作正在执行。Windows Sockets只允许一个任务(或线程)在同一时间可以有一
个未完成的阻塞操作,如果此时调用了任何函数(不管此函数是否引用了该套接字或任何其
它套接字),此函数将以错误码WSAEINPROGRESS返回。
WSAEINTR (10004) Interrupted function call.
阻塞操作被函数WSACancelBlockingCall ()调用所中断。
WSAEINVAL (10022) Invalid argument.
提供了非法参数(例如,在使用setsockopt()函数时指定了非法的level)。在一些实例中,
它也可能与套接字的当前状态相关,例如,在套接字没有使用listen()使其处于监听时调用
accept()函数。
WSAEISCONN (10056) Socket is already connected.
连接请求发生在已经连接的套接字上。一些实现对于在已连接SOCK_DGRAM套接字上使
用sendto()函数的情况也返回此错误(对于SOCK_STREAM套接字,sendto()函数的to参数
被忽略),尽管其它一些实现将此操作视为合法事件。
WSAEMFILE (10024) Too many open files.
打开了太多的套接字。不管是对整个系统还是每一进程或线程,Windows Sockets实现都可
能有一个最大可用的套接字句柄数。
WSAEMSGSIZE (10040) Message too long.
在数据报套接字上发送的一个消息大于内部消息缓冲区或一些其它网络限制,或者是用来接
受数据报的缓冲区小于数据报本身。
WSAENETDOWN (10050) Network is down.
套接字操作遇到一个不活动的网络。此错误可能指示网络系统(例如WinSock DLL运行的
协议栈)、网络接口或本地网络本身发生了一个严重的失败。
WSAENETRESET (10052) Network dropped connection on reset.
在操作正在进行时连接因“keep-alive”检测到失败而中断。也可能由setsockopt()函数返回,
如果试图使用它在一个已经失败的连接上设置SO_KEEPALIVE。
WSAENETUNREACH (10051) Network is unreachable.
试图和一个无法到达的网络进行套接字操作。它常常意味着本地软件不知道到达远程主机的
路由。
WSAENOBUFS (10055) No buffer space available.
由于系统缺乏足够的缓冲区空间,或因为队列已满,在套接字上的操作无法执行。
WSAENOPROTOOPT (10042) Bad protocol option.
在getsockopt()或setsockopt()调用中,指定了一个未知的、非法的或不支持的选项或层
(level)。
WSAENOTCONN (10057) Socket is not connected.
因为套接字没有连接,发送或接收数据的请求不被允许,或者是使用sendto()函数在数据报
套接字上发送时没有提供地址。任何其它类型的操作也可以返回此错误,例如,使用
setsockopt()函数在一个已重置的连接上设置SO_KEEPALIVE。
WSAENOTSOCK (10038) Socket operation on non-socket.
操作试图不是在套接字上进行。它可能是套接字句柄参数没有引用到一个合法套接字,或者
是调用select()函数时,一个fd_set中的成员不合法。
WSAEOPNOTSUPP (10045) Operation not supported.
对于引用的对象的类型来说,试图进行的操作不支持。通常它发生在套接字不支持此操作的
套接字描述符上,例如,试图在数据报套接字上接收连接。
WSAEPFNOSUPPORT (10046) Protocol family not supported.
协议簇没有在系统中配置或没有支持它的实现存在。它与WSAEAFNOSUPPORT有些微的
不同,但在绝大多数情况下是可互换的,返回这两个错误的所有Windows Sockets函数的说
明见WSAEAFNOSUPPORT的描述。
命令。

WSAEPROCLIM (10067) Too many processes.
Windows Sockets实现可能限制同时使用它的应用程序的数量,如果达到此限制,
WSAStartup()函数可能因此错误失败。
WSAEPROTONOSUPPORT (10043) Protocol not supported.
请求的协议没有在系统中配置或没有支持它的实现存在。例如,socket()调用请求一个
SOCK_DGRAM套接字,但指定的是流协议。
WSAEPROTOTYPE (10041) Protocol wrong type for socket.
在socket()函数调用中指定的协议不支持请求的套接字类型的语义。例如,ARPA Internet
UDP协议不能和SOCK_STREAM套接字类型一同指定。
WSAESHUTDOWN (10058) Cannot send after socket shutdown.
因为套接字在相应方向上已经被先前的shutdown()调用关闭,因此该方向上的发送或接收请
求不被允许。通过调用shutdown()函数来请求对套接字的部分关闭,它发送一个信号来停止
发送或接收或双向操作。
WSAESOCKTNOSUPPORT (10044) Socket type not supported.
不支持在此地址族中指定的套接字类型。例如,socket()调用中选择了可选的套接字类型
SOCK_RAW,但是实现却根本不支持SOCK_RAW类型的套接字。
WSAETIMEDOUT (10060) Connection timed out.
连接请求因被连接方在一个时间周期内不能正确响应而失败,或已经建立的连接因被连接的
主机不能响应而失败。
WSATYPE_NOT_FOUND (10109) Class type not found
指定的类没有找到。
WSAEWOULDBLOCK (10035) Resource temporarily unavailable.
此错误由在非阻塞套接字上不能立即完成的操作返回,例如,当套接字上没有排队数据可读
时调用了recv()函数。此错误不是严重错误,相应操作应该稍后重试。对于在非阻塞
SOCK_STREAM套接字上调用connect()函数来说,报告WSAEWOULDBLOCK是正常的,
因为建立一个连接必须花费一些时间。
WSAHOST_NOT_FOUND (11001) Host not found.
主机未知。此名字不是一个正式主机名,也不是一个别名,它不能在查询的数据库中找到。
此错误也可能在协议和服务查询中返回,它意味着指定的名字不能在相关数据库中找到。
WSA_INVALID_HANDLE (OS dependent) Specified event object handle is invalid.
应用程序试图使用一个事件对象,但指定的句柄非法。
WSA_INVALID_PARAMETER (OS dependent) One or more parameters are invalid.
应用程序使用了一个直接映射到Win32函数的WinSock函数,而Win32函数指示一个或多
个参数有问题。
WSAINVALIDPROCTABLE (OS dependent) Invalid procedure table from service
provider.
服务提供者返回了一个假的WS2_32.DLL程序(procedure)表。这通常是由一个或多个函数
指针为空引起。
WSAINVALIDPROVIDER (OS dependent) Invalid service provider version number.
服务提供者返回一个不同于2.2的版本号。
WSA_IO_INCOMPLETE (OS dependent) Overlapped I/O event object not in signaled
state.
应用程序试图检测一个没有完成的重叠操作的状态。应用程序使用函数
WSAGetOverlappedResult()(参数fWait设置为false)以轮询模式检测一个重叠操作是否完
成时将得到此错误码,除非该操作已经完成。
WSA_IO_PENDING (OS dependent) Overlapped operations will complete later.
应用程序已经初始化了一个不能立即完成的重叠操作。当稍后此操作完成时将有完成指示。
WSA_NOT_ENOUGH_MEMORY (OS dependent) Insufficient memory available.
应用程序使用了一个直接映射到Win32函数的WinSock函数,而Win32函数指示缺乏必要
的内存资源。
WSANOTINITIALISED (10093) Successful WSAStartup() not yet performed.
应用程序没有调用WSAStartup()函数,或函数WSAStartup()调用失败了。应用程序可能访问
了不属于当前活动任务的套接字(例如试图在任务间共享套接字),或调用了过多的
WSACleanup()函数。
WSANO_DATA (11004) Valid name, no data record of requested type.
请求的名字合法并且在数据库中找到了,但它没有正确的关联数据用于解析。此错误的通常
例子是主机名到地址(使用gethostbyname()或WSAAsyncGetHostByName()函数)的DNS转
换请求,返回了MX(Mail eXchanger)记录但是没有A(Address)记录,它指示主机本身
是存在的,但是不能直接到达。
WSANO_RECOVERY (11003) This is a non-recoverable error.
此错误码指示在数据库查找时发生了某种不可恢复错误。它可能是因为数据库文件(如BSD
兼容的HOSTS、SERVICES或PROTOCOLS文件)找不到,或DNS请求应服务器有严重错
误而返回。
WSAPROVIDERFAILEDINIT (OS dependent) Unable to initialize a service provider.
服务提供者的DLL不能加载(LoadLibrary()失败)或提供者的WSPStartup/NSPStartup函数
失败。
WSASYSCALLFAILURE (OS dependent) System call failure..
当一个不应该失败的系统调用失败时返回。例如,如果WaitForMultipleObjects()调用失败,
或注册的API不能够利用协议/名字空间目录。
WSASYSNOTREADY (10091) Network subsystem is unavailable.
此错误由WSAStartup()函数返回,它表示此时Windows Sockets实现因底层用来提供网络服
务的系统不可用。用户应该检查:
是否有合适的Windows Sockets DLL文件在当前路径中。
是否同时使用了多个WinSock实现。如果有多于一个的WINSOCK DLL在系统
中,必须确保搜索路径中第一个WINSOCK DLL文件是当前加载的网络子系统所
需要的。
查看WinSock实现的文档以确保所有必须的部件都正确地安装并配置好了。
WSATRY_AGAIN (11002) Non-authoritative host not found.
此错误通常是在主机名解析时的临时错误,它意味着本地服务器没有从授权服务器接收到一
个响应。稍后的重试可能会获得成功。
WSAVERNOTSUPPORTED (10092) WINSOCK.DLL version out of range.
当前的WinSock实现不支持应用程序指定的Windows Sockets规范版本。检查是否有旧的
Windows Sockets DLL文件正在被访问。
WSAEDISCON (10101) Graceful shutdown in progress.
由WSARecv()和WSARecvFrom()函数返回,指示远程方已经初始化了一个“雅致”的关闭
序列。
WSA_OPERATION_ABORTED (OS dependent) Overlapped operation aborted.
因为套接字的关闭,一个重叠操作被取消,或是执行了WSAIoctl()函数的SIO_FLUSH

 

标签:软件开发 | 浏览数(951) | 评论数(0) | 2007-04-03

发布版本时的收获(原创)

关键字:应用程序正常初始化(0xc0000135)失败、OLE支持 
正文

 

浏览数(404) | 评论数(0) | 2007-03-06

使用标准C++的类型转换符:static_cast、dynamic_cast、reinterpret_cast、和const_cast。

3.1 static_cast
用法:static_cast < type-id > ( expression )     
该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
①用于类层次结构中基类和子类之间指针或引用的转换。
  进行上行转换(把子类的指针或引用转换成基类表示)是安全的;
  进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
③把空指针转换成目标类型的空指针。
④把任何类型的表达式转换成void类型。

注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。


3.2 dynamic_cast
用法:dynamic_cast < type-id > ( expression )
该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;
如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。

dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
class B{
public:
       int m_iNum;
       virtual void foo();
};

class D:public B{
    public:
       char *m_szName[100];
};

void func(B *pb){
    D *pd1 = static_cast<D *>(pb);
    D *pd2 = dynamic_cast<D *>(pb);
}

在上面的代码段中,如果pb指向一个D类型的对象,pd1和pd2是一样的,并且对这两个指针执行D类型的任何操作都是安全的;
但是,如果pb指向的是一个B类型的对象,那么pd1将是一个指向该对象的指针,对它进行D类型的操作将是不安全的(如访问m_szName),
而pd2将是一个空指针。

另外要注意:B要有虚函数,否则会编译出错;static_cast则没有这个限制。
这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(
关于虚函数表的概念,详细可见<Inside c++ object model>)中,只有定义了虚函数的类才有虚函数表,
没有定义虚函数的类是没有虚函数表的。

另外,dynamic_cast还支持交叉转换(cross cast)。如下代码所示。
class A{
public:
        int m_iNum;
        virtual void f(){}
};

class B:public A{
};

class D:public A{
};

void foo(){
    B *pb = new B;
    pb->m_iNum = 100;

    D *pd1 = static_cast<D *>(pb);    //compile error
    D *pd2 = dynamic_cast<D *>(pb);  //pd2 is NULL
    delete pb;
}

在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错;而使用 dynamic_cast的转换则是允许的,结果是空指针。


3.3 reinpreter_cast
用法:reinpreter_cast<type-id> (expression)
type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。
它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,
在把该整数转换成原类型的指针,还可以得到原先的指针值)。

该运算符的用法比较多。

3.4 const_cast
用法:const_cast<type_id> (expression)
该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
常量指针被转化成非常量指针,并且仍然指向原来的对象;
常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。

Voiatile和const类试。举如下一例:
class B{
public:
     int m_iNum;
}
void foo(){
 const B b1;
 b1.m_iNum = 100;            //comile error
 B b2 = const_cast<B>(b1);
 b2. m_iNum = 200;           //fine
}
上面的代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;
使用const_cast把它转换成一个常量对象,就可以对它的数据成员任意改变。注意:b1和b2是两个不同的对象。

 

标签:软件开发 | 浏览数(530) | 评论数(0) | 2007-01-27

1、引言

在Windows程序中,各个进程之间常常需要交换数据,进行数据通讯。WIN32 API提供了许多函数使我们能够方便高效的进行进程间的通讯,通过这些函数我们可以控制不同进程间的数据交换,就如同在WIN16中对本地进程进行读写操作一样。

典型的WIN16两进程可以通过共享内存来进行数据交换:(1)进程A将GlobalAlloc(GMEM_SHARE...)API分配一定长度的内存;(2)进程A将GlobalAlloc函数返回的句柄传递给进程B(通过一个登录消息);(3)进程B对这个句柄调用GlobalLock函数,并利用GlobalLock函数返回的指针访问数据。这种方法在WIN32中可能失败,这是因为GlobalLock函数返回指向的是进程A的内存,由于进程使用的是虚拟地址而非实际物理地址,因此这一指针仅与A进程有关,而于B进程无关。

本文探讨了几种WIN32下进程之间通讯的几种实现方法,读者可以使用不同的方法以达到程序运行高效可靠的目的。

2、Windows95中进程的内存空间管理

WIN32进程间通讯与Windows95的内存管理有密切关系,理解Windows95的内存管理对我们如下的程序设计将会有很大的帮助,下面我们讨论以下Windows95中进程的内存空间管理。

在WIN16下,所有Windows应用程序共享单一地址,任何进程都能够对这一空间中属于共享单一的地址空间,任何进程都能够对这一空间中属于其他进程的内存进行读写操作,甚至可以存取操作系统本身的数据,这样就可能破坏其他程序的数据段代码。

在WIN32下,每个进程都有自己的地址空间,一个WIN32进程不能存取另一个地址的私有数据,两个进程可以用具有相同值的指针寻址,但所读写的只是它们各自的数据,这样就减少了进程之间的相互干扰。另一方面,每个WIN32进程拥有4GB的地址空间,但并不代表它真正拥有4GB的实际物理内存,而只是操作系统利用CPU的内存分配功能提供的虚拟地址空间。在一般情况下,绝大多数虚拟地址并没有物理内存于它对应,在真正可以使用这些地址空间之前,还要由操作系统提供实际的物理内存(这个过程叫“提交”commit)。在不同的情况下,系统提交的物理内存是不同的,可能是RAM,也可能是硬盘模拟的虚拟内存。

3、WIN32中进程间的通讯

在Windows 95中,为实现进程间平等的数据交换,用户可以有如下几种选择:
* 使用内存映射文件
* 通过共享内存DLL共享内存
* 向另一进程发送WM_COPYDATA消息
* 调用ReadProcessMemory以及WriteProcessMemory函数,用户可以发送由GlobalLock(GMEM_SHARE,...)函数调用提取的句柄、GlobalLock函数返回的指针以及VirtualAlloc函数返回的指针。

3.1、利用内存映射文件实现WIN32进程间的通讯

Windows95中的内存映射文件的机制为我们高效地操作文件提供了一种途径,它允许我们在WIN32进程中保留一段内存区域,把目标文件映射到这段虚拟内存中。在程序实现中必须考虑各进程之间的同步。具体实现步骤如下:

首先我们在发送数据的进程中需要通过调用内存映射API函数CreateFileMapping创建一个有名的共享内存:
HANDLE CreateFileMapping(
HANDLE hFile,    // 映射文件的句柄,
//设为0xFFFFFFFF以创建一个进程间共享的对象
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,    // 安全属性
DWORD flProtect,    // 保护方式
DWORD dwMaximumSizeHigh,    //对象的大小
DWORD dwMaximumSizeLow,   
LPCTSTR lpName     // 必须为映射文件命名
);

与虚拟内存类似,保护方式可以是PAGE_READONLY或是PAGE_READWRITE。如果多进程都对同一共享内存进行写访问,则必须保持相互间同步。映射文件还可以指定PAGE_WRITECOPY标志,可以保证其原始数据不会遭到破坏,同时允许其他进程在必要时自由的操作数据的拷贝。

在创建文件映射对象后使用可以调用MapViewOfFile函数映射到本进程的地址空间内。

下面说明创建一个名为MySharedMem的长度为4096字节的有名映射文件:
HANDLE hMySharedMapFile=CreateFileMapping((HANDLE)0xFFFFFFFF),
NULL,PAGE_READWRITE,0,0x1000,"MySharedMem");
并映射缓存区视图:
LPSTR pszMySharedMapView=(LPSTR)MapViewOfFile(hMySharedMapFile,
FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);

其他进程访问共享对象,需要获得对象名并调用OpenFileMapping函数。
HANDLE hMySharedMapFile=OpenFileMapping(FILE_MAP_WRITE,
FALSE,"MySharedMem");

一旦其他进程获得映射对象的句柄,可以象创建进程那样调用MapViewOfFile函数来映射对象视图。用户可以使用该对象视图来进行数据读写操作,以达到数据通讯的目的。

当用户进程结束使用共享内存后,调用UnmapViewOfFile函数以取消其地址空间内的视图:
if (!UnmapViewOfFile(pszMySharedMapView))
{ AfxMessageBox("could not unmap view of file"); }

3.2、利用共享内存DLL

共享数据DLL允许进程以类似于Windows 3.1 DLL共享数据的方式访问读写数据,多个进程都可以对该共享数据DLL进行数据操作,达到共享数据的目的。在WIN32中为建立共享内存,必须执行以下步骤:
首先创建一个有名的数据区。这在Visual C++中是使用data_seg pragma宏。使用data_seg pragma宏必须注意数据的初始化:
#pragma data_seg("MYSEC")
char MySharedData[4096]={0};
#pragma data_seg()
然后在用户的DEF文件中为有名的数据区设定共享属性。
LIBRARY TEST
DATA READ WRITE
SECTIONS
    .MYSEC READ WRITE SHARED

这样每个附属于DLL的进程都将接受到属于自己的数据拷贝,一个进程的数据变化并不会反映到其他进程的数据中。

在DEF文件中适当地输出数据。以下的DEF文件项说明了如何以常数变量的形式输出MySharedData。
EXPORTS
    MySharedData CONSTANT
最后在应用程序(进程)按外部变量引用共享数据。
extern _export"C"{char * MySharedData[];}
进程中使用该变量应注意间接引用。
m_pStatic=(CEdit*)GetDlgItem(IDC_SHARED);
m_pStatic->GetLine(0,*MySharedData,80);

3.3、用于传输只读数据的WM_COPYDATA

传输只读数据可以使用Win32中的WM_COPYDATA消息。该消息的主要目的是允许在进程间传递只读数据。Windows95在通过WM_COPYDATA消息传递期间,不提供继承同步方式。SDK文档推荐用户使用SendMessage函数,接受方在数据拷贝完成前不返回,这样发送方就不可能删除和修改数据:

SendMessage(hwnd,WM_COPYDATA,wParam,lParam);
其中wParam设置为包含数据的窗口的句柄。lParam指向一个COPYDATASTRUCT的结构:
typedef struct tagCOPYDATASTRUCT{
    DWORD dwData;//用户定义数据
    DWORD cbData;//数据大小
    PVOID lpData;//指向数据的指针
}COPYDATASTRUCT;
该结构用来定义用户数据。

3.4、直接调用ReadProcessMemory和WriteProcessMemory函数实现进程间通讯

通过调用ReadProcessMemory以及WriteProcessMemory函数用户可以按类似与Windows3.1的方法实现进程间通讯,在发送进程中分配一块内存存放数据,可以调用GlobalAlloc或者VirtualAlloc函数实现:
pApp->m_hGlobalHandle=GlobalAlloc(GMEM_SHARE,1024);
可以得到指针地址:
pApp->mpszGlobalHandlePtr=(LPSTR)GlobalLock
(pApp->m_hGlobalHandle);
在接收进程中要用到用户希望影响的进程的打开句柄。为了读写另一进程,应按如下方式调用OpenProcess函数:
HANDLE hTargetProcess=OpenProcess(
STANDARD_RIGHTS_REQUIRED|
PROCESS_VM_REDA|
PROCESS_VM_WRITE|
PROCESS_VM_OPERATION,//访问权限
FALSE,//继承关系
dwProcessID);//进程ID
为保证OpenProcess函数调用成功,用户所影响的进程必须由上述标志创建。
一旦用户获得一个进程的有效句柄,就可以调用ReadProcessMemory函数读取该进程的内存:
BOOL ReadProcessMemory(
HANDLE hProcess,    // 进程指针
LPCVOID lpBaseAddress,    // 数据块的首地址
LPVOID lpBuffer,    // 读取数据所需缓冲区
DWORD cbRead,    // 要读取的字节数
LPDWORD lpNumberOfBytesRead    
);
使用同样的句柄也可以写入该进程的内存:
BOOL WriteProcessMemory(
HANDLE hProcess,    // 进程指针
LPVOID lpBaseAddress,    // 要写入的首地址
LPVOID lpBuffer,    // 缓冲区地址
DWORD cbWrite,    // 要写的字节数
LPDWORD lpNumberOfBytesWritten
);   
如下所示是读写另一进程的共享内存中的数据:
ReadProcessMemory((HANDLE)hTargetProcess,
(LPSTR)lpsz,m_strGlobal.GetBuffer(_MAX_FIELD),
_MAX_FIELD,&cb);
WriteProcessMemory((HANDLE)hTargetProcess,
(LPSTR)lpsz,(LPSTR)STARS,
m_strGlobal.GetLength(),&cb);

4、进程之间的消息发送与接收

在实际应用中进程之间需要发送和接收Windows消息来通知进程间相互通讯,发送方发送通讯的消息以通知接收方,接收方在收到发送方的消息后就可以对内存进行读写操作。

我们在程序设计中采用Windows注册消息进行消息传递,首先在发送进程初始化过程中进行消息注册:
m_nMsgMapped=::RegisterWindowsMessage("Mapped");
m_nMsgHandle=::RegisterWindowsMessage("Handle");
m_nMsgShared=::RegisterWindowsMessage("Shared");
在程序运行中向接收进程发送消息:
CWnd* pWndRecv=FindWindow(lpClassName,"Receive");
pWndRecv->SendMessage(m_MsgMapped,0,0);
pWndRecv->SendMessage(m_nMsgHandle,
(UINT)GetCurrentProcessID(),(LONG)pApp->m_hGlobalHandle);
pWndRecv->SendMessage(m_nMsgShared,0,0);
可以按如下方式发送WM_COPYDATA消息:
static COPYDATASTRUCT cds;//用户存放数据
pWnd->SendMessage(WM_COPYDATA,NULL,(LONG)&cds);

接收方进程初始化也必须进行消息注册:

UNIT CRecvApp:: m_nMsgMapped=::RegisterWindowsMessage("Mapped");
UNIT CRecvApp::m_nMsgHandle=::RegisterWindowsMessage("Handle");
UNIT CRecvApp::m_nMsgShared=::RegisterWindowsMessage("Shared");
同时映射消息函数如下:
ON_REGISTERED_MASSAGE(CRecvApp::m_nMsgMapped,OnRegMsgMapped)
ON_REGISTERED_MASSAGE(CRecvApp::m_nMsgHandle,OnRegMsgHandle)
ON_REGISTERED_MASSAGE(CRecvApp::m_nMsgShared,OnRegMsgShared)
在这些消息函数我们就可以采用上述技术实现接收进程中数据的读写操作了。

5、结束语

从以上分析中我们可以看出Windows95的内存管理与Windows 3.x相比有很多的不同,对进程之间的通讯有较为严格的限制。这就确保了任何故障程序无法意外地写入用户的地址空间,而用户则可根据实际情况灵活地进行进程间的数据通讯,从这一点上来讲Windows95增强应用程序的强壮性。

标签:软件开发 | 浏览数(379) | 评论数(0) | 2007-01-17

作者:Amit Dey 译:刘涛
近来,我写了一个outlook2000的Addin Com作为我建立CRM 工具的工程的一部分。当我为这个工程写代码的时候,我想这可能是一个很好的题目,因为我在internet上找到的与Office相关的资料大部分是VB/VBA 相关的,几乎没有与ATL相关的。
在这篇文章里的代码并没有进行优化,为了使读者便于跟随,我尽量将它写的浅显易懂。我写这篇文章花了一些时间,并且也尽了我的最大努力,万一还存在什么错误,请爽快的给我发封邮件。如果你喜欢这篇文章或者觉得它读起来很有趣,并给我一个高的评价或是发邮件告诉我你的看法,我将非常高兴。谢谢!

概况:
通过这篇文章,我们将会了解怎样使用纯ATL Com 对象编写Outlook2000/2K+ COM addin程序。我们将从写一个最基本的Com AddIn程序开始。接下来我将向你们展示怎样将标准的界面元素比如工具栏或是菜单项加入到outlook中去,并响应他们的事件。紧接着,我们要为Outlook's Tools->Options加入我们自己编写的属性表。接着我们将看一些相关的注册键和ATL向导的一些非常有用的特征并且学习有效地使用他们。
虽然我们写的是一个Outlook2000 COM addin的程序。但是Office2000的应用程序,比如Word,Access等等,他们的Com AddIn的写法是非常相似的。除了注册键和接口,其余的部分基本上是一样的。
我假设你是一个VC++ Com的开发人员,并且也有一些基于ATL的组件开发和OLE/自动化方面的经验,尽管这也不是必须的。创建和测试这个AddIn程序,你必须安装Office2000,至少有outlook2000。程序代码使用VC++ 6.0 sp3+/ATL3.0创建,使用的操作系统是:安装了Office2000的Windows2000。

开始:
Office AddIn 是一个可以动态扩充和增强的Com 自动化组件,可以控制任何的Office应用程序。微软的Office2000和以后的版本都支持创建Add_Ins的一个新的、统一的应用设计架构。AddIn通常都被置于一个ActiveX动态库中(进程内服务器),并且能被用户动态的从主程序中引导和卸载。
Office AddIn 必须实现 _IDTExtensibility2 接口。IDTExtensibility2接口定义于MSADDin Designer typelibrary (MSADDNDR.dll/MSADDNDR.tlb)文件中。一般在/Program Files/Common Files/Designer目录下。
接口象这样定义:
enum {
ext_cm_AfterStartup = 0,
ext_cm_Startup = 1,
ext_cm_External = 2,
ext_cm_CommandLine = 3
} ext_ConnectMode;

enum {
ext_dm_HostShutdown = 0,
ext_dm_UserClosed = 1
} ext_DisconnectMode;

...
...
...

interface _IDTExtensibility2 : IDispatch {
[id(0x00000001)]
HRESULT OnConnection(
[in] IDispatch* Application,
[in] ext_ConnectMode ConnectMode,
[in] IDispatch* AddInInst,
[in] SAFEARRAY(VARIANT)* custom);
[id(0x00000002)]
HRESULT OnDisconnection(
[in] ext_DisconnectMode RemoveMode,
[in] SAFEARRAY(VARIANT)* custom);
[id(0x00000003)]
HRESULT OnAddInsUpdate([in] SAFEARRAY(VARIANT)* custom);
[id(0x00000004)]
HRESULT OnStartupComplete([in] SAFEARRAY(VARIANT)* custom);
[id(0x00000005)]
HRESULT OnBeginShutdown([in] SAFEARRAY(VARIANT)* custom);
};
所有的Com AddIn继承于IDTExtensibility2,而且必须实现他的五个方法。
当AddIn被引导和卸载的时候,OnConnection 和 OnDisconnection, 就像他们的名字显示的一样。AddIn程序可以被引导,也可以在应用程序使用过程中被用户启动或者通过自动化和enumerator ext_Connect 指示连接到那些模块。当一组Com AddIn组件被改变,那么OnAddinsUpdate被调用。OnStartupComplete 只有在应用程序使用过程中启动Com AddIn组件时才被调用,如果AddIn在主应用程序被关掉的时候断开与主应用程序的连接,那么OnBeginShutdown 被调用。

注册AddIn组件:
使用主应用程序注册AddIn组件,我们需要在注册表目录:
HKEY_CURRENT_USER"Software"Microsoft"Office"<TheOfficeApp>"Addins"<ProgID> 下创建两个子键,这里ProgID指的是Addin Com对象的唯一标识符。别的入口通过AddIn提供的关于他自己的信息和制定的引导选项给主应用程的是:
FriendlyName – 字符串 – 主应用程序显示的这个AddIn程序的名字。
Description – 字符串 – 对AddIn的描述.
LoadBehavior - DWORD 值. –一个决定AddIn怎样被主应用程序引导的值的组合。 设置成 0x03 表示主应用程序启动时引导,设置成0x08表示由用户来激活。
CommandLineSafe - DWORD 值. 0x01(TRUE) 或者 0x00(FALSE).
对于所有值和可选项的完整描述,请参考MSDN。

创建一个小的Com AddIn:
现在我们了解了足够的知识,应该朝前一步编写一个小的Outlook2K COM addin。创建一个新的ATL COM Appwizard 工程,命名为OutlookAddin。记住如果你把他命名成别的,他可能会不能运行(开个玩笑)。
在向导的第一个对话框中接收默认的服务器类型Dynamic Link Library(DLL),检查Allow merging of proxy-stub code,选择这个可选项,点击完成。接着点击OK,产生工程文件。
下一步,点击Insert->New ATL Object菜单项,通过从Category中选择Objects从Objects列表中选择Simple Object插入一个ATL simple object到工程中。点击Next,输入”AddIn”作为ShortName,在属性表里选上Support ISupportErrorInfo。接受剩下的默认选项,然后点击OK。
到现在为止,向导已经给我们了一个置于动态链接库中的自动化兼容的、DispInterface-savvy的进程内的Com对象。默认的情况下,一个加到Com对象上的指定注册值的注册脚本文件被提交给我们。Build这个工程,看看一切是否运行良好。
如果你想我一样雄心勃勃,起码在继续往下进行前还应该编译你工程中的.idl文件。现在就去做吧。
接下来我们为AddIn写一些特定的代码去实现IDTExtensibility2 接口。在类视图里,我们在CAddIn类上右键点击,选择Implement Interface,这将带出ATL Implement Interface 向导。点击Add Typelib,在Browse Typelibraries对话框里向下滚动,选上Microsoft Add-in Designer(1.0),点击OK。在AddinDesignerObjects列表中选择_IDTExtensibility2接口点击OK。
向导为IDTExtensibility2接口的五个方法中每一个生成默认的实现,将他们加到CAddIn类中,并且更新COM_INTERFACE_MAP()宏。当然在加有些有用的代码之前每个方法都只会返回E_NOTIMPL。现在,为ComAddIn进行必要的注册,我们的Com AddIn已经就绪了。
使用主应用程序注册我们的Addin组件。如果是outlook2000,打开工程的AddIn.rgs注册脚本文件。把下面的代码加到文件的结尾。
HKCU
{
Software
{
Microsoft
{
Office
{
Outlook
{
Addins
{
'OutlookAddin.Addin'
{
val FriendlyName = s 'ADOutlook2K Addin'
val Description = s 'ATLCOM Outlook Addin'
val LoadBehavior = d '00000008'
val CommandLineSafe = d '00000000'
}
}
}
}
}
}
}
既然我们希望在程序启动的时候AddIn被引导,那么LoadBehavior设置为3。现在Build这个工程。如果一切顺利,那么将会创建成功并且注册了这个AddIn。为了测试这个AddIn,我们要运行这个工程并输入完整的outlook.exe的完整的路径("Program Files"Microsoft Office"Office"Outlook.exe),或者在注册了这个DLL之后从VC++IDE环境外运行outlook。如果你的AddIn被成功的注册了,那么在outlook里,点击Tools->Options,在Other页点击Advanced Options->COM Addins,我们的AddIn应该已经出现在可获得的AddIns的列表中。字符串是我们在脚本中为'FriendlyName'指定的值。
AddIn可以被编写来执行各种不同的任务。典型的,包括为outlook添加一些界面元素,比如工具条和菜单项,而且用户可以控制AddIn。通过点击这个工具条按钮和菜单项,用户可以实现AddIn的功能。接下来我们将制作这样一个工具条和附加的菜单项。

命令与征服:
在Office应用程序中,菜单和工具条被组合在一个名叫“CommandBars “的完全可编程的集合中。CommandBars通常是可共享可编程的对象,并且作为所有的office应用程序的一部分被暴露。CommandBars代表一个同一的机制,通过他可以将单个的工具条和菜单项加到相应的应用程序里。每一个CommandBars由几个独立的CommandBar对象组成。每一个CommandBar又由CommandBarControl对象集合组成,这个集合被叫做CommandBarControls。
CommandBarControls代表了一个复杂的对象和组成它的子对象层次。一个CommandBarControl能被包含在一个CommandBar中,并且通过控件的CommandBar属性访问。最后每一个在控件的CommandBarControls集合中的CommandBarControl即可能是CommandBarComboBox、CommandBarButton(工具条按钮)也可能是CommandBarPopup(弹出式菜单)。我很希望我能画出一个代表这个对象层次的图例,但是我很不擅长这个(我很诚实!)。我保证在MSDN中一定有关于MS Office CommandBars描述的图例。
在我们的AddIn里,我想加入以下的界面元素:
? 在一个新的工具条里加入两个位图按钮。
? 在“Tool“菜单里添加一个新的带位图的弹出式菜单项。
首先,我们应该将office和outlook的类型库导入到我们的工程中。我们打开stdAfx.h,然后添加以下语句:
#import "C:"Program Files"Microsoft Office"Office"mso9.dll" "
rename_namespace("Office") named_guids
using namespace Office;

#import "C:"Program Files"Microsoft Office"Office"MSOUTL9.olb"
rename_namespace("Outlook"), raw_interfaces_only, named_guids
using namespace Outlook;
注意:你应该改变这些路径,是他们匹配你安装的office的路径。
好了,现在让我们来看看代码。首先式ToolBand和ToolBar Button。
在outlook模块里,Application 对象位于代表整个应用程序的对象层次的最顶层。通过他的ActiveExplorer 方法我们可以得到代表当前窗口的Explorer对象。下来我们使用GetCommandBars方法得到CommandBars对象(他是outlook工具条和菜单项的集合)。我们使用CommandBars集合的Add方法加上相应的参数就可以添加一个新的工具条。如果想向工具条中加入按钮只需要得到工具条的CommandBarControls集合,接着调用他的Add方法。最后我们为那些对应于按钮的CommandBarButton对象(我们可以用它来设置按钮的风格和别的属性,比如标题、提示和文本等等)。
代码片断如下:
STDMETHODIMP CAddin::OnConnection(IDispatch * Application,
ext_ConnectMode ConnectMode,
IDispatch * AddInInst, SAFEARRAY * * custom)
{

CComPtr < Office::_CommandBars> spCmdBars;
CComPtr < Office::CommandBar> spCmdBar;

// QI() for _Application
CComQIPtr <Outlook::_Application> spApp(Application);
ATLASSERT(spApp);
// get the CommandBars interface that represents Outlook's
//toolbars & menu items

CComPtr<Outlook::_Explorer> spExplorer;
spApp->ActiveExplorer(&spExplorer);

HRESULT hr = spExplorer->get_CommandBars(&spCmdBars);
if(FAILED(hr))
return hr;
ATLASSERT(spCmdBars);

// now we add a new toolband to Outlook
// to which we'll add 2 buttons
CComVariant vName("OutlookAddin");
CComPtr <Office::CommandBar> spNewCmdBar;

// position it below all toolbands
//MsoBarPosition::msoBarTop = 1
CComVariant vPos(1);

CComVariant vTemp(VARIANT_TRUE); // menu is temporary
CComVariant vEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
//Add a new toolband through Add method
// vMenuTemp holds an unspecified parameter
//spNewCmdBar points to the newly created toolband
spNewCmdBar = spCmdBars->Add(vName, vPos, vEmpty, vTemp);

//now get the toolband's CommandBarControls
CComPtr < Office::CommandBarControls> spBarControls;
spBarControls = spNewCmdBar->GetControls();
ATLASSERT(spBarControls);

//MsoControlType::msoControlButton = 1
CComVariant vToolBarType(1);
//show the toolbar?
CComVariant vShow(VARIANT_TRUE);

CComPtr < Office::CommandBarControl> spNewBar;
CComPtr < Office::CommandBarControl> spNewBar2;

// add first button
spNewBar = spBarControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow);
ATLASSERT(spNewBar);
// add 2nd button
spNewBar2 = spBarControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow);
ATLASSERT(spNewBar2);

_bstr_t bstrNewCaption(OLESTR("Item1"));
_bstr_t bstrTipText(OLESTR("Tooltip for Item1"));

// get CommandBarButton interface for each toolbar button
// so we can specify button styles and stuff
// each button displays a bitmap and caption next to it
CComQIPtr < Office::_CommandBarButton> spCmdButton(spNewBar);
CComQIPtr < Office::_CommandBarButton> spCmdButton2(spNewBar2);

ATLASSERT(spCmdButton);
ATLASSERT(spCmdButton2);

// to set a bitmap to a button, load a 32x32 bitmap
// and copy it to clipboard. Call CommandBarButton's PasteFace()
// to copy the bitmap to the button face. to use
// Outlook's set of predefined bitmap, set button's FaceId to //the
// button whose bitmap you want to use
HBITMAP hBmp =(HBITMAP)::LoadImage(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDB_BITMAP1),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);

// put bitmap into Clipboard
::OpenClipboard(NULL);
::EmptyClipboard();
::SetClipboardData(CF_BITMAP, (HANDLE)hBmp);
::CloseClipboard();
::DeleteObject(hBmp);
// set style before setting bitmap
spCmdButton->PutStyle(Office::msoButtonIconAndCaption);

HRESULT hr = spCmdButton->PasteFace();
if (FAILED(hr))
return hr;

spCmdButton->PutVisible(VARIANT_TRUE);
spCmdButton->PutCaption(OLESTR("Item1"));
spCmdButton->PutEnabled(VARIANT_TRUE);
spCmdButton->PutTooltipText(OLESTR("Tooltip for Item1"));
spCmdButton->PutTag(OLESTR("Tag for Item1"));

//show the toolband
spNewCmdBar->PutVisible(VARIANT_TRUE);

spCmdButton2->PutStyle(Office::msoButtonIconAndCaption);

//specify predefined bitmap
spCmdButton2->PutFaceId(1758);

spCmdButton2->PutVisible(VARIANT_TRUE);
spCmdButton2->PutCaption(OLESTR("Item2"));
spCmdButton2->PutEnabled(VARIANT_TRUE);
spCmdButton2->PutTooltipText(OLESTR("Tooltip for Item2"));
spCmdButton2->PutTag(OLESTR("Tag for Item2"));
spCmdButton2->PutVisible(VARIANT_TRUE);

//..........
//..........
//code to add new menubar to be added here
//read on
//..........
我们用相似的方法来给outlook的Tools菜单添加菜单项,我们照以下方法做。CommandBars的ActiveMenuBar属性返回一个表示在Application容器中活动的菜单。我们通过GetControls方法找到活动的菜单控件集合。我们想要加入一个弹出式的菜单项到outlook的Tools菜单(第6个菜单项),我们从Activemenubars控件集合中可以找到第6个菜单项,直接调用Add方法创建一个新的菜单项并且将他连接到Tools菜单。这里没有什么新东西。
相应的代码片断如下所示:
//......
//code to add toolbar here
//......

_bstr_t bstrNewMenuText(OLESTR("New Menu Item"));
CComPtr < Office::CommandBarControls> spCmdCtrls;
CComPtr < Office::CommandBarControls> spCmdBarCtrls;
CComPtr < Office::CommandBarPopup> spCmdPopup;
CComPtr < Office::CommandBarControl> spCmdCtrl;

// get CommandBar that is Outlook's main menu
hr = spCmdBars->get_ActiveMenuBar(&spCmdBar);
if (FAILED(hr))
return hr;
// get menu as CommandBarControls
spCmdCtrls = spCmdBar->GetControls();
ATLASSERT(spCmdCtrls);

// we want to add a menu entry to Outlook's 6th(Tools) menu //item
CComVariant vItem(5);
spCmdCtrl= spCmdCtrls->GetItem(vItem);
ATLASSERT(spCmdCtrl);

IDispatchPtr spDisp;
spDisp = spCmdCtrl->GetControl();

// a CommandBarPopup interface is the actual menu item
CComQIPtr < Office::CommandBarPopup> ppCmdPopup(spDisp);
ATLASSERT(ppCmdPopup);

spCmdBarCtrls = ppCmdPopup->GetControls();
ATLASSERT(spCmdBarCtrls);

CComVariant vMenuType(1); // type of control - menu
CComVariant vMenuPos(6);
CComVariant vMenuEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
CComVariant vMenuShow(VARIANT_TRUE); // menu should be visible
CComVariant vMenuTemp(VARIANT_TRUE); // menu is temporary


CComPtr < Office::CommandBarControl> spNewMenu;
// now create the actual menu item and add it
spNewMenu = spCmdBarCtrls->Add(vMenuType, vMenuEmpty, vMenuEmpty,
vMenuEmpty, vMenuTemp);
ATLASSERT(spNewMenu);

spNewMenu->PutCaption(bstrNewMenuText);
spNewMenu->PutEnabled(VARIANT_TRUE);
spNewMenu->PutVisible(VARIANT_TRUE);

//we'd like our new menu item to look cool and display
// an icon. Get menu item as a CommandBarButton
CComQIPtr < Office::_CommandBarButton> spCmdMenuButton(spNewMenu);
ATLASSERT(spCmdMenuButton);
spCmdMenuButton->PutStyle(Office::msoButtonIconAndCaption);

// we want to use the same toolbar bitmap for menuitem too.
// we grab the CommandBarButton interface so we can add
// a bitmap to it through PasteFace().
spCmdMenuButton->PasteFace();
// show the menu
spNewMenu->PutVisible(VARIANT_TRUE);

return S_OK;
}
点击F5,如果一切都没问题,那么工程将成功建立,并且你将第一次看见你的AddIn程序的运行。现在我们运行outlook来测试我们的AddIn。在'Executable for Debug'对话框,设置outlook可执行程序的当前路径,现在我们准备测试。在outlook中点击Tools->Option,点击Other页面,点击Advanced Options。在Advanced Option对话框中,点击Com AddIns 按钮。接着从可获得的AddIns列表中选择我们的AddIn并点击OK。当我们的AddIn被引导,一个停靠工具条将被创建,你也可以看到你加入到Tools菜单的菜单项。
他们就在那里!一个有你写的AddIn的outlook,一个带有很酷的工具条和新的菜单项的扩展的outlook!感谢ATL!你的小于50Kb的AddIn同样提供了轻量级的有意义的Com服务。享受这一刻吧!

单单放置两个工具条按钮和一个菜单项并没有什么用处,除非我们写命令处理代码和响应他们的事件。现在我们回到正题。当然在这里,点击不同的按钮和菜单项,我们紧紧弹出简单的对话框。这就是你添加AddIn功能的地方。从CRM 工具、自动联系管理、邮件通知、邮件过滤到高级的文档管理到各种各样的应用,Com AddIns可以执行各种各样的任务的验证。
CommandBarButton控件暴露了一个点击事件(当用户点击一个Command Bar 按钮时触发)。当用户点击工具条按钮或者是点击菜单项的时候我们将使用这个事件去运行代码。对于这些,我们的Com AddIn对象不得不处理_CommandBarButtonEvents事件。点击事件被声明如下:
//...
//....Office objects typelibrary
//....

[id(0x00000001), helpcontext(0x00038271)]
void Click(
[in] CommandBarButton* Ctrl,
[in, out] VARIANT_BOOL* CancelDefault);

//....
//...
我们不得不做所有我们能做的事情去实现那些将被事件源通过规范的连接点协议调用的接收器接口(无论什么时候一个工具条按钮或菜单项被点击)。通过回调函数我们可以得到一个源CommandBarButton 对象的指针和一个用来接受和取消默认操作的布尔值。就像实现一个dispatch接收器接口一样,那也不是什么新东西,作为一个ATL程序员你可能要花一段时间去做这些。
但是对于那些非初始化的,ATL为ATLCom对象提供两个模板类IDispEventImpl<> 和 IDispEventSimpleImpl<> ,这为IDispatch接口提供了实现。我更喜欢用轻量级的IDispEventSimpleImpl,因为它不需要另外的类型库信息。你的类紧紧源于IDispEventSimpleImpl<>。建立你的接收器映射,通过_ATL_SINK_INFO结构体设置你的回调参数,最后调用DispEventAdvise 和 DispEventUnadvise从源接口连接和断开。对于我们的工具条按钮和菜单项,如果我们要写一个单一的回调函数来处理所有的事件,那么,一旦我们有一个指向触发事件的CommandBarButton的指针,我们可以使用GetCaption去得到这个按钮的文本,在这个基础上,我们可以执行一些选择性的动作。但是对于这个例子,我们为每一个事件编写一个回调函数。
下面是编写的步骤:
使你的类继承于IDispSimpleEventImpl-第一个参数是封装在ActiveX控件中的子窗口的ID。但是对于我们来说,它可以是任何预先定义的唯一标识事件源的整数(在这里指的是第一个工具条按钮)。
class ATL_NO_VTABLE CAddin :
public CComObjectRootEx < CComSingleThreadModel>,
.....
.....
public IDispEventSimpleImpl<1,CAddin,&__uuidof(Office::_CommandBarButtonEvents>
建立回调函数-第一个我们定义的,如下所示:
void __stdcall OnClickButton(IDispatch * /*Office::_CommandBarButton**/ Ctrl,VARIANT_BOOL * CancelDefault);
接下来我们使用_ATL_SINK_INFO结构去描述回调参数。打开AddIn.h文件,在文件顶部添加如下声明:
? extern _ATL_FUNC_INFO OnClickButtonInfo;
接着打开AddIn.cpp,添加如下定义:
? _ATL_FUNC_INFO OnClickButtonInfo ={CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};
OnClickButton是非常基础的,就像下面的:
? void __stdcall CAddin::OnClickButton(IDispatch* /*Office::_CommandBarButton* */ Ctrl,
? VARIANT_BOOL * CancelDefault)
? {
? USES_CONVERSION;
? CComQIPtr<Office::_CommandBarButton> pCommandBarButton(Ctrl);
? //the button that raised the event. Do something with this...
? MessageBox(NULL, "Clicked Button1", "OnClickButton", MB_OK);
?
? }
我们使用ATL宏BEGIN_SINK_MAP() 和 END_SINK_MAP()建立接收器消息映射。接收器消息映射由SINK_ENTRY_XXX组成。接收器消息映射提供定义事件的Dispatch ID和处理他的成员函数。
? BEGIN_SINK_MAP(CAddin)
? SINK_ENTRY_INFO(1, __uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01,
? OnClickButton, &OnClickButtonInfo)
? END_SINK_MAP()
现在每一件事情都到位了,我们不得不使用DispEventAdvise() and DispEventUnadvise()连接和断开事件源.我们的CAddIn类的OnConnection() 和OnDisconnection()仅仅是替代了这些。对于DispEventAdvise() and DispEventUnadvise()的参数分别是事件源上的任何的接口和任何被期望的事件源上的接口。
//connect to event source in OnConnection
// m_spButton member variable is a smart pointer to _CommandBarButton
// that is used to cache the pointer to the first toolbar button.

DispEventAdvise((IDispatch*)m_spButton,&DIID__CommandBarButtonEvents);

//when I'm done disconnect from the event source
//some where in OnDisconnection()

DispEventUnadvise((IDispatch*)m_spButton);
为我们的命令按钮和菜单项实现Dispatch 接收器是很相似的,写处理代码并且连接和断开他们就像上面的描述。如果每一步都进行的畅通无阻,在你Rebuild你的程序并且运行它。无论什么时候,按钮和菜单项被点击,你的回调函数将被执行。

添加属性页:
在这篇文章里我们最后要学会去做的是添加我们自己的“Option“属性页到outlook的Tools->Option的属性表中。
下来我们要加一个页到outlook的option菜单里作为我们我们的AddIn的一部分。我们将象ActiveX控件一样实现实现属性页。当用户点击Tools->Option菜单项,应用程序对象发出一个OptionsPagesAdd事件(通过outlook对象模块中的_ApplicationEvents接口)。
dispinterface ApplicationEvents
{
....

[id(0x0000f005), helpcontext(0x0050df87)]
void OptionsPagesAdd([in] PropertyPages* Pages);
....
}

[
odl,
uuid(00063080-0000-0000-C000-000000000046),
helpcontext(0x0053ec78),
dual,
oleautomation
]
....
....

interface PropertyPages : IDispatch {
[id(0x0000f000), propget, helpcontext(0x004deb87)]
HRESULT Application([out, retval] _Application** Application);
....
....

[id(0x0000005f), helpcontext(0x00526624)]
HRESULT Add([in] VARIANT Page,
[in, optional] BSTR Title);

[id(0x00000054), helpcontext(0x00526625)]
HRESULT Remove([in] VARIANT Index);
};

OptionsPagesAdd事件传递给我们我们一个PropertyPages Dispatch接口,他的Add方法用来添加页。Add方法的参数是我们的控件的ProgID和新的页的标题文本。相似的,我们调用Remove()方法和要删除页的索引来删除页。
现在我们来加一个ActiveX复合控件。我们点击Insert->new ATL Object.从Category中选择Controls,从Object列表中选择Lite Composite Control,点击OK。在ShortName中输入PropPage,在属性页面选上Support ISupportErrorInfo选项。点击Ok,接受所有的默认选项。
现在我们要来实现PropertyPage接口。在类视图里右键点击CPropPage,选择Implement Interface,点击Add TypeLib按钮。选中Microsoft Outlook 9.0 Object Library 点击OK。从接口列表中选择PropertyPage点击OK。
向导自动为PropertyPage接口添加三个方法:Apply()、Get_Dirty()、GetPageInfo()。现在做下面的修改,在Com Map中把这一行:
COM_INTERFACE_ENTRY(IDispatch)
改成:
COM_INTERFACE_ENTRY2(IDispatch,IPropPage)
以排除不明确的地方。
接下来实现IDispatch,我们使用IDispatchImpl<>模板类。我们在CPropPage类的声明部分用以下代码:
public IDispatchImpl < Outlook::PropertyPage,&__uuidof(Outlook::PropertyPage),
&LIBID_OUTLOOKADDINLib>
替换掉:
class ATL_NO_VTABLE CPropPage :
public CComObjectRootEx<CComSingleThreadModel>,
public IDispatchImpl<IPropPage, &IID_IPropPage, &LIBID_TRAILADDINLib>,
....
....
public PropertyPage
从PropPage.h文件的顶部删掉多余的#import语句。类型库已经在stdAfx.h中导入了,因此这里没有必要再导入。
下来我们要连接和断开ApplicationEvents接口,并为他写回调函数。你已经知道该做什么了。我们再次使用IDispEventSimpleImpl<>为ApplicationEvents建立Dispatch接收器,更新接收器映射,为OptionsAddPage事件写回调函数。因为我们多次使用了IDispEventSimpleImpl<>, 我们为每一个接口事件使用TypeDef。代码片段如下:
extern _ATL_FUNC_INFO OnOptionsAddPagesInfo;

class ATL_NO_VTABLE CAddin :
....
....
public IDispEventSimpleImpl<4,CAddin,&__uuidof(Outlook::ApplicationEvents)>
{
public:
//typedef for applicationEvents sink implementation
typedef IDispEventSimpleImpl</*nID =*/ 4,CAddin,
&__uuidof(Outlook::ApplicationEvents)> AppEvents;
....
....
....
BEGIN_SINK_MAP(CAddin)
....
SINK_ENTRY_INFO(4,__uuidof(Outlook::ApplicationEvents),
/*dispid*/0xf005,OnOptionsAddPages,&OnOptionsAddPagesInfo)
END_SINK_MAP()

public:
//callback method for OptionsAddPages event
void __stdcall OnOptionsAddPages(IDispatch *Ctrl);
};

//in PropPage.cpp file

_ATL_FUNC_INFO OnOptionsAddPagesInfo = (CC_STDCALL,VT_EMPTY,1,{VT_DISPATCH}};

void __stdcall CAddin::OnOptionsAddPages(IDispatch* Ctrl)
{
CComQIPtr<Outlook::PropertyPages> spPages(Ctrl);
ATLASSERT(spPages);

//ProgId of the propertypage control
CComVariant varProgId(OLESTR("OutlookAddin.PropPage"));

//tab text
CComBSTR bstrTitle(OLESTR("OutlookAddin"));

HRESULT hr = spPages->Add((_variant_t)varProgId,(_bstr_t)bstrTitle);
if(FAILED(hr))
ATLTRACE(""nFailed adding propertypage");
}
最后,在OnConnection和OnDisConnection里,调用DispEventAdvise 和 DispEventUnadvise连接和断开ApplicationEvents。现在一切就绪,我们ReBuild工程。下来点击F5,点击Outlook的Tools->Options菜单。你应该看见了我们新加的页。但是当我们点击这个新的页,一个对话框将出现告诉我们属性页不能被显示。发生了什么?难道我们的辛苦劳动白费了?
发生这个情况的原因是:尽管我们的属性页创建了,但是outlook并没有得到关于这个页的键盘行为的任何信息。IOleControl的GetControlInfo方法的ATL的默认实现返回E_NOTIMPL,因此包容器无法为这个属性页和包容器处理击键事件。因此我们的页不能被显示。修改这个问题,只需重载GetControlInfo()方法,让他返回S_OK。
在.PropPage.h里添加如下声明:
STDMETHOD(GetControlInfo)(LPCONTROLINFO lpCI);
我们在PropPage.cpp文件里重载GetControlInfo()方法,仅仅将返回值改为S_OK,代码如下:
STDMETHODIMP CPropPage::GetControlInfo(LPCONTROLINFO lpCI)
{
return S_OK;
}
就是这些了。现在再次Build工程,点击outlook的tools->Option,激活我们的页,现在我们的属性页应该正确无误的显示了。
我们的学习要结束了。我们能在office里我们能做的事情无穷无尽。因为在一个AddIn里你可以获得父应用程序的内部对象模块,你能做所有主应用程序能做的事,或者更多。另外你也能使用别的接口比如MS Assistant(并不直接关联到应用程序)。没有做不到的只有想不到的。

 

标签:软件开发 | 浏览数(619) | 评论数(0) | 2007-01-17

函数调用方式分为两类:标准调用约定、C/C++调用约定。

标准调用约定(__stdcall):这些函数将在返回到调用者之间将参数从栈中删除。

C/C++调用约定(__cdecl):栈的清理工作由调用者来完成。

除C/C++之外的其他语言缺省情况下使用标准调用。

MICROSOFT平台上的COM接口提供的所有函数使用的均是标准调用约定,参数可变的函数使用的则是C调用约定。

WINDEF.H中

#define pascal __stdcall

OBJBASE.H中

#define STDMETHODCALLTYPE __stdcall

 

标签:软件开发 | 浏览数(342) | 评论数(0) | 2007-01-05

OFFICE WIKI

 

该网站查询很方便,点击网页中的Office wiki标头位置即可进行查询

 

标签:软件开发 | 浏览数(404) | 评论数(0) | 2006-12-18

导入PPT所需类型库

 

#import "C:"Program Files"Microsoft Office"Office"mso9.dll" rename_namespace("Office") "

rename("RGB","OfficeRGB") "

rename("DocumentProperties","OfficeDocumentProperties")

using namespace Office;

#import "C:"Program Files"Common Files"Microsoft Shared"VBA"VBA6"VBE6EXT.olb" rename_namespace("VBE6")

using namespace VBE6;

#import "C:"Program Files"Microsoft Office"Office"MSPPT9.OLB" named_guids,rename_namespace("MSPPT") "

rename("RGB","PPTRGB")

using namespace MSPPT;

 

标签:软件开发 | 浏览数(585) | 评论数(0) | 2006-12-15

摘 要:介绍了动态链接库这种模块复用方法及在VC中对它的调用,并给出了一个通过复用来实现数据加密的具体实例。

  关键词:VC DLL 模块复用 数据加密

  引言

  模块化思想贯穿于软件工程各个发展阶段,模块复用是构建大系统的一种重要思想。模块复用方法有:函数、函数库、动态链接库、COM。其都是基于模块化的基本思想。函数是最简单的模块化思想,也是后面方法的基础,甚至是一个应用程序的基础。函数库是函数的组合,一般将一些功能相似的函数放在一起作为函数库,这种函数库通常叫做静态库,其链接方式是静态的。COM即组件对象模型,是一种集成技术,可以使程序在运行时把各种不相关的软件程序混合在一起,而不必考虑这些不相关的程序是用什么语言编写的,它也是一种标准或者称为协议,负责将一个软件模块和另一个软件连接起来。动态链接库DLL(Dynamic Link Library)是一个可以被其它应用程序共享的程序模块,其中封装了一些可以被共享的例程和资源,其链接方式是动态的。动态链接库文件的扩展名一般是dll,也有可能是fon、sys和dry,它和可执行文件(.exe)非常相似,区别在于DLL中虽然包含了可执行代码却不能单独执行,而应由Windows应用程序直接或间接调用。Windows操作系统包含大量动态链接库,其中最主要的是KERNEL32.DLL、USER32.DLL、GDI32.DLL 。

  DLL的调用

  调用DLL,首先需要将DLL文件映像到用户进程的地址空间中,然后才能进行函数调用,这个函数和进程内部一般函数的调用方法相同。Windows提供了两种将DLL映像到进程地址空间的方法:

  1、隐式的加载时链接

  这种方法需要DLL工程经编译产生的LIB文件,此文件中包含了DLL允许应用程序调用的所有函数的列表,当链接器发现应用程序调用了LIB文件列出的某个函数,就会在应用程序的可执行文件的文件映像中加入一些信息,这些信息指出了包含这个函数的DLL文件的名字。当这个应用程序运行时,也就是它的可执行文件被操作系统产生映像文件时,系统会查看这个映像文件中关于DLL的信息,然后将这个DLL文件映像到进程的地址空间。

  系统通过DLL文件的名称,试图加载这个文件到进程地址空间时,它寻找DLL 文件的路径按照先后顺序如下:

  烦绦蛟诵惺钡哪柯迹纯芍葱形募诘哪柯迹?br />
  返鼻俺绦蚬ぷ髂柯?br />
  废低衬柯迹憾杂赪indows95/98来说,可以调用GetSystemDirectory函数来得到,对于WindowsNT/2000来说,指的是32位Windows的系统目录,也可以调用GetSystemDirectory函数来得到,得到的值为SYSTEM32。

  稺indows目录

  妨性赑ATH环境变量中的所有目录

  VC中加载DLL的LIB文件的方法有以下三种:

  ①LIB文件直接加入到工程文件列表中

  在VC中打开File View一页,选中工程名,单击鼠标右键,然后选中"Add Files to Project"菜单,在弹出的文件对话框中选中要加入DLL的LIB文件即可。

  ②设置工程的 Project Settings来加载DLL的LIB文件

  打开工程的 Project Settings菜单,选中Link,然后在Object/library modules下的文本框中输入DLL的LIB文件。

  ③通过程序代码的方式

  加入预编译指令#pragma comment (lib,"*.lib"),这种方法优点是可以利用条件预编译指令链接不同版本的LIB文件。因为,在Debug方式下,产生的LIB文件是Debug版本,如Regd.lib;在Release方式下,产生的LIB文件是Release版本,如Regr.lib。

  当应用程序对DLL的LIB文件加载后,还需要把DLL对应的头文件(*.h)包含到其中,在这个头文件中给出了DLL中定义的函数原型,然后声明。

  2、显式的运行时链接

  隐式链接虽然实现较简单,但除了必须的*.dll文件外还需要DLL的*.h文件和*.lib文件,在那些只提供*.dll文件的场合就无法使用,而只能采用显式链接的方式。这种方式通过调用API函数来完成对DLL的加载与卸载,其能更加有效地使用内存,在编写大型应用程序时往往采用此方式。这种方法编程具体实现步骤如下:

  ①使用Windows API函数Load Library或者MFC提供的AfxLoadLibrary将DLL模块映像到进程的内存空间,对DLL模块进行动态加载。

  ②使用GetProcAddress函数得到要调用DLL中的函数的指针。

  ③不用DLL时,用Free Library函数或者AfxFreeLibrary函数从进程的地址空间显式卸载DLL。

  VC中调用实例

  数据加密是计算机安全领域的重要内容,其基本思想是通过变换信息的表现形式来保护敏感信息,使非授权者不能了解被保护信息的内容。常见的数据加密算法有:DES,IDEA,RSA,ECC,AES,MD5,SHA等。

  《共享软件加密算法库》是一款针对个人、企业开发共享软件的加密工具,支持Windows平台下各类开发工具:VC、VB、Delphi、PB、VFP等,算法库集成的算法有:BlowFish、MD5、Secret16、AES、SHA、CRC32、RSA、DES、字符串加/解密、文件加/解密等多种功能强大的算法。其提供了DLL文件-Reg.dll,可以通过复用它来实现数据加密与解密。

  1、隐式链接

  其提供了 Reg.h与Reg.lib两个隐式链接所必须的文件,所以可以采用此种方式。

  ①在VC中打开File View一页,选中工程名,单击鼠标右键,然后选中"Add Files to Project"菜单,在弹出的文件对话框中选中要加入Reg.lib。

  ②在VC中打开File View一页,选中Header files,单击鼠标右键,然后选中"Add Files to Folder"菜单,在弹出的文件对话框中选中要加入Reg.h,然后在工程相应的头文件中加入#include "Reg.h"。在Reh.h头文件中给出了DLL中定义的函数原型及声明。

  如:加密函数原型及声明为extern "C" BOOL WINAPI File Encrypt(LPCTSTR lpInputFileName, LPCTSTR lpOutputFileName, LPCTSTR lpKey, LPCTSTR lpRegisterCode);解密函数原型及声明为extern "C" BOOL WINAPI File Decrypt(LPCTSTR lpInputFileName, LPCTSTR lpOutputFileName, LPCTSTR lpKey, LPCTSTR lpRegisterCode)。其中对于WINAPI宏,把它加到函数原型定义前,系统会把它翻译为适当的调用方式,在Win32中,是把它翻译为_stdcall调用方式。

  ③直接调用所需要的加密与解密函数,如调用File Encrypt()函数实现文本文件和二进制文件的加密,调用File Decrypt()函数实现文本文件和二进制文件的解密,调用时的参数要与函数定义参数相符合。

  2、显式链接

  如果只提供Reg.dll一个文件,那么须用此种方式。

  ①加密模块:调用File Encrypt()函数实现文本文件和二进制文件的加密。

//装载加密/解密DLL
HINSTANCE hdll=::Load Library ("Reg.dll");
//通过类型定义语句typedef来定义函数指针类型
Typedef BOOL (_stdcall *lpFileEncrypt)(LPCTSTR, LPCTSTR, LPCTSTR, LPCTSTR);
//函数声明
LpFileEncrypt FileEncrypt1;
//获取加密函数File Encrypt的函数指针
FileEncrypt1=(lpFileEncrypt)::GetProcAddress(hdll,"FileEncrypt");
//调用DLL中加密函数File Encrypt对文件加密,user-12345678为软件注册号
FileEncrypt1(加密源文件名,加密生成目标文件名,密码,"user-12345678");
//释放DLL模块
::AfxFreeLibrary(hdll);

  ②解密模块:调用File Decrypt()函数实现文本文件和二进制文件的解密。

//装载加密/解密DLL
HINSTANCE hdll=::Load Library ("Reg.dll");
//通过类型定义语句typedef来定义函数指针类型
Typedef BOOL (_stdcall *lpFileDecrypt)(LPCTSTR, LPCTSTR, LPCTSTR, LPCTSTR);
//函数声明
LpFileDecrypt FileDecrypt2;
//获取解密函数File Decrypt的函数指针
FileDecrypt2=(lpFileDecrypt)::GetProcAddress(hdll,"FileDecrypt");
//调用DLL中解密函数FileDecrypt对文件加密,user-12345678为软件注册号
FileDecrypt2(解密源文件名,解密生成目标文件名,密码,"user-12345678");
//释放DLL模块
::AfxFreeLibrary(hdll);

  结束语

  利用DLL这种模块复用方法可以减少软件工程开发的工作量,增强代码的可移植性,降低模块测试的复杂性,从总体上提高软件工程的开发效率。

 

标签:软件开发 | 浏览数(376) | 评论数(0) | 2006-12-14

12.1 多任务、进程和线程

 

12.1.1 Windows 3.x的协同多任务

  在16位的Windows 3.x中,应用程序具有对CPU的控制权。只有在调用了GetMessage、PeekMessage、WaitMessage或Yield后,程序才有可能把CPU控制权交给系统,系统再把控制权转交给别的应用程序。如果应用程序在长时间内无法调用上述四个函数之一,那么程序就一直独占CPU,系统会被挂起而无法接受用户的输入。

  因此,在设计16位的应用程序时,程序员必须合理地设计消息处理函数,以使程序能够尽快返回到消息循环中。如果程序需要进行费时的操作,那么必须保证程序在进行操作时能周期性的调用上述四个函数中的一个。

  在Windows 3.x环境下,要想设计一个既能执行实时的后台工作(如对通信端口的实时监测和读写),又能保证所有界面响应用户输入的单独的应用程序几乎是不可能的。

  有人可能会想到用CWinApp::OnIdle函数来执行后台工作,因为该函数是程序主消息循环在空闲时调用的。但OnIdle的执行并不可靠,例如,如果用户在程序中打开了一个菜单或模态对话框,那么OnIdle将停止调用,因为此时程序不能返回到主消息循环中!在实时任务代码中调用PeekMessage也会遇到同样的问题,除非程序能保证用户不会选择菜单或弹出模态对话框,否则程序将不能返回到PeekMessage的调用处,这将导致后台实时处理的中断。

  折衷的办法是在执行长期工作时弹出一个非模态对话框并禁止主窗口,在消息循环内分批执行后台操作。对话框中可以显示工作的进度,也可以包含一个取消按钮以让用户有机会中断一个长期的工作。典型的代码如清单12.1所示。这样做既可以保证工作实时进行,又可以使程序能有限地响应用户输入,但此时程序实际上已不能再为用户干别的事情了。

清单12.1 在协同多任务环境下防止程序被挂起的一种方法

bAbort=FALSE;

lpMyDlgProc=MakeProcInstance(MyDlgProc, hInst);

hMyDlg=CreateDialog(hInst, “Abort”, hwnd, lpMyDlgProc); //创建一个非模态对话框

ShowWindow(hMyDlg, SW_NORMAL);

UpdateWindow(hMyDlg);

EnableWindow(hwnd, FALSE); //禁止主窗口

. . .

while(!bAbort)

{

. . . //执行一次后台操作

. . .

while(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))

{

if(!IsDialogMessage(hMyDlg, &msg))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

}

}

EnableWindow(hwnd, TRUE); //允许主窗口

DestroyWindow(hMyDlg);

FreeProcInstance(lpMyDlgProc);

 

12.1.2 Windows 95/NT的抢先式多任务

  在32位的Windows系统中,采用的是抢先式多任务,这意味着程序对CPU的占用时间是由系统决定的。系统为每个程序分配一定的CPU时间,当程序的运行超过规定时间后,系统就会中断该程序并把CPU控制权转交给别的程序。与协同式多任务不同,这种中断是汇编语言级的。程序不必调用象PeekMessage这样的函数来放弃对CPU的控制权,就可以进行费时的工作,而且不会导致系统的挂起。

  例如,在Windows3.x 中,如果某一个应用程序陷入了死循环,那么整个系统都会瘫痪,这时唯一的解决办法就是重新启动机器。而在Windows 95/NT中,一个程序的崩溃一般不会造成死机,其它程序仍然可以运行,用户可以按Ctrl+Alt+Del键来打开任务列表并关闭没有响应的程序。

12.1.3 进程与线程

  在32位的Windows系统中,术语多任务是指系统可以同时运行多个进程,而每个进程也可以同时执行多个线程。

  进程就是应用程序的运行实例。每个进程都有自己私有的虚拟地址空间。每个进程都有一个主线程,但可以建立另外的线程。进程中的线程是并行执行的,每个线程占用CPU的时间由系统来划分。

  可以把线程看成是操作系统分配CPU时间的基本实体。系统不停地在各个线程之间切换,它对线程的中断是汇编语言级的。系统为每一个线程分配一个CPU时间片,某个线程只有在分配的时间片内才有对CPU的控制权。实际上,在PC机中,同一时间只有一个线程在运行。由于系统为每个线程划分的时间片很小(20毫秒左右),所以看上去好象是多个线程在同时运行。

  进程中的所有线程共享进程的虚拟地址空间,这意味着所有线程都可以访问进程的全局变量和资源。这一方面为编程带来了方便,但另一方面也容易造成冲突。

  虽然在进程中进行费时的工作不会导致系统的挂起,但这会导致进程本身的挂起。所以,如果进程既要进行长期的工作,又要响应用户的输入,那么它可以启动一个线程来专门负责费时的工作,而主线程仍然可以与用户进行交互。

12.1.4 线程的创建和终止

  线程分用户界面线程和工作者线程两种。用户界面线程拥有自己的消息泵来处理界面消息,可以与用户进行交互。工作者线程没有消息泵,一般用来完成后台工作。

  MFC应用程序的线程由对象CWinThread表示。在多数情况下,程序不需要自己创建CWinThread对象。调用AfxBeginThread函数时会自动创建一个CWinThread对象。

  例如,清单12.2中的代码演示了工作者线程的创建。AfxBeginThread函数负责创建新线程,它的第一个参数是代表线程的函数的地址,在本例中是MyThreadProc。第二个参数是传递给线程函数的参数,这里假定线程要用到CMyObject对象,所以把pNewObject指针传给了新线程。线程函数MyThreadProc用来执行线程,请注意该函数的声明。线程函数有一个32位的pParam参数可用来接收必要的参数。

清单12.2 创建一个工作者线程

//主线程

pNewObject = new CMyObject;

AfxBeginThread(MyThreadProc, pNewObject);

 

//新线程

UINT MyThreadProc( LPVOID pParam )

{

CMyObject* pObject = (CMyObject*)pParam;

 

if (pObject == NULL ||

!pObject->IsKindOf(RUNTIME_CLASS(CMyObject)))

return -1; // 非法参数

 

// 用pObject对象来完成某项工作

 

return 0; // 线程正常结束

}

 

 

AfxBeginThread的声明为:

CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

 

  参数pfnThreadProc是工作线程函数的地址。pParam是传递给线程函数的参数。nPriority是线程的优先级,一般是THREAD_PRIORITY_NORMAL,若为0,则使用创建线程的优先级。nStackSize说明了线程的堆栈尺寸,若为0则堆栈尺寸与创建线程相同。dwCreateFlags指定了线程的初始状态,如果为0,那么线程在创建后立即执行,如果为CREATE_SUSPENDED,则线程在创建后就被挂起。参数lpSecurityAttrs用来说明保密属性,一般为0。函数返回新建的CWinThread对象的指针。

  程序应该把AfxBeginThread返回的CWinThread指针保存起来,以便对创建的线程进行控制。例如,可以调用CWinThread::SetThreadPriority来设置线程的优先级,用CWinThread::SuspendThread来挂起线程。如果线程被挂起,那么直到调用CWinThread::ResumeThread后线程才开始运行。

  如果要创建用户界面线程,那么必须从CWinThread派生一个新类。事实上,代表进程主线程的CWinApp类就是CWinThread的派生类。派生类必须用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏来声明和实现。需要重写派生类的InitInstance、ExitInstance、Run等函数。

  可以使用AfxBeginThread函数的另一个版本来创建用户界面线程。函数的声明为:

CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

  参数pThreadClass指向一个CRuntimeClass对象,该对象是用RUNTIME_CLASS宏从CWinThread的派生类创建的。其它参数以及函数的返回值与第一个版本的AfxBeginThread是一样的。

  当发生下列事件之一时,线程被终止:

线程调用ExitThread。

线程函数返回,即线程隐含调用了ExitThread。

ExitProcess被进程的任一线程显示或隐含调用。

用线程的句柄调用TerminateThread。

用进程句柄调用TerminateProcess。

12.2 线程的同步

 

  多线程的使用会产生一些新的问题,主要是如何保证线程的同步执行。多线程应用程序需要使用同步对象和等待函数来实现同步。

12.2.1 为什么需要同步

  由于同一进程的所有线程共享进程的虚拟地址空间,并且线程的中断是汇编语言级的,所以可能会发生两个线程同时访问同一个对象(包括全局变量、共享资源、API函数和MFC对象等)的情况,这有可能导致程序错误。例如,如果一个线程在未完成对某一大尺寸全局变量的读操作时,另一个线程又对该变量进行了写操作,那么第一个线程读入的变量值可能是一种修改过程中的不稳定值。

  属于不同进程的线程在同时访问同一内存区域或共享资源时,也会存在同样的问题。

  因此,在多线程应用程序中,常常需要采取一些措施来同步线程的执行。需要同步的情况包括以下几种:

在多个线程同时访问同一对象时,可能产生错误。例如,如果当一个线程正在读取一个至关重要的共享缓冲区时,另一个线程向该缓冲区写入数据,那么程序的运行结果就可能出错。程序应该尽量避免多个线程同时访问同一个缓冲区或系统资源。

在Windows 95环境下编写多线程应用程序还需要考虑重入问题。Windows NT是真正的32位操作系统,它解决了系统重入问题。而Windows 95由于继承了Windows 3.x的部分16位代码,没能够解决重入问题。这意味着在Windows 95中两个线程不能同时执行某个系统功能,否则有可能造成程序错误,甚至会造成系统崩溃。应用程序应该尽量避免发生两个以上的线程同时调用同一个Windows API函数的情况。

由于大小和性能方面的原因,MFC对象在对象级不是线程安全的,只有在类级才是。也就是说,两个线程可以安全地使用两个不同的CString对象,但同时使用同一个CString对象就可能产生问题。如果必须使用同一个对象,那么应该采取适当的同步措施。

多个线程之间需要协调运行。例如,如果第二个线程需要等待第一个线程完成到某一步时才能运行,那么该线程应该暂时挂起以减少对CPU的占用时间,提高程序的执行效率。当第一个线程完成了相应的步骤后,应该发出某种信号来激活第二个线程。

 

12.2.2 等待函数

  Win32 API提供了一组能使线程阻塞其自身执行的等待函数。这些函数只有在作为其参数的一个或多个同步对象(见下小节)产生信号时才会返回。在超过规定的等待时间后,不管有无信号,函数也都会返回。在等待函数未返回时,线程处于等待状态,此时线程只消耗很少的CPU时间。

  使用等待函数即可以保证线程的同步,又可以提高程序的运行效率。最常用的等待函数是WaitForSingleObject,该函数的声明为:

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);

  参数hHandle是同步对象的句柄。参数dwMilliseconds是以毫秒为单位的超时间隔,如果该参数为0,那么函数就测试同步对象的状态并立即返回,如果该参数为INFINITE,则超时间隔是无限的。函数的返回值在表12.1中列出。

 

表12.1 WaitForSingleObject的返回值

 

 

 

函数WaitForMultipleObjects可以同时监测多个同步对象,该函数的声明为:

DWORD WaitForMultipleObjects(DWORD nCount, CONST HANDLE *lpHandles, BOOL bWaitAll, DWORD dwMilliseconds );

 

  参数nCount是句柄数组中句柄的数目。lpHandles代表一个句柄数组。bWaitAll说明了等待类型,如果为TRUE,那么函数在所有对象都有信号后才返回,如果为FALSE,则只要有一个对象变成有信号的,函数就返回。函数的返回值在表12.2中列出。参数dwMilliseconds是以毫秒为单位的超时间隔,如果该参数为0,那么函数就测试同步对象的状态并立即返回,如果该参数为INFINITE,则超时间隔是无限的。

 

表12.2 WaitForMultipleObjects的返回值

 

 

12.2.3 同步对象

  同步对象用来协调多线程的执行,它可以被多个线程共享。线程的等待函数用同步对象的句柄作为参数,同步对象应该是所有要使用的线程都能访问到的。同步对象的状态要么是有信号的,要么是无信号的。同步对象主要有三种:事件、mutex和信号灯。

  事件对象(Event)是最简单的同步对象,它包括有信号和无信号两种状态。在线程访问某一资源之前,也许需要等待某一事件的发生,这时用事件对象最合适。例如,只有在通信端口缓冲区收到数据后,监视线程才被激活。

  事件对象是用CreateEvent函数建立的。该函数可以指定事件对象的种类和事件的初始状态。如果是手工重置事件,那么它总是保持有信号状态,直到用ResetEvent函数重置成无信号的事件。如果是自动重置事件,那么它的状态在单个等待线程释放后会自动变为无信号的。用SetEvent可以把事件对象设置成有信号状态。在建立事件时,可以为对象起个名字,这样其它进程中的线程可以用OpenEvent函数打开指定名字的事件对象句柄。

  mutex对象的状态在它不被任何线程拥有时是有信号的,而当它被拥有时则是无信号的。mutex对象很适合用来协调多个线程对共享资源的互斥访问(mutually exclusive)。

  线程用CreateMutex函数来建立mutex对象,在建立mutex时,可以为对象起个名字,这样其它进程中的线程可以用OpenMutex函数打开指定名字的mutex对象句柄。在完成对共享资源的访问后,线程可以调用ReleaseMutex来释放mutex,以便让别的线程能访问共享资源。如果线程终止而不释放mutex,则认为该mutex被废弃。

  信号灯对象维护一个从0开始的计数,在计数值大于0时对象是有信号的,而在计数值为0时则是无信号的。信号灯对象可用来限制对共享资源进行访问的线程数量。线程用CreateSemaphore函数来建立信号灯对象,在调用该函数时,可以指定对象的初始计数和最大计数。在建立信号灯时也可以为对象起个名字,别的进程中的线程可以用OpenSemaphore函数打开指定名字的信号灯句柄。

  一般把信号灯的初始计数设置成最大值。每次当信号灯有信号使等待函数返回时,信号灯计数就会减1,而调用ReleaseSemaphore可以增加信号灯的计数。计数值越小就表明访问共享资源的程序越多。

  除了上述三种同步对象外,表12.3中的对象也可用于同步。另外,有时可以用文件或通信设备作为同步对象使用。

 

表12.3 可用于同步的对象

 

 

 

  当对象不再使用时,应该用CloseHandle函数关闭对象句柄。

  清单12.3是一个使用事件对象的简单例子,在该例中,假设主线程要读取共享缓冲区中的内容,而辅助线程负责向缓冲区中写入数据。两个线程使用了一个hEvent事件对象来同步。在用CreateEvent函数创建事件对象句柄时,指定该对象是一个自动重置事件,其初始状态为有信号的。当线程要读写缓冲区时,调用WaitForSingleObject函数无限等待hEvent信号。如果hEvent无信号,则说明另一线程正在访问缓冲区;如果有信号,则本线程可以访问缓冲区,WaitForSingleObject函数在返回后会自动把hEvent置成无信号的,这样在本线程读写缓冲区时别的线程不会同时访问。在完成读写操作后,调用SetEvent函数把hEvent置成有信号的,以使别的线程有机会访问共享缓冲区。

 

清单12.3 使用事件对象的简单例子

HANDLE hEvent; //全局变量

 

//主线程

hEvent=CreateEvent(NULL, FALSE, TRUE, NULL);

if(hEvent= =NULL) return;

 

. . .

WaitForSingleObject(hEvent, INFINITE);

ReadFromBuf( );

SetEvent( hEvent );

 

. . .

CloseHandle( hEvent );

 

 

//辅助线程

UINT MyThreadProc( LPVOID pParam )

{

. . .

WaitForSingleObject(hEvent, INFINITE);

WriteToBuf( );

SetEvent( hEvent );

. . .

return 0; // 线程正常结束

}

12.2.4 关键节和互锁变量访问

  关键节(Critical Seciton)与mutex的功能类似,但它只能由同一进程中的线程使用。关键节可以防止共享资源被同时访问。

  进程负责为关键节分配内存空间,关键节实际上是一个CRITICAL_SECTION型的变量,它一次只能被一个线程拥有。在线程使用关键节之前,必须调用InitializeCriticalSection函数将其初始化。如果线程中有一段关键的代码不希望被别的线程中断,那么可以调用EnterCriticalSection函数来申请关键节的所有权,在运行完关键代码后再用LeaveCriticalSection函数来释放所有权。如果在调用EnterCriticalSection时关键节对象已被另一个线程拥有,那么该函数将无限期等待所有权。

  利用互锁变量可以建立简单有效的同步机制。使用函数InterlockedIncrement和InterlockedDecrement可以增加或减少多个线程共享的一个32位变量的值,并且可以检查结果是否为0。线程不必担心会被其它线程中断而导致错误。如果变量位于共享内存中,那么不同进程中的线程也可以使用这种机制。

12.3 串行通信与重叠I/O

  Win 32系统为串行通信提供了全新的服务。传统的OpenComm、ReadComm、WriteComm、CloseComm等函数已经过时,WM_COMMNOTIFY消息也消失了。取而代之的是文件I/O函数提供的打开和关闭通信资源句柄及读写操作的基本接口。

  新的文件I/O函数(CreateFile、ReadFile、WriteFile等)支持重叠式输入输出,这使得线程可以从费时的I/O操作中解放出来,从而极大地提高了程序的运行效率。

12.3.1 串行口的打开和关闭

  Win 32系统把文件的概念进行了扩展。无论是文件、通信设备、命名管道、邮件槽、磁盘、还是控制台,都是用API函数CreateFile来打开或创建的。该函数的声明为:

HANDLE CreateFile(

LPCTSTR lpFileName, // 文件名

DWORD dwDesiredAccess, // 访问模式

DWORD dwShareMode, // 共享模式

LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 通常为NULL

DWORD dwCreationDistribution, // 创建方式

DWORD dwFlagsAndAttributes, // 文件属性和标志

HANDLE hTemplateFile // 临时文件的句柄,通常为NULL

);

  如果调用成功,那么该函数返回文件的句柄,如果调用失败,则函数返回INVALID_HANDLE_VALUE。

  如果想要用重叠I/O方式(参见12.3.3)打开COM2口,则一般应象清单12.4那样调用CreateFile函数。注意在打开一个通信端口时,应该以独占方式打开,另外要指定GENERIC_READ、GENERIC_WRITE、OPEN_EXISTING和FILE_ATTRIBUTE_NORMAL等属性。如果要打开重叠I/O,则应该指定 FILE_FLAG_OVERLAPPED属性。

 

清单12.4

HANDLE hCom;

DWORD dwError;

hCom=CreateFile(“COM2”, // 文件名

GENERIC_READ | GENERIC_WRITE, // 允许读和写

0, // 独占方式

NULL,

OPEN_EXISTING, //打开而不是创建

FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // 重叠方式

NULL

);

if(hCom = = INVALID_HANDLE_VALUE)

{

dwError=GetLastError( );

. . . // 处理错误

}

当不再使用文件句柄时,应该调用CloseHandle函数关闭之。

12.3.2 串行口的初始化

  在打开通信设备句柄后,常常需要对串行口进行一些初始化工作。这需要通过一个DCB结构来进行。DCB结构包含了诸如波特率、每个字符的数据位数、奇偶校验和停止位数等信息。在查询或配置置串行口的属性时,都要用DCB结构来作为缓冲区。

  调用GetCommState函数可以获得串口的配置,该函数把当前配置填充到一个DCB结构中。一般在用CreateFile打开串行口后,可以调用GetCommState函数来获取串行口的初始配置。要修改串行口的配置,应该先修改DCB结构,然后再调用SetCommState函数用指定的DCB结构来设置串行口。

  除了在DCB中的设置外,程序一般还需要设置I/O缓冲区的大小和超时。Windows用I/O缓冲区来暂存串行口输入和输出的数据,如果通信的速率较高,则应该设置较大的缓冲区。调用SetupComm函数可以设置串行口的输入和输出缓冲区的大小。

  在用ReadFile和WriteFile读写串行口时,需要考虑超时问题。如果在指定的时间内没有读出或写入指定数量的字符,那么ReadFile或WriteFile的操作就会结束。要查询当前的超时设置应调用GetCommTimeouts函数,该函数会填充一个COMMTIMEOUTS结构。调用SetCommTimeouts可以用某一个COMMTIMEOUTS结构的内容来设置超时。

  有两种超时:间隔超时和总超时。间隔超时是指在接收时两个字符之间的最大时延,总超时是指读写操作总共花费的最大时间。写操作只支持总超时,而读操作两种超时均支持。用COMMTIMEOUTS结构可以规定读/写操作的超时,该结构的定义为:

typedef struct _COMMTIMEOUTS {

DWORD ReadIntervalTimeout; // 读间隔超时

DWORD ReadTotalTimeoutMultiplier; // 读时间系数

DWORD ReadTotalTimeoutConstant; // 读时间常量

DWORD WriteTotalTimeoutMultiplier; // 写时间系数

DWORD WriteTotalTimeoutConstant; // 写时间常量

} COMMTIMEOUTS,*LPCOMMTIMEOUTS;

  COMMTIMEOUTS结构的成员都以毫秒为单位。总超时的计算公式是:

总超时=时间系数滓蠖?写的字符数 + 时间常量

  例如,如果要读入10个字符,那么读操作的总超时的计算公式为:

读总超时=ReadTotalTimeoutMultiplier?0 + ReadTotalTimeoutConstant

  可以看出,间隔超时和总超时的设置是不相关的,这可以方便通信程序灵活地设置各种超时。

  如果所有写超时参数均为0,那么就不使用写超时。如果ReadIntervalTimeout为0,那么就不使用读间隔超时,如果ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant都为0,则不使用读总超时。如果读间隔超时被设置成MAXDWORD并且两个读总超时为0,那么在读一次输入缓冲区中的内容后读操作就立即完成,而不管是否读入了要求的字符。

  在用重叠方式读写串行口时,虽然ReadFile和WriteFile在完成操作以前就可能返回,但超时仍然是起作用的。在这种情况下,超时规定的是操作的完成时间,而不是ReadFile和WriteFile的返回时间。

清单12.5列出了一段简单的串行口初始化代码。

 

清单12.5 打开并初始化串行口

HANDLE hCom;

DWORD dwError;

DCB dcb;

COMMTIMEOUTS TimeOuts;

hCom=CreateFile(“COM2”, // 文件名

GENERIC_READ | GENERIC_WRITE, // 允许读和写

0, // 独占方式

NULL,

OPEN_EXISTING, //打开而不是创建

FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // 重叠方式

NULL

);

if(hCom = = INVALID_HANDLE_VALUE)

{

dwError=GetLastError( );

. . . // 处理错误

}

 

SetupComm( hCom, 1024, 1024 ) //缓冲区的大小为1024

 

TimeOuts. ReadIntervalTimeout=1000;

TimeOuts.ReadTotalTimeoutMultiplier=500;

TimeOuts.ReadTotalTimeoutConstant=5000;

TimeOuts.WriteTotalTimeoutMultiplier=500;

TimeOuts.WriteTotalTimeoutConstant=5000;

SetCommTimeouts(hCom, &TimeOuts); // 设置超时

 

GetCommState(hCom, &dcb);

dcb.BaudRate=2400; // 波特率为2400

dcb.ByteSize=8; // 每个字符有8位

dcb.Parity=NOPARITY; //无校验

dcb.StopBits=ONESTOPBIT; //一个停止位

SetCommState(hCom, &dcb);

 

12.3.3 重叠I/O

  在用ReadFile和WriteFile读写串行口时,既可以同步执行,也可以重叠(异步)执行。在同步执行时,函数直到操作完成后才返回。这意味着在同步执行时线程会被阻塞,从而导致效率下降。在重叠执行时,即使操作还未完成,调用的函数也会立即返回。费时的I/O操作在后台进行,这样线程就可以干别的事情。例如,线程可以在不同的句柄上同时执行I/O操作,甚至可以在同一句柄上同时进行读写操作。“重叠”一词的含义就在于此。

  ReadFile函数只要在串行口输入缓冲区中读入指定数量的字符,就算完成操作。而WriteFile函数不但要把指定数量的字符拷入到输出缓冲中,而且要等这些字符从串行口送出去后才算完成操作。

  ReadFile和WriteFile函数是否为执行重叠操作是由CreateFile函数决定的。如果在调用CreateFile创建句柄时指定了FILE_FLAG_OVERLAPPED标志,那么调用ReadFile和WriteFile对该句柄进行的读写操作就是重叠的,如果未指定重叠标志,则读写操作是同步的。

  函数ReadFile和WriteFile的参数和返回值很相似。这里仅列出ReadFile函数的声明:

BOOL ReadFile(

HANDLE hFile, // 文件句柄

LPVOID lpBuffer, // 读缓冲区

DWORD nNumberOfBytesToRead, // 要求读入的字节数

LPDWORD lpNumberOfBytesRead, // 实际读入的字节数

LPOVERLAPPED lpOverlapped // 指向一个OVERLAPPED结构

); //若返回TRUE则表明操作成功

 

  需要注意的是如果该函数因为超时而返回,那么返回值是TRUE。参数lpOverlapped在重叠操作时应该指向一个OVERLAPPED结构,如果该参数为NULL,那么函数将进行同步操作,而不管句柄是否是由FILE_FLAG_OVERLAPPED标志建立的。

  当ReadFile和WriteFile返回FALSE时,不一定就是操作失败,线程应该调用GetLastError函数分析返回的结果。例如,在重叠操作时如果操作还未完成函数就返回,那么函数就返回FALSE,而且GetLastError函数返回ERROR_IO_PENDING。

  在使用重叠I/O时,线程需要创建OVERLAPPED结构以供读写函数使用。OVERLAPPED结构最重要的成员是hEvent,hEvent是一个事件对象句柄,线程应该用CreateEvent函数为hEvent成员创建一个手工重置事件,hEvent成员将作为线程的同步对象使用。如果读写函数未完成操作就返回,就那么把hEvent成员设置成无信号的。操作完成后(包括超时),hEvent会变成有信号的。

  如果GetLastError函数返回ERROR_IO_PENDING,则说明重叠操作还为完成,线程可以等待操作完成。有两种等待办法:一种办法是用象WaitForSingleObject这样的等待函数来等待OVERLAPPED结构的hEvent成员,可以规定等待的时间,在等待函数返回后,调用GetOverlappedResult。另一种办法是调用GetOverlappedResult函数等待,如果指定该函数的bWait参数为TRUE,那么该函数将等待OVERLAPPED结构的hEvent 事件。GetOverlappedResult可以返回一个OVERLAPPED结构来报告包括实际传输字节在内的重叠操作结果。

  如果规定了读/写操作的超时,那么当超过规定时间后,hEvent成员会变成有信号的。因此,在超时发生后,WaitForSingleObject和GetOverlappedResult都会结束等待。WaitForSingleObject的dwMilliseconds参数会规定一个等待超时,该函数实际等待的时间是两个超时的最小值。注意GetOverlappedResult不能设置等待的时限,因此如果hEvent成员无信号,则该函数将一直等待下去。

  在调用ReadFile和WriteFile之前,线程应该调用ClearCommError函数清除错误标志。该函数负责报告指定的错误和设备的当前状态。

  调用PurgeComm函数可以终止正在进行的读写操作,该函数还会清除输入或输出缓冲区中的内容。

 

12.3.4 通信事件

  在Windows 95/NT中,WM_COMMNOTIFY消息已经取消,在串行口产生一个通信事件时,程序并不会收到通知消息。线程需要调用WaitCommEvent函数来监视发生在串行口中的各种事件,该函数的第二个参数返回一个事件屏蔽变量,用来指示事件的类型。线程可以用SetCommMask建立事件屏蔽以指定要监视的事件,表12.4列出了可以监视的事件。调用GetCommMask可以查询串行口当前的事件屏蔽。

 

表12.4 通信事件

 

  WaitCommEvent即可以同步使用,也可以重叠使用。如果串口是用FILE_FLAG_OVERLAPPED标志打开的,那么WaitCommEvent就进行重叠操作,此时该函数需要一个OVERLAPPED结构。线程可以调用等騁etOverlappedResult函数来等待重叠操作的完成。

  当指定范围内的某一事件发生后,线程就结束等待并把该事件的屏蔽码设置到事件屏蔽变量中。需要注意的是,WaitCommEvent只检测调用该函数后发生的事件。例如,如果在调用WaitCommEvent前在输入缓冲区中就有字符,则不会因为这些字符而产生EV_RXCHAR事件。

  如果检测到输入的硬件信号(如CTS、RTS和CD信号等)发生了变化,线程可以调用GetCommMaskStatus函数来查询它们的状态。而用EscapeCommFunction函数可以控制输出的硬件信号(如DTR和RTS信号)。

12. 4 一个通信演示程序

  为了使读者更好地掌握本章的概念,这里举一个具体实例来说明问题。如图12.1所示,例子程序名为Terminal,是一个简单的TTY终端仿真程序。读者可以用该程序打开一个串行口,该程序会把用户的键盘输入发送给串行口,并把从串口接收到的字符显示在视图中。用户通过选择File->Connect命令来打开串行口,选择File->Disconnect命令则关闭串行口。

图12.1 Terminal终端仿真程序

  当用户选择File->Settings...命令时,会弹出一个Communication settings对话框,如图12.2所示。该对话框主要用来设置串行口,包括端口、波特率、每字节位数、校验、停止位数和流控制。

图12.2 Communication settings对话框

 

  通过该对话框也可以设置TTY终端仿真的属性,如果选择New Line(自动换行),那么每当从串口读到回车符(‘"r’)时,视图中的正文就会换行,否则,只有在读到换行符(‘"n’)时才会换行。如果选择Local echo(本地回显),那么发送的字符会在视图中显示出来。

  终端仿真程序的特点是数据的传输没有规律。因为键盘输入速度有限,所以发送的数据量较小,但接收的数据源是不确定的,所以有可能会有大量数据高速涌入的情况发生。根据Terminal的这些特性,我们在程序中创建了一个辅助工作者线程专门来监视串行口的输入。由于写入串行口的数据量不大,不会太费时,所以在主线程中完成写端口的任务是可以的,不必另外创建线程。

  现在就让我们开始工作。请读者按下面几步进行:

用AppWizard建立一个名为Terminal的MFC应用程序。在MFC AppWizard对话框的第1步选择Single document,在第4步去掉Docking toolbar的选择,在第6步把CTerminalView的基类改为CEditView。

在Terminal工程的资源视图中打开IDR_MAINFRAME菜单资源。去掉Edit菜单和View菜单,并去掉File菜单中除Exit以外的所有菜单项。然后在File菜单中加入三个菜单项,如表12.5所示。

 

表12.5 新菜单项

标题

ID

Settings...

ID_FILE_SETTINGS

Connect

ID_FILE_CONNECT

Disconnect

ID_FILE_DISCONNECT

 

 

用ClassWizard为CTerminalDoc类创建三个与上表菜单消息对应的命令处理函数,使用缺省的函数名。为ID_FILE_CONNECT和ID_FILE_DISCONNECT命令创建命令更新处理函数。另外,用ClassWizard为该类加入CanCloseFrame成员函数。

用ClassWizard为CTerminalView类创建OnChar函数,该函数用来把用户键入的字符向串行口输出。

新建一个对话框模板资源,令其ID为IDD_COMSETTINGS。请按图12.2和表12.6设计对话框模板。

 

表12.6 通信设置对话框中的主要控件

控件

ID

属性设置

Base options组框

缺省

标题为Base options

Port组合框

IDC_PORT

Drop List,不选Sort,初始列表为COM1、COM2、COM3、COM4

Baud rate组合框

IDC_BAUD

Drop List,不选Sort,初始列表为300、600、1200、2400、9600、14400、19200、38400、57600

Data bits组合框

IDC_DATABITS

Drop List,不选Sort,初列表为5、6、7、8

Parity组合框

IDC_PARITY

Drop List,不选Sort,初列表为None、Even、Odd

Stop bits组合框

IDC_STOPBITS

Drop List,不选Sort,初列表为1、1.5、2

Flow control组框

缺省

标题为Flow control

None单选按钮

IDC_FLOWCTRL

标题为None,选择Group属性

RTS/CTS单选按钮

缺省

标题为RTS/CTS

XON/XOFF单选按钮

缺省

标题为XON/XOFF

TTY options组框

缺省

标题为TTY options

New line检查框

IDC_NEWLINE

标题为New line

Local echo检查框

IDC_ECHO

标题为Local echo

 

 

打开ClassWizard,为IDD_COMSETTINGS模板创建一个名为CSetupDlg的对话框类。为该类加入OnInitDialog成员函数,并按表12.7加入数据成员。

 

表12.7 CSetupDlg类的数据成员

控件ID

变量名

数据类型

IDC_BAND

m_sBaud

CString

IDC_DATABITS

m_sDataBits

CString

IDC_ECHO

m_bEcho

BOOL

IDC_FLOWCTRL

m_nFlowCtrl

int

IDC_NEWLINE

m_bNewLine

BOOL

IDC_PARITY

m_nParity

int

IDC_PORT

m_sPort

CString

IDC_STOPBITS

m_nStopBits

int

 

 

按清单12.6、12.7和12.8修改程序。清单12.6列出了CTerminalDoc类的部分代码,清单12.7是CTerminalView的部分代码,清单12.8是CSetupDlg类的部分代码。在本例中使用了WM_COMMNOTIFY消息。虽然在Win32中,WM_COMMNOTIFY消息已经取消,系统自己不会产生该消息,但Visual C++对该消息的定义依然保留。考虑到使用习惯,Terminal程序辅助线程通过发送该消息来通知视图有通信事件发生。

 

清单12.6 CTerminalDoc类的部分代码

// TerminalDoc.h : interface of the CTerminalDoc class

//

/////////////////////////////////////////////////////////////////////////////

 

 

#define MAXBLOCK 2048

#define XON 0x11

#define XOFF 0x13

 

UINT CommProc(LPVOID pParam);

 

class CTerminalDoc : public CDocument

{

protected: // create from serialization only

CTerminalDoc();

DECLARE_DYNCREATE(CTerminalDoc)

 

// Attributes

public:

 

CWinThread* m_pThread; // 代表辅助线程

volatile BOOL m_bConnected;

volatile HWND m_hTermWnd;

volatile HANDLE m_hPostMsgEvent; // 用于WM_COMMNOTIFY消息的事件对象

OVERLAPPED m_osRead, m_osWrite; // 用于重叠读/写

 

volatile HANDLE m_hCom; // 串行口句柄

int m_nBaud;

int m_nDataBits;

BOOL m_bEcho;

int m_nFlowCtrl;

BOOL m_bNewLine;

int m_nParity;

CString m_sPort;

int m_nStopBits;

 

 

// Operations

public:

 

BOOL ConfigConnection();

BOOL OpenConnection();

void CloseConnection();

DWORD ReadComm(char *buf,DWORD dwLength);

DWORD WriteComm(char *buf,DWORD dwLength);

// Overrides

. . .

};

 

/////////////////////////////////////////////////////////////////////////////

// TerminalDoc.cpp : implementation of the CTerminalDoc class

//

 

#include "SetupDlg.h"

 

CTerminalDoc::CTerminalDoc()

{

// TODO: add one-time construction code here

 

m_bConnected=FALSE;

m_pThread=NULL;

 

m_nBaud = 9600;

m_nDataBits = 8;

m_bEcho = FALSE;

m_nFlowCtrl = 0;

m_bNewLine = FALSE;

m_nParity = 0;

m_sPort = "COM2";

m_nStopBits = 0;

}

 

CTerminalDoc::~CTerminalDoc()

{

 

if(m_bConnected)

CloseConnection();

// 删除事件句柄

if(m_hPostMsgEvent)

CloseHandle(m_hPostMsgEvent);

if(m_osRead.hEvent)

CloseHandle(m_osRead.hEvent);

if(m_osWrite.hEvent)

CloseHandle(m_osWrite.hEvent);

}

 

BOOL CTerminalDoc::OnNewDocument()

{

if (!CDocument::OnNewDocument())

return FALSE;

((CEditView*)m_viewList.GetHead())->SetWindowText(NULL);

 

// TODO: add reinitialization code here

// (SDI documents will reuse this document)

 

 

// 为WM_COMMNOTIFY消息创建事件对象,手工重置,初始化为有信号的

if((m_hPostMsgEvent=CreateEvent(NULL, TRUE, TRUE, NULL))==NULL)

return FALSE;

memset(&m_osRead, 0, sizeof(OVERLAPPED));

memset(&m_osWrite, 0, sizeof(OVERLAPPED));

// 为重叠读创建事件对象,手工重置,初始化为无信号的

if((m_osRead.hEvent=CreateEvent(NULL, TRUE, FALSE, NULL))==NULL)

return FALSE;

// 为重叠写创建事件对象,手工重置,初始化为无信号的

if((m_osWrite.hEvent=CreateEvent(NULL, TRUE, FALSE, NULL))==NULL)

return FALSE;

return TRUE;

}

 

void CTerminalDoc::OnFileConnect()

{

// TODO: Add your command handler code here

 

if(!OpenConnection())

AfxMessageBox("Can't open connection");

}

 

void CTerminalDoc::OnFileDisconnect()

{

// TODO: Add your command handler code here

 

CloseConnection();

}

 

void CTerminalDoc::OnUpdateFileConnect(CCmdUI* pCmdUI)

{

// TODO: Add your command update UI handler code here

 

pCmdUI->Enable(!m_bConnected);

}

 

void CTerminalDoc::OnUpdateFileDisconnect(CCmdUI* pCmdUI)

{

// TODO: Add your command update UI handler code here

 

pCmdUI->Enable(m_bConnected);

}

 

 

// 打开并配置串行口,建立工作者线程

BOOL CTerminalDoc::OpenConnection()

{

COMMTIMEOUTS TimeOuts;

POSITION firstViewPos;

CView *pView;

 

firstViewPos=GetFirstViewPosition();

pView=GetNextView(firstViewPos);

m_hTermWnd=pView->GetSafeHwnd();

 

if(m_bConnected)

return FALSE;

m_hCom=CreateFile(m_sPort, GENERIC_READ | GENERIC_WRITE, 0, NULL,

OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,

NULL); // 重叠方式

if(m_hCom==INVALID_HANDLE_VALUE)

return FALSE;

SetupComm(m_hCom,MAXBLOCK,MAXBLOCK);

SetCommMask(m_hCom, EV_RXCHAR);

 

// 把间隔超时设为最大,把总超时设为0将导致ReadFile立即返回并完成操作

TimeOuts.ReadIntervalTimeout=MAXDWORD;

TimeOuts.ReadTotalTimeoutMultiplier=0;

TimeOuts.ReadTotalTimeoutConstant=0;

/* 设置写超时以指定WriteComm成员函数中的

GetOverlappedResult函数的等待时间*/

TimeOuts.WriteTotalTimeoutMultiplier=50;

TimeOuts.WriteTotalTimeoutConstant=2000;

SetCommTimeouts(m_hCom, &TimeOuts);

if(ConfigConnection())

{

m_pThread=AfxBeginThread(CommProc, this, THREAD_PRIORITY_NORMAL,

0, CREATE_SUSPENDED, NULL); // 创建并挂起线程

if(m_pThread==NULL)

{

CloseHandle(m_hCom);

return FALSE;

}

else

{

m_bConnected=TRUE;

m_pThread->ResumeThread(); // 恢复线程运行

}

}

else

{

CloseHandle(m_hCom);

return FALSE;

}

return TRUE;

}

 

 

// 结束工作者线程,关闭串行口

void CTerminalDoc::CloseConnection()

{

if(!m_bConnected) return;

m_bConnected=FALSE;

 

//结束CommProc线程中WaitSingleObject函数的等待

SetEvent(m_hPostMsgEvent);

 

//结束CommProc线程中WaitCommEvent的等待

SetCommMask(m_hCom, 0);

 

//等待辅助线程终止

WaitForSingleObject(m_pThread->m_hThread, INFINITE);

m_pThread=NULL;

CloseHandle(m_hCom);

}

 

// 让用户设置串行口

void CTerminalDoc::OnFileSettings()

{

// TODO: Add your command handler code here

 

CSetupDlg dlg;

CString str;

 

dlg.m_bConnected=m_bConnected;

dlg.m_sPort=m_sPort;

str.Format("%d",m_nBaud);

dlg.m_sBaud=str;

str.Format("%d",m_nDataBits);

dlg.m_sDataBits=str;

dlg.m_nParity=m_nParity;

dlg.m_nStopBits=m_nStopBits;

dlg.m_nFlowCtrl=m_nFlowCtrl;

dlg.m_bEcho=m_bEcho;

dlg.m_bNewLine=m_bNewLine;

if(dlg.DoModal()==IDOK)

{

m_sPort=dlg.m_sPort;

m_nBaud=atoi(dlg.m_sBaud);

m_nDataBits=atoi(dlg.m_sDataBits);

m_nParity=dlg.m_nParity;

m_nStopBits=dlg.m_nStopBits;

m_nFlowCtrl=dlg.m_nFlowCtrl;

m_bEcho=dlg.m_bEcho;

m_bNewLine=dlg.m_bNewLine;

if(m_bConnected)

if(!ConfigConnection())

AfxMessageBox("Can't realize the settings!");

}

}

 

 

// 配置串行口

BOOL CTerminalDoc::ConfigConnection()

{

DCB dcb;

 

if(!GetCommState(m_hCom, &dcb))

return FALSE;

dcb.fBinary=TRUE;

dcb.BaudRate=m_nBaud; // 波特率

dcb.ByteSize=m_nDataBits; // 每字节位数

dcb.fParity=TRUE;

switch(m_nParity) // 校验设置

{

case 0: dcb.Parity=NOPARITY;

break;

case 1: dcb.Parity=EVENPARITY;

break;

case 2: dcb.Parity=ODDPARITY;

break;

default:;

}

switch(m_nStopBits) // 停止位

{

case 0: dcb.StopBits=ONESTOPBIT;

break;

case 1: dcb.StopBits=ONE5STOPBITS;

break;

case 2: dcb.StopBits=TWOSTOPBITS;

break;

default:;

}

// 硬件流控制设置

dcb.fOutxCtsFlow=m_nFlowCtrl==1;

dcb.fRtsControl=m_nFlowCtrl==1?

RTS_CONTROL_HANDSHAKE:RTS_CONTROL_ENABLE;

// XON/XOFF流控制设置

dcb.fInX=dcb.fOutX=m_nFlowCtrl==2;

dcb.XonChar=XON;

dcb.XoffChar=XOFF;

dcb.XonLim=50;

dcb.XoffLim=50;

return SetCommState(m_hCom, &dcb);

}

 

 

// 从串行口输入缓冲区中读入指定数量的字符

DWORD CTerminalDoc::ReadComm(char *buf,DWORD dwLength)

{

DWORD length=0;

COMSTAT ComStat;

DWORD dwErrorFlags;

ClearCommError(m_hCom,&dwErrorFlags,&ComStat);

length=min(dwLength, ComStat.cbInQue);

ReadFile(m_hCom,buf,length,&length,&m_osRead);

return length;

 

}

 

// 将指定数量的字符从串行口输出

DWORD CTerminalDoc::WriteComm(char *buf,DWORD dwLength)

{

BOOL fState;

DWORD length=dwLength;

COMSTAT ComStat;

DWORD dwErrorFlags;

ClearCommError(m_hCom,&dwErrorFlags,&ComStat);

fState=WriteFile(m_hCom,buf,length,&length,&m_osWrite);

if(!fState){

if(GetLastError()==ERROR_IO_PENDING)

{

GetOverlappedResult(m_hCom,&m_osWrite,&length,TRUE);// 等待

}

else

length=0;

}

return length;

}

 

// 工作者线程,负责监视串行口

UINT CommProc(LPVOID pParam)

{

OVERLAPPED os;

DWORD dwMask, dwTrans;

COMSTAT ComStat;

DWORD dwErrorFlags;

CTerminalDoc *pDoc=(CTerminalDoc*)pParam;

 

memset(&os, 0, sizeof(OVERLAPPED));

os.hEvent=CreateEvent(NULL, TRUE, FALSE, NULL);

if(os.hEvent==NULL)

{

AfxMessageBox("Can't create event object!");

return (UINT)-1;

}

while(pDoc->m_bConnected)

{

ClearCommError(pDoc->m_hCom,&dwErrorFlags,&ComStat);

if(ComStat.cbInQue)

{

// 无限等待WM_COMMNOTIFY消息被处理完

WaitForSingleObject(pDoc->m_hPostMsgEvent, INFINITE);

ResetEvent(pDoc->m_hPostMsgEvent);

// 通知视图

PostMessage(pDoc->m_hTermWnd, WM_COMMNOTIFY, EV_RXCHAR, 0);

continue;

}

dwMask=0;

if(!WaitCommEvent(pDoc->m_hCom, &dwMask, &os)) // 重叠操作

{

if(GetLastError()==ERROR_IO_PENDING)

// 无限等待重叠操作结果

GetOverlappedResult(pDoc->m_hCom, &os, &dwTrans, TRUE);

else

{

CloseHandle(os.hEvent);

return (UINT)-1;

}

}

}

CloseHandle(os.hEvent);

return 0;

}

 

BOOL CTerminalDoc::CanCloseFrame(CFrameWnd* pFrame)

{

// TODO: Add your specialized code here and/or call the base class

 

SetModifiedFlag(FALSE); // 将文档的修改标志设置成未修改

return CDocument::CanCloseFrame(pFrame);

}

  毫无疑问,CTerminalDoc类是研究重点。该类负责Terminal的通信任务,主要包括设置通信参数、打开和关闭串行口、建立和终止辅助工作线程、用辅助线程监视串行口等等。

  在CTerminalDoc类的头文件中,有些变量是用volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。

  成员m_bConnected用来表明当前是否存在一个通信连接。m_hTermWnd用来保存是视图的窗口句柄。m_hPostMsgEvent事件对象用于WM_COMMNOTIFY消息的允许和禁止。m_pThread用来指向AfxBeginThread创建的CWinThread对象,以便对线程进行控制。OVERLAPPED结构m_osRead和m_osWrite用于串行口的重叠读/写,程序应该为它们的hEvent成员创建事件句柄。

  CTerminalDoc类的构造函数主要完成一些通信参数的初始化工作。OnNewDocument成员函数创建了三个事件对象,CTerminalDoc的析构函数关闭串行口并删除事件对象句柄。

  OnFileSettings是File->Settings...的命令处理函数,该函数弹出一个CSetupDlg对话框来设置通信参数。实际的设置工作由ConfigConnection函数完成,在OpenConnection和OnFileSettings中都会调用该函数。

  OpenConnection负责打开串行口并建立辅助工作线程,当用户选择了File->Connect命令时,消息处理函数OnFileConnect将调用该函数。该函数调用CreateFile以重叠方式打开指定的串行口并把返回的句柄保存在m_hCom成员中。接着,函数对m_hCom通信设备进行各种设置。需要注意的是对超时的设定,将读间隔超时设置为MAXDWORD并使其它读超时参数为0会导致ReadFile函数立即完成操作并返回,而不管读入了多少字符。设置超时就规定了GetOverlappedResult函数的等待时间,因此有必要将写超时设置成适当的值,这样如果不能完成写串口的任务,GetOverlappedResult函数会在超过规定超时后结束等待并报告实际传输的字符数。

  如果对m_hCom设置成功,则函数会建立一个辅助线程并暂时将其挂起。在最后,调用CWinThread:: ResumeThread使线程开始运行。

  OpenConnection调用成功后,线程函数CommProc就开始工作。该函数的主体是一个while循环,在该循环内,混合了两种方法监视串行口输入的方法。先是调用ClearCommError函数查询输入缓冲区中是否有字符,如果有,就向视图发送WM_COMMNOTIFY消息通知其接收字符。如果没有,则调用WaitCommEvent函数监视EV_RXCHAR通信事件,该函数执行重叠操作,紧接着调用的GetOverlappedResult函数无限等待通信事件,如果EV_RXCHAR事件发生(串口收到字符并放入输入缓冲区中),那么函数就结束等待。

  上述两种方法的混合使用兼顾了线程的效率和可靠性。如果只用ClearCommError函数,则辅助线程将不断耗费CPU时间来查询,效率较低。如果只用WaitCommEvent来监视,那么由于该函数对输入缓冲区中已有的字符不会产生EV_RXCHAR事件,因此在通信速率较高时,会造成数据的延误和丢失。

  注意到辅助线程用m_PostMsgEvent事件对象来同步WM_COMMNOTIFY消息的发送。在发送消息之前,WaitForSingleObject函数无限等待m_PostMsgEvent对象,WM_COMMNOTIFY的消息处理函数CTerminalView::OnCommNotify在返回时会把该对象置为有信号,因此,如果WaitForSingleObject函数返回,则说明上一个WM_COMMNOTIFY消息已被处理完,这时才能发下一个消息,在发消息前还要调用ResetEvent把m_PostMsgEvent对象置为无信号的,以供下次使用。

  由于PostMessage函数在消息队列中放入消息后会立即返回,所以如果不采取上述措施,那么辅助线程可能在主线程未处理之前重复发出WM_COMMNOTIFY消息,这会降低系统的效率。

  可能有读者会问,为什么不用SendMessage?该函数在发送的消息被处理完毕后才返回,这样不就不用考虑同步问题了吗?是的,本例中也可以使用SendMessage,但该函数会阻塞辅助线程的执行直到消息处理完毕,这会降低效率。如果用PostMessage,那么在函数立即返回后线程还可以干别的事情,因此,考虑到效率问题,这里使用了PostMessage而不是SendMessage。

  函数ReadComm和WriteComm分别用来从m_hCom通信设备中读/写指定数量的字符。ReadComm函数很简单,由于对读超时的特殊设定,ReadFile函数会立即返回并完成操作,并在length变量中报告实际读入的字符数。此时,没有必要调用等待函数或GetOverlappedResult。在WriteComm中,调用GerOverlappedResult来等待操作结果,直到超时发生。不管是否超时,该函数在结束等待后都会报告实际的传输字符数。

  CloseConnection函数的主要任务是终止辅助线程并关闭m_hCom通信设备。为了终止线程,该函数设置了一系列信号,以结束辅助线程中的等待和循环,然后调用WaitForSingleObject等待线程结束。

清单12.7 CTerminalView类的部分代码

// TerminalView.h : interface of the CTerminalView class

/////////////////////////////////////////////////////////////////////////////

 

class CTerminalView : public CEditView

{

. . .

afx_msg LRESULT OnCommNotify(WPARAM wParam, LPARAM lParam);

DECLARE_MESSAGE_MAP()

};

 

 

// TerminalView.cpp : implementation of the CTerminalView class

//

BEGIN_MESSAGE_MAP(CTerminalView, CEditView)

. . .

ON_MESSAGE(WM_COMMNOTIFY, OnCommNotify)

END_MESSAGE_MAP()

 

 

LRESULT CTerminalView::OnCommNotify(WPARAM wParam, LPARAM lParam)

{

char buf[MAXBLOCK/4];

CString str;

int nLength, nTextLength;

CTerminalDoc* pDoc=GetDocument();

CEdit& edit=GetEditCtrl();

if(!pDoc->m_bConnected ||

(wParam & EV_RXCHAR)!=EV_RXCHAR) // 是否是EV_RXCHAR事件?

{

SetEvent(pDoc->m_hPostMsgEvent); // 允许发送下一个WM_COMMNOTIFY消息

return 0L;

}

nLength=pDoc->ReadComm(buf,100);

if(nLength)

{

nTextLength=edit.GetWindowTextLength();

edit.SetSel(nTextLength,nTextLength); //移动插入光标到正文末尾

for(int i=0;i<nLength;i++)

{

switch(buf[i])

{

case '"r': // 回车

if(!pDoc->m_bNewLine)

break;

case '"n': // 换行

str+=""r"n";

break;

case '"b': // 退格

edit.SetSel(-1, 0);

edit.ReplaceSel(str);

nTextLength=edit.GetWindowTextLength();

edit.SetSel(nTextLength-1,nTextLength);

edit.ReplaceSel(""); //回退一个字符

str="";

break;

case '"a': // 振铃

MessageBeep((UINT)-1);

break;

default :

str+=buf[i];

}

}

edit.SetSel(-1, 0);

edit.ReplaceSel(str); // 向编辑视图中插入收到的字符

}

SetEvent(pDoc->m_hPostMsgEvent); // 允许发送下一个WM_COMMNOTIFY消息

return 0L;

}

 

void CTerminalView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// TODO: Add your message handler code here and/or call default

 

CTerminalDoc* pDoc=GetDocument();

char c=(char)nChar;

 

if(!pDoc->m_bConnected)return;

pDoc->WriteComm(&c, 1);

if(pDoc->m_bEcho)

CEditView::OnChar(nChar, nRepCnt, nFlags); // 本地回显

}

  CTerminalView是CEditView的派生类,利用CEditView的编辑功能,可以大大简化程序的设计。

  OnChar函数对WM_CHAR消息进行处理,它调用CTerminalDoc::WriteComm把用户键入的字符从串行口输出。如果设置了Local echo,那么就调用CEditView::OnChar把字符输出到视图中。

  OnCommNotify是WM_COMMNOTIFY消息的处理函数。该函数调用CTerminalDoc::ReadComm从串行口输入缓冲区中读入字符并把它们输出到编辑视图中。在输出前,函数会对一些特殊字符进行处理。如果读者对控制编辑视图的代码不太明白,那么请参见6.1.4。在函数返回时,要调用SetEvent把m_hPostMsgEvent置为有信号。

 

清单12.8 CSetupDlg类的部分代码

// SetupDlg.h : header file

//

class CSetupDlg : public CDialog

{

 

. . .

public:

BOOL m_bConnected;

. . .

};

 

 

// SetupDlg.cpp : implementation file

//

 

BOOL CSetupDlg::OnInitDialog()

{

CDialog::OnInitDialog();

 

// TODO: Add extra initialization here

 

GetDlgItem(IDC_PORT)->EnableWindow(!m_bConnected);

return TRUE; // return TRUE unless you set the focus to a control

// EXCEPTION: OCX Property Pages should return FALSE

}

  CSetupDlg的主要任务是配置通信参数。在OnInitDialog函数中,要根据当前是否连接来允许/禁止Port组合框。因为在打开一个连接后,显然不能随便改变端口。

小 结

 

  本章重点介绍了Win 32环境下的多线程和串行通信编程。本章的要点如下:

Windows 3.x实行的是协同式多任务,应用程序必须“自觉”地放弃CPU控制权,否则系统会被挂起。

Windows 95/NT实现了抢先式多任务,应用程序对CPU的控制时间由系统分配,系统可以在任何时候中断应用程序,并把控制权转交给别的程序。

在Win 32环境下,每个进程可以同时执行多个线程。线程是系统分配CPU时间片的基本实体,系统在所有线程之间快速切换以实现多任务。

由于同一进程的所有线程共享进程的虚拟地址空间、Windows 95的重入问题、MFC在对象级的线程不安全性以及线程之间的协调等原因,多个线程必须同步执行。同步机制是由同步对象和等待函数共同实现的。同步对象主要包括事件、mutex和信号灯,进程和线程句柄、文件和通信设备也可以用作同步对象。

在Win 32中,传统的OpenComm、ReadComm、WriteComm、CloseComm等串行通信函数已经过时,WM_COMMNOTIFY消息也消失了。程序应该调用CreateFile打开一个串行通信设备,用ReadFile和WriteFile来进行I/O操作,用WaitCommEvent来监视通信事件。ReadFile、WriteFile和WaitCommEvent既可以同步操作,也可以重叠操作。

利用Win 32的重叠I/O操作和多线程特性,程序员可以编写出高效的通信程序。

事件屏蔽

含义

EV_BREAK

检测到一个输入中断

EV_CTS

CTS信号发生变化

EV_DSR

DSR信号发生变化

EV_ERR

发生行状态错误

EV_RING

检测到振铃信号

EV_RLSD

RLSD(CD)信号发生变化

EV_RXCHAR

输入缓冲区接收到新字符

EV_RXFLAG

输入缓冲区收到事件字符

EV_TXEMPTY

发送缓冲区为空

对象

描述

变化通知

由FindFirstChangeNotification函数建立,当在指定目录中发生指定类型的变化时对象变成有信号的。

控制台输入

在控制台建立是被创建。它是用CONIN$调用CreateFile函数返回的句柄,或是GetStdHandle函数的返回句柄。如果控制台输入缓冲区中有数据,那么对象是有信号的,如果缓冲区为空,则对象是无信号的。

进程

当调用CreateProcess建立进程时被创建。进程在运行时对象是无信号的,当进程终止时对象是有信号的。

线程

当调用Createprocess、CreateThread或CreateRemoteThread函数创建新线程时被创建。在线程运行是对象是无信号的,在线程终止时则是有信号的。

返回值

说明

WAIT_OBJECT_0到WAIT_ OBJECT_0+nCount-1

若bWaitAll为TRUE,则返回值表明所有对象都是有信号的。如果bWaitAll为FALSE,则返回值减去WAIT_OBJECT_0就是数组中有信号对象的最小索引。

WAIT_ABANDONED_0到WAIT_ ABANDONED_ 0+nCount-1

若bWaitAll为TRUE,则返回值表明所有对象都有信号,但有一个mutex被放弃了。若bWaitAll为FALSE,则返回值减去WAIT_ABANDONED_0就是被放弃mutex在对象数组中的索引。

WAIT_TIMEOUT

超时返回。

返回值

含义

WAIT_FAILED

函数失败

WAIT_OBJECT_0

指定的同步对象处于有信号的状态

WAIT_ABANDONED

拥有一个mutex的线程已经中断了,但未释放该MUTEX

WAIT_TIMEOUT

超时返回,并且同步对象无信号

 

标签:软件开发 | 浏览数(491) | 评论数(0) | 2006-12-06

Smartphone & PocketPC dotNet Compact Framework

在启动时,蓝牙协议栈的完全装载和初始化需要一定的时间。可以使用BTH_NAMEDEVENT_STACK_INITED来检查协议栈的初始化情况,它定义在%_WINCEROOT%"Public"Common"Sdk"Inc"Bt_api.h中。要打开这个命名事件需要使用OpenEvent函数。

// 确定协议栈是否已初始化
BOOL fStackUp = FALSE;
for (int i = 0 ; i < 100 ; ++i)
{
 HANDLE hBthStackInited = OpenEvent (EVENT_ALL_ACCESS, FALSE, BTH_NAMEDEVENT_STACK_INITED);
 if (hBthStackInited)
 {
    DWORD dwRes = WaitForSingleObject (hBthStackInited, INFINITE);
    CloseHandle (hBthStackInited);
    if (WAIT_OBJECT_0 == dwRes)
    {
      fStackUp = TRUE;
      break;
    }
 }
 Sleep (1000);
}
if (! fStackUp)
{
 // 错误处理
}


Microsoft?Windows?CE提供了COM接口,可以用来以流的形式分析SDP记录。ISdpStream 定义了处理流数据的方法。在你的程序获取SDP记录之前,使用 ISdpStream::Validate方法来确保原始的SDP流是有效的并且格式正确,也可以使用ISdpStream::VerifySequenceOf方法来确保原始SDP数组中的元素是有效的。使用ISdpStream::RetrieveRecords方法来获取流数据,这个方法返回SDP记录到一个ISdpRecord数组中并且返回记录的总数。然后你可以使用ISdpRecord方法来对SDP记录进行操作,例如获取SDP服务记录的属性。更多信息参见Searching SDP Attributes Using COM Interfaces.
STDMETHODIMP
ServiceAndAttributeSearchParse(
 UCHAR *szResponse,   // in – 指向SDP记录的缓冲区,二进制格式
                       // 由Bthnscreate示例获得
 DWORD cbResponse,   // in – szResponse的大小
 ISdpRecord ***pppSdpRecords, // out – pSdpRecords的数组
 ULONG *pNumRecords   // out – pSdpRecords数组中的元素个数
)
{
 
 HRESULT hres = E_FAIL;
 *pppSdpRecords = NULL;
 *pNumRecords = 0;
 ISdpStream *pIStream = NULL;
// 创建一个流对象
if (FAILED(CoCreateInstance(__uuidof(SdpStream),NULL,CLSCTX_INPROC_SERVER,
                        __uuidof(ISdpStream),(LPVOID *) &pIStream)))
{
 return E_FAIL;
}
// 确保SDP流是有效的并且格式正确
 hres = pIStream->Validate(szResponse,cbResponse,NULL);
 
 if (SUCCEEDED(hres))
 {
 //确保SDP流的序列是有效的并且格式正确
    hres = pIStream->VerifySequenceOf(szResponse,cbResponse,
                       SDP_TYPE_SEQUENCE,NULL,pNumRecords);
    if (SUCCEEDED(hres) && *pNumRecords > 0)
    {
      *pppSdpRecords = (ISdpRecord **)
      //为SDP记录缓冲区分配内存空间
      CoTaskMemAlloc(sizeof(ISdpRecord*) * (*pNumRecords));
      if (pppSdpRecords != NULL)
      {
         // 从流中获取SDP记录
         hres = pIStream->RetrieveRecords ( szResponse,
                    cbResponse,*pppSdpRecords,pNumRecords);
        //如果获取记录失败,释放已分配的内存,设SDP记录总数为0
        if (!SUCCEEDED(hres))
        {
            CoTaskMemFree(*pppSdpRecords);
            *pppSdpRecords = NULL;
            *pNumRecords = 0;
         }
       }
       else
       {
          hres = E_OUTOFMEMORY;
       }
     }
 }
 // Release the stream.
 if (pIStream != NULL)
 {
    pIStream->Release();
    pIStream = NULL;
 }
 return hres;
}


在程序中可以使用一下这些标准Winsock编程元素来查询远程设备上的服务:
在查询远程设备上的服务性能之前,必须先知道一下信息:
  • 要查询的远程设备的地址,BT_ADDR类型,定义在Ws2bth.h中:
typedef ULONGLONG bt_addr, *pbt_addr, BT_ADDR, *PBT_ADDR;
注意   为了使清晰起见,文中忽略了错误处理。
在远程蓝牙设备上执行服务查询
  1. 提供Winsock的版本和实现细节的数据来初始化caller application。可以通过调用WSAStartup函数来获得这个数据。
WSADATA wsd;
WSAStartup (MAKEWORD(1,0), &wsd);
  1. 通过设置WSAQUERYSET结构体来指定搜索参数。
WSAQUERYSET wsaq;
memset (&wsaq, 0, sizeof(wsaq));
wsaq.dwSize      = sizeof(wsaq);
wsaq.dwNameSpace = NS_BTH;
wsaq.lpBlob      = &blob;
wsaq.lpcsaBuffer = &csai;
设置dwNameSpace成员为NS_BTH将查询指定为蓝牙查询。
  1. 调用WSALookupServiceBegin函数初始化搜索,将第一步中创建的WSAQUERYSET变量传递给pQuerySet参数来指定搜索标准
int iRet = WSALookupServiceBegin (&wsaq, 0, &hLookup);
dwFlags参数设置为0来在远程设备上执行一个服务查询, WSALookupServiceBegin 返回一个句柄到hLookup参数中。
注意   dwFlags参数设置为LUP_CONTAINERS, 调用 WSALookupServiceBegin将执行一个设备查询。详情参见“使用WinSock搜索蓝牙设备”。
  1. 要返回在远程设备上所找到的服务的相关数据,使用从WSALookupServiceBegin返回的句柄调用 WSALookupServiceNext函数。
CHAR buf[5000];
LPWSAQUERYSET pwsaResults = (LPWSAQUERYSET) buf;
DWORD dwSize = sizeof(buf);
memset(pwsaResults,0,sizeof(WSAQUERYSET));
pwsaResults->dwSize      = sizeof(WSAQUERYSET);
pwsaResults->dwNameSpace = NS_BTH;
pwsaResults->lpBlob      = NULL;
iRet = WSALookupServiceNext (hLookup, 0, &dwSize, pwsaResults);
WSALookupServiceNext返回了一个指向WSAQUERYSET的指针,它包含了lpBlob成员里的服务数据的引用。这个成员指向一个BLOB结构体,它包含了由WSALookupServiceNext一次性返回的二进制数据。 Windows CE提供了COM接口,你可以使用它们来分析服务数据。详情参见使用COM接口分析SDP记录.
调用WSALookupServiceEnd函数来结束设备搜索。这个函数将释放由WSALookupServiceBegin创建的lookup句柄。
WSALookupServiceEnd(hLookup);
要结束对Winsock服务的使用,调用WSACleanup函数。在程序中对每个成功调用的WSAStartup都必须对应地调用WSACleanup


可以利用下面这些变成元素创建一个查询来搜索一定范围内的远程蓝牙设备:
注意   为了使清晰起见,文中忽略了错误处理
搜索并返回远程设备的地址
  1. 提供Winsock的版本和实现细节的数据来初始化caller application。可以通过调用WSAStartup函数来获得这个数据。
WSADATA wsd;
WSAStartup (MAKEWORD(1,0), &wsd);
  1. 创建并初始化一个WSAQUERYSET变量用于指定搜索参数,设置dwNameSpace成员为NS_BTH限制为查询蓝牙设备。
WSAQUERYSET wsaq;
ZeroMemory(&wsaq, sizeof(wsaq));
wsaq.dwSize = sizeof(wsaq);
wsaq.dwNameSpace = NS_BTH;
wsaq.lpcsaBuffer = NULL;
  1. 调用WSALookupServiceBegin函数来执行一个查询。
int iRet = WSALookupServiceBegin (&wsaq, LUP_CONTAINERS, &hLookup);
LUP_CONTAINERS赋给dwFlags参数,启动SDP来搜索蓝牙设备。
注意   dwFlags参数设为零将执行一个服务搜索。
WSALookupServiceBegin函数返回一个句柄到hLookup参数中。
  1. 要枚举在上一步中调用WSALookupServiceBegin所找到的设备,就要使用WSALookupServiceNex函数。函数返回一个指向存有查询结果的WSAQUERYSET及构体。
    1. 设置一个WSAQUERYSET结构体来储存WSALookupServiceNext函数返回的设备数据。
CHAR buf[4096];
LPWSAQUERYSET pwsaResults = (LPWSAQUERYSET) buf;
ZeroMemory(pwsaResults, sizeof(WSAQUERYSET));
pwsaResults->dwSize = sizeof(WSAQUERYSET);
pwsaResults->dwNameSpace = NS_BTH;
pwsaResults->lpBlob = NULL;
  1. 调用WSALookupServiceNext并将WSALookupServiceBegin返回的hLookUp参数传递给它。为了提高性能,对WSALookupServiceBegin 的调用只返回设备的地址并存储在内存中。要返回设备的名称和地址,就要将LUP_RETURN_NAME | LUP_RETURN_ADDR赋给dwFlags参数。
DWORD dwSize = sizeof(buf);
int iRet = WSALookupServiceNext (hLookup, LUP_RETURN_NAME | LUP_RETURN_ADDR, &dwSize, pwsaResults)
可以通过循环调用WSALookupServiceNext来枚举所有的设备。
  1. 调用WSALookupServiceEnd函数来结束设备搜索。这个函数将释放由WSALookupServiceBegin创建的lookup句柄。
WSALookupServiceEnd(hLookup);
  1. 要结束对Winsock服务的使用,调用WSACleanup函数。在程序中对每个成功调用的WSAStartup都必须对应地调用WSACleanup
The following code example performs a device inquiry displays the device name in a user interface.
下面的代码执行了一次设备查询,并将设备名称显示在用户界面上。
//------------------------------------------------------------------------
// Function: PerformInquiry
// Purpose: Performs a device inquiry displays the device name in a user interface.
//------------------------------------------------------------------------
#define MAX_NAME 248
static BOOL PerformInquiry()
{
 WSAQUERYSET wsaq;
 HANDLE hLookup;
 union {
    CHAR buf[5000];
    double __unused; // ensure proper alignment
 };
 
 LPWSAQUERYSET pwsaResults = (LPWSAQUERYSET) buf;
 DWORD dwSize = sizeof(buf);
 BOOL bHaveName;
 ZeroMemory(&wsaq, sizeof(wsaq));
 wsaq.dwSize = sizeof(wsaq);
 wsaq.dwNameSpace = NS_BTH;
 wsaq.lpcsaBuffer = NULL;
 if (ERROR_SUCCESS != WSALookupServiceBegin (&wsaq, LUP_CONTAINERS, &hLookup))
 {
    wprintf(L"WSALookupServiceBegin failed %d"r"n", GetLastError());
    return FALSE;
 }
 
 ZeroMemory(pwsaResults, sizeof(WSAQUERYSET));
 pwsaResults->dwSize = sizeof(WSAQUERYSET);
 pwsaResults->dwNameSpace = NS_BTH;
 pwsaResults->lpBlob = NULL;
 while (ERROR_SUCCESS == WSALookupServiceNext (hLookup, LUP_RETURN_NAME | LUP_RETURN_ADDR, &dwSize, pwsaResults))
 {
    ASSERT (pwsaResults->dwNumberOfCsAddrs == 1);
    BT_ADDR b = ((SOCKADDR_BTH *)pwsaResults->lpcsaBuffer->RemoteAddr.lpSockaddr)->btAddr;
    bHaveName = pwsaResults->lpszServiceInstanceName && *(pwsaResults->lpszServiceInstanceName);
    wprintf (L"%s%s%04x%08x%s"n", bHaveName ? pwsaResults->lpszServiceInstanceName : L"",
    bHaveName ? L"(" : L"", GET_NAP(b), GET_SAP(b), bHaveName ? L")" : L"");
 }
 WSALookupServiceEnd(hLookup);
 return TRUE;
}


你可以通过创建服务端和客户端套接字来连接两个蓝牙设备。服务端套接字必须设定为监听传入连接并接受客户端套接字;客户端套接字在发送连接请求之前必须要知道要连接到的设备的地址。
你也可以在Microsoft Windows CE上使用串口仿真方便地创建连接,详情参见“利用虚拟串口连接到远程设备”
注意   为了使清晰起见,文中忽略了错误处理
在创建连接前,你必须先获得以下信息:
?span style="FONT: 7pt 'Times New Roman'">            要查询的远程设备的地址,BT_ADDR类型,定义在Ws2bth.h中:
typedef ULONGLONG bt_addr, *pbt_addr, BT_ADDR, *PBT_ADDR;
注意   仅仅客户端须要如此。
?span style="FONT: 7pt 'Times New Roman'">            一个GUID类型变量的服务标识符,或者RFCOMM通道(1到31)。
创建客户端套接字
1.    提供Winsock的版本和实现细节的数据来初始化caller application。可以通过调用WSAStartup函数来获得这个数据。
WSADATA wsd;
WSAStartup (MAKEWORD(1,0), &wsd);
2.    调用socket函数来创建一个蓝牙套接字。
SOCKET client_socket = socket (AF_BT, SOCK_STREAM, BTHPROTO_RFCOMM);
socket函数的参数值将套接字设置为蓝牙服务。
3.    通过设置SOCKADDR_BTH结构体来储存客户端要连接到的远程设备的信息。
a.    创建并初始化SOCKADDR_BTH变量。
SOCKADDR_BTH sa;
memset (&sa, 0, sizeof(sa));
b.    btAddr成员赋值为包含目标设备地址的BT_ADDR变量。
sa.btAddr = b; //b is a BT_ADDR variable
你的程序可以接受字符串类型的设备地址,但必须将其转换并储存为一个BT_ADDR类型变量。
c.    如果服务标识符有效,则将serviceClassId成员设置为基于RFCOMM的服务的GUID。这种情况下,客户端执行SDP查询然后使用得到的服务器通道。
或者
如果你要使用硬编码的通道编号,将port成员变量设置为服务器通道编号。
sa.port = channel & 0xff;
4.    调用connect函数来连接到蓝牙套接字。
if (connect (client_socket, (SOCKADDR *)&sa, sizeof(sa)))
{
 //Perform error handling.
 closesocket (client_socket);
 return 0;
}
传递第3步中设置好的SOCKADDR_BTH来指定目标设备的属性。
连接建立后,你可以通过发送和接收数据来和目标设备通信。
5.    要关闭与目标设备的连接调用closesocket函数关闭蓝牙套接字,并且确保使用CloseHandle函数释放套接字。
closesocket(client_socket);
CloseHandle ((LPVOID)client_socket);
6.    要结束对Winsock服务的使用,调用WSACleanup函数。在程序中对每个成功调用的WSAStartup都必须对应地调用WSACleanup
To create a server socket
1.      提供Winsock的版本和实现细节的数据来初始化caller application。可以通过调用WSAStartup函数来获得这个数据。
WSADATA wsd;
WSAStartup (MAKEWORD(1,0), &wsd);
2.      调用socket函数来创建一个蓝牙套接字。
SOCKET server_socket = socket (AF_BT, SOCK_STREAM, BTHPROTO_RFCOMM);
socket函数的参数值将套接字设置为蓝牙服务。
3.      通过设置SOCKADDR_BTH结构体来储存服务器设备的信息。
SOCKADDR_BTH sa;
memset (&sa, 0, sizeof(sa));
sa.addressFamily = AF_BT;
sa.port = channel & 0xff;
注意   为了避免冲突,在选择服务器通道时建议将channel设置为0,这样RFCOMM将自动使用下一个有效的通道。
结构体中的信息用来将套接字绑定到服务器设备的本地地址上。
4.      调用bind函数绑定第二步中创建的server_socket,传入第三步中创建的sa的引用指定设备信息。
if (bind (server_socket, (SOCKADDR *)&sa, sizeof(sa)))
{
 ... //Perform error handling
 closesocket (server_socket);
 return 0;
}
5.      listen函数来监听客户端蓝牙设备发送的连接请求。
if (listen (server_socket, 5))
{
 ... //Perform error handling
 closesocket (server_socket);
 return 0;
}
6.      accept函数来接受传入的连接请求。
SOCKADDR_BTH sa2;
int size = sizeof(sa2);
SOCKET s2 = accept (server_socket, (SOCKADDR *)&sa2, &size);
调用accept将返回SCOKADDR_BTH类型的客户端地址。
7.      调用closecocket函数来关闭套接字。
closesocket(server_socket);

要结束对Winsock服务的使用,调用WSACleanup函数。在程序中对每个成功调用的WSAStartup都必须对应地调用WSACleanup


你可以利用Microsoft Windows CE的串口模拟器在两个蓝牙设备间建立连接。串口模拟器处于蓝牙协议栈的顶层,在虚拟串口的基础上提供连接RFCOMM的通路。它没有暴露栈的接口,而是提供了一个API层来向远程蓝牙设备开放连接。
Microsoft?Windows?CE的蓝牙实现允许你建立一个piconet。按照蓝牙规范,一个主设备可以同时与7个从设备连接。要了解更多关于piconet的信息,请参见Official Bluetooth Wireless Info Web site上的蓝牙核心规范
当准备好蓝牙栈中的这一层时,就可以创建一个虚拟的服务端或客户端串口来接受或者发起RFCOMM连接。
在你在两个设备间建立连接之前,你必须先知道以下信息:
?span style="FONT: 7pt 'Times New Roman'">        要连接得蓝牙设备的地址,以BT_ADDR类型存储(用于客户端端口)
?span style="FONT: 7pt 'Times New Roman'">        RFCOMM通道编号(1到31)
?span style="FONT: 7pt 'Times New Roman'">        要分配给蓝牙操作的串口编号(0到9)
创建虚拟串口
1.      通过配置PORTEMUPortParams结构体来设置虚拟串口的属性。这个结构体储存了诸如通道和地址等蓝牙详细信息。
对于服务器端口,像下面的示例一样设置PORTEMUPortParams的成员。
PORTEMUPortParams pp;
memset (&pp, 0, sizeof(pp));
pp.flocal = TRUE;
pp.channel = channel & 0xff;
上面的示例中将flocal设为TRUE,使得服务器串口端口能接受传入的连接。服务器通道设置为channel的值。
注意   为了消除冲突,建议你在选择服务器通道时将channel设置为RFCOMM_CHANNEL_MULTIPLE(0xfe),让RFCOMM使用下一个可用的通道。
对于客户端端口,设置PORTEMUPortParams中的devicechanneluiportflags
PORTEMUPortParams pp;
memset (&pp, 0, sizeof(pp));
pp.device = ba;
pp.channel = channel & 0xff;
pp.uiportflags = RFCOMM_PORT_FLAGS_REMOTE_DCB;
在上面的示例中,device赋值成一个存有远程设备地址的BT_ADDR型变量,将uiportflags设置为RFCOMM_PORT_FLAGS_REMOTE_DCB通过RFCOMM层初始化一个远程的链接。
如果不知道服务器通道,客户端可以在uuidService成员中指定服务器的UUID。在这种条件下,SDP查询将自动执行并返回目标通道编号。
2.       通过条用RegisterDevice函数来注册设备,如下例所示。
HANDLE h = RegisterDevice (L"COM", index, L"btd.dll", (DWORD)&pp);
上述示例将端口类型定义为COM,端口号、设备驱动DLL名称如参数所示。同时也在dwInfo参数位置传入在第一步创建的PORTEMUPortParams结构体的地址。RegisterDevice将虚拟串口注册到蓝牙协议栈上。
3.      创建一个字符串来储存串口的端口名称,如下例所示,你必须在名称后添加一个冒号
WCHAR szComPort[30];
wsprintf (szComPort, L"COM%d:", index);
4.       通过调用CreateFile函数来打开串口。
HANDLE hCommPort = CreateFile (szComPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
对于客户端端口,只有在the device is open with read or write access by using CreateFile时才能建立物理连接,当第一个具有读或写权限的句柄关闭的时候服务器和客户端的端口的物理连接都将中止。
每个创建的虚拟串口最多能识别4个打开的句柄,每个句柄都维持其自身的连接和掩码。如果打开文件时的掩码是0,那么这个句柄就只能被WaitCommEvent所使用,而不能与ReadFileWriteFile一起使用。
一旦串口创建好了,它就等价于一个真实的串口,能调用同样的API来操作它。
删除现有的虚拟串口
1.   调用CloseHandle函数,将CreateFile返回的句柄传给它,如下所示。
CloseHandle (hCommPort);
2.   要注销设备,调用DeregisterDevice函数并将RegisterDevice函数返回的句柄传给它,如下所示。
DeregisterDevice (h);
在端口模拟中使用“自动通道绑定”
1.        PORTEMUPortParams中的channel 设置为RFCOMM_CHANNEL_MULTIPLE
PORTEMUPortParams pp;
memset (&pp, 0, sizeof(pp));
pp.channel = RFCOMM_CHANNEL_MULTIPLE;
2.        使用RegisterDeviceCreateFile创建一个虚拟串口。
3.    通过使用IOCTL来确定分配的RFCOMM通道。IOCTL_BLUETOOTH_GET_RFCOMM_CHANNEL.
DWORD port = 0;
DWORD dwSizeOut = 0;
HANDLE hFile;
if (!DeviceIoControl (hFile, IOCTL_BLUETOOTH_GET_RFCOMM_CHANNEL, NULL, 0, &port, sizeof(port), &dwSizeOut, NULL))
{
 // Perform error handling
}



    摘要:蓝牙协议栈基本组成结构及其主要功能    (全文共11757字)——点击此处阅读全文

 

标签:手机开发 | 浏览数(1543) | 评论数(1) | 2006-11-17

一、基本知识

   Win32下串口通信与16位串口通信有很大的区别。在Win32下,可以使用两种编程方式实现串口通信,其一是调用的Windows的API函数,其二是使用ActiveX控件。使用API 调用,可以清楚地掌握串口通信的机制,熟悉各种配置和自由灵活采用不同的流控进行串口通信。下面介绍串口操作的基本知识。

  打开串口:使用CreateFile()函数,可以打开串口。有两种方法可以打开串口,一种是同步方式(NonOverlapped),另外一种异步方式(Overlapped)。使用Overlapped打开时,适当的方法是:
  HANDLE hComm;
  hComm = CreateFile( gszPort,
  GENERIC_READ | GENERIC_WRITE,
  0,
  0,
  OPEN_EXISTING,
  FILE_FLAG_OVERLAPPED,
  0);
  if (hComm == INVALID_HANDLE_VALUE)
  // error opening port; abort

  配置串口:

  1.DCB配置

   DCB(Device Control Block)结构定义了串口通信设备的控制设置。许多重要设置都是在DCB结构中设置的,有三种方式可以初始化DCB。

  (1)通过GetCommState()函数得DCB的初始值,其使用方式为:
DCB dcb = {0};
if (!GetCommState(hComm, &dcb))
// Error getting current DCB settings
else
// DCB is ready for use.

  (2)用BuildCommDCB()函数初始化DCB结构,该函数填充 DCB的波特率、奇偶校验类型、数据位、停止位。对于流控成员函数设置了缺省值。其用法是:
DCB dcb;
FillMemory(&dcb, sizeof(dcb), 0);
dcb.DCBlength = sizeof(dcb);
if (!BuildCommDCB(“9600,n,8,1"", &dcb)) {
// Couldn"'t build the DCB. Usually a problem
// with the communications specification string.
return FALSE;
}
else
// DCB is ready for use.

  (3)用SetCommState()函数手动设置DCB初值。用法如下:
DCB dcb;
FillMemory(&dcb, sizeof(dcb), 0);
if (!GetCommState(hComm, &dcb)) // get current DCB
// Error in GetCommState
return FALSE;
// Update DCB rate.
dcb.BaudRate = CBR_9600 ;
// Set new state.
if (!SetCommState(hComm, &dcb))
// Error in SetCommState.
  Possibly a problem with the communications
// port handle or a problem with the DCB structure itself.

  手动设置DCB值时,DCB的结构的各成员的含义,可以参看MSDN帮助。

   2.流控设置

  硬件流控:串口通信中的硬件流控有两种,DTE/DSR方式和RTS/CTS方式,这与DCB结构的初始化有关系,DCB结构中的OutxCtsFlow、 fOutxDsrFlow、fDsrSensitivity、fRtsControl、fDtrControl几个成员的初始值很关键,不同的值代表不同流控,也可以自己设置流控,但建议采用标准流行的流控方式。采用硬件流控时,DTE、DSR、RTS、CTS的逻辑位直接影响到数据的读写及收发数据的缓冲区控制。

   软件流控:串口通信中采用特殊字符XON和XOFF作为控制串口数据的收发。与此相关的DCB成员是:fOut、fInX、XoffChar、XonChar、 XoffLim和XonLim。具体含义参见MSDN帮助。

   串口读写操作:串口读写有两种方式:同步方式(NonOverlapped)和异步方式(Overlapped)。同步方式是指必须完成了读写操作,函数才返回,这可能造成程序死掉,因为如果在读写时发生了错误,永远不返回就会出错,可能线程将永远等待在那儿。而异步方式则灵活得多,一旦读写不成功,就将读写挂起,函数直接返回,可以通过GetLastError函数得知读写未成功的原因,所以常常采用异步方式操作。

   读操作:ReadFile()函数用于完成读操作。异步方式的读操作为:
  DWORD dwRead;
  BOOL fWaitingOnRead = FALSE;
  OVERLAPPED osReader = {0};
  // Create the overlapped event. Must be closed before exiting
  // to avoid a handle leak.
  osReader.hEvent = CreateEvent
  (NULL, TRUE, FALSE, NULL);
  if (osReader.hEvent == NULL)
  // Error creating overlapped event; abort.
  if (!fWaitingOnRead) {
  // Issue read operation.
  if (!ReadFile(hComm, lpBuf, READ_BUF_SIZE,
   &dwRead, &osReader)) {
  if (GetLastError() != ERROR_IO_PENDING)
    // read not delayed?
  // Error in communications; report it.
  else
  fWaitingOnRead = TRUE;
  }
  else {
  // read completed immediately
  HandleASuccessfulRead(lpBuf, dwRead);
  }
  }

   如果读操作被挂起,可以调用WaitForSingleObject()函数或WaitForMuntilpleObjects()函数等待读操作完成或者超时发生,再调用 GetOverlappedResult()得到想要的信息。

   写操作:与读操作相似,故不详述,调用的API函数是: WriteFile函数。

   串口状态:

  (1)通信事件:用SetCommMask()函数设置想要得到的通信事件的掩码,再调用WaitCommEvent()函数检测通信事件的发生。可设置的通信事件标志(即SetCommMask()函数所设置的掩码)可以有EV_BREAK、EV_CTS、EV_DSR、 EV_ERR、EV_RING、EV_RLSD、EV_RXCHAR、EV_RXFLAG、EV_TXEMPTY。

   注意:1对于EV_RING标志的设置,WIN95是不会返回EV_RING事件的,因为WIN95不检测该事件。2设置EV_RXCHAR,可以检测到字符到达,但是在绑定此事件和ReadFile()函数一起读取串口接收数据时,可能会出现错误,造成少读字节数,具体原因查看MSDN帮助。可以采用循环读的办法,另外一个比较好的解决办法是调用ClearCommError()函数,确定在一次读操作中在缓冲区中等待被读的字节数。

  (2)错误处理和通信状态:在串口通信中,可能会产生很多的错误,使用ClearCommError()函数可以检测错误并且清除错误条件。

   (3)Modem状态:用SetcommMask()可以包含很多事件标志,但是这些事件标志只指示在串口线路上的电压变化情况。而调用 GetCommModemStatus()函数可以获得线路上真正的电压状态。

   扩展函数:如果应用程序想用自己的流控,可以使用 EscapeCommFunction()函数设置DTR和RTS线路的电平。

   通信超时:在通信中,超时是个很重要的考虑因素,因为如果在数据接收过程中由于某种原因突然中断或停止,如果不采取超时控制机制,将会使得I/O线程被挂起或无限阻塞。串口通信中的超时设置分为两步,首先设置 COMMTIMEOUTS结构的五个变量,然后调用SetcommTimeouts()设置超时值。对于使用异步方式读写的操作,如果操作挂起后,异步成功完成了读写,WaitForSingleObject()或 WaitForMultipleObjects()函数将返回WAIT_OBJECT_0,GetOverlappedResult()返回TRUE。其实还可以用GetCommTimeouts()得到系统初始值。

   关闭串口:程序结束或需要释放串口资源时,应该正确关闭串口,关闭串口比较简单,使用API调用CloseHandle()关闭串口的句柄就可以了。

  调用方法为:CloseHandle(hComm);

   但是值得注意的是在关闭串口之前必须保证读写串口线程已经退出,否则会引起误操作,一般采用的办法是使用事件驱动机制,启动一事件,通知串口读写线程强制退出,在线程退出之前,通知主线程可以关闭串口。
二、实现

  1.程序设计思路

   对于不同的应用程序,虽然界面不同,但是如果采用串口与主机之间的通信,对串口的处理方式大致相似,无非就是通过串口收发数据,对于通过串口接收到的数据,交给上层软件处理显示,对于上层要发给串口的数据,进行转发。但在实际编程中,由于采用的通信方式和流控不同,串口设置也不同,这就涉及到 DCB的初始化问题和读写串口等细节问题。串口通信应用程序设计的总体思路(即操作过程)是:首先,确定要打开的串口名、波特率、奇偶校验方式、数据位、停止位,传递给CreateFile()函数打开特定串口;其次,为了保护系统对串口的初始设置,调用 GetCommTimeouts()得到串口的原始超时设置;然后,初始化DCB对象,调用SetCommState() 设置DCB,调用SetCommTimeouts()设置串口超时控制;再次,调用SetupComm()设置串口接收发送数据的缓冲区大小,串口的设置就基本完成,之后就可以启动读写线程了。

  一般来说,串口的读写由串口读写线程完成,这样可以避免读写阻塞时主程序死锁。对于全双工的串口读写,应该分别开启读线程和写线程;对于半双工和单工的,建议只需开启一个线程即可。在线程中,按照预定好的通信握手方式,正确检测串口状态,读取发送串口数据。

  2.实现细节

  在半双工的情况下,首先完成必要的串口配置,成功打开串口、DCB设置、超时设置;然后开启线程,如: CwinThread hSerialThread = (CWinThread*) AfxBeginThread(SerialOperation,hWnd,THREAD_PRIORITY_NORMAL); 其中开启之线程为SerialOperation,优先级为普通。

   全双工情况下的串口编程,与单工差不多,区别仅仅在于启动双线程,分别为读线程和写线程,读线程根据不同的事件或消息,通过不断查询串口所收到的有效数据,完成读操作;写线程通过接收主线程的发送数据事件和要发送的数据,向串口发送。

 

标签:软件开发 | 浏览数(423) | 评论数(0) | 2006-11-06

串口是常用的计算机与外部串行设备之间的数据传输通道,由于串行通信方便易行,所以应用广泛。我们可以利用Windows API 提供的通信函数编写出高可移植性的串行通信程序。本实例介绍在Visual C++6.0下如何利用Win32 API 实现串行通信程序。程序编译运行后的界面效果如图一所示:

图一、串口通信示例程序


  一、实现方法

  在Win16中,可以利用OpenComm()、CloseComm()和WriteComm()等函数打开、关闭和读写串口。但在Win32中,串口和其他通信设备均被作为文件处理,串口的打开、关闭和读写等操作所用的API函数与操作文件的函数相同。可通过CreateFile()函数打开串口;通过CloseFile()函数关闭串口;通过DCB结构、CommProp()、GetCommProperties()、SetCommProperties()、GetCommState()及SetCommState()等函数设置串口状态,通过函数ReadFile()和WritFile()等函数读写串口。下面来详细介绍其实现原理。

  对于串行通信设备,Win32 API支持同步和异步两种I/O操作。同步操作方式的程序设计相对比较简单,但I/O操作函数在I/O操作结束前不能返回,这将挂起调用线程,直到I/O操作结束。异步操作方式相对要复杂一些,但它可让耗时的I/O操作在后台进行,不会挂起调用线程,这在大数据量通信的情况下对改善调用线程的响应速度是相当有效的。异步操作方式特别适合同时对多个串行设备进行I/O操作和同时对一个串行设备进行读/写操作。

  串行设备的初始化

  串行设备的初始化是利用CreateFile()函数实现的。该函数获得串行设备句柄并对其进行通信参数设置,包括设置输出/接收缓冲区大小、超时控制和事件监视等。 例如下面的代码实现了串口的初始化:
//串行设备句柄;

HANDLE hComDev=0;

//串口打开标志;

BOOL bOpen=FALSE;

//线程同步事件句柄;

HANDLE hEvent=0;
DCB dcb;
COMMTIMEOUTS timeouts;
//设备已打开
if(bOpen) return FALSE; 
//打开COM1
if((hComDev=CreateFile(“COM1”,GENERIC?READ|GENERIC?WRITE,0,NULL,OPEN?EXISTING,FILE?ATTRIBUTE?NORMAL,NULL))==INVALID?HANDLE?VALUE) 
 return FALSE;
//设置超时控制
SetCommTimeouts(hComDev,&timeouts);
//设置接收缓冲区和输出缓冲区的大小
SetupComm(hComDev,1024,512);
//获取缺省的DCB结构的值
GetCommState(hComDev,&dcb);
//设定波特率为9600 bps

 dcb.BaudRate=CBR?9600;

//设定无奇偶校验

 dcb.fParity=NOPARITY;

//设定数据位为8

 dcb.ByteSize=8;

 //设定一个停止位

 dcb.StopBits=ONESTOPBIT;

//监视串口的错误和接收到字符两种事件

 SetCommMask(hComDev,EV?ERR|EV?RXCHAR);

//设置串行设备控制参数

 SetCommState(hComDev,&dcb);

//设备已打开

 bOpen=TRUE;

 //创建人工重设、未发信号的事件

 hEvent=CreateEvent(NULL,FALSE,FALSE,

“WatchEvent”);

//创建一个事件监视线程来监视串口事件

 AfxBeginThread(CommWatchProc,pParam);

}


  在设置串口DCB结构的参数时,不必设置每一个值。首先读出DCB缺省的参数设置,然后只修改必要的参数,其他参数都取缺省值。由于对串口进行的是同步I/O操作,所以除非指定进行监测的事件发生,否则WaitCommEvent()函数不会返回。在串行设备初始化的最后要建立一个单独的监视线程来监视串口事件,以免挂起当前调用线程,其中pParam可以是一个对事件进行处理的窗口类指针。
 
  如果要进行异步I/O操作,打开设备句柄时,CreateFile的第6个参数应增加FILE?FLAG?OVERLAPPED 标志。

  数据发送

  数据发送利用WriteFile()函数实现。对于同步I/O操作,它的最后一个参数可为NULL;而对异步I/O操作,它的最后一个参数必需是一个指向OVERLAPPED结构的指针,通过OVERLAPPED结构来获得当前的操作状态。
BOOL WriteComm(LPCVOID lpSndBuffer,DWORD dwBytesToWrite)

{
 //lpSndBuffer为发送数据缓冲区指针,
 dwBytesToWrite为将要发送的字节长度
 //设备已打开
 BOOL bWriteState;
 //实际发送的字节数
 DWORD dwBytesWritten;
 //设备未打开
 if(!bOpen) return FALSE;
 bWriteState=WriteFile(hComDev,lpSndBuffer,dwBytesToWrite,&dwBytesWritten,NULL);
 if(!bWriteState || dwBytesToWrite!=dwBytesWritten)
  //发送失败
  return FALSE;
 else
  //发送成功
  return TRUE;
}


  数据接收

  接收数据的任务由ReadFile函数完成。该函数从串口接收缓冲区中读取数据,读取数据前,先用ClearCommError函数获得接收缓冲区中的字节数。接收数据时,同步和异步读取的差别同发送数据是一样的。
DWORD ReadComm(LPVOID lpInBuffer,DWORD dwBytesToRead)

{
 //lpInBuffer为接收数据的缓冲区指针, dwBytesToRead为准备读取的数据长度(字节数)
 //串行设备状态结构

 COMSTAT ComStat;
 DWORD dwBytesRead,dwErrorFlags; 
 //设备未打开
 if(!bOpen) return 0;
 //读取串行设备的当前状态
 ClearCommError(hComDev,&dwErrorFlags,&ComStat);
 //应该读取的数据长度
 dwBytesRead=min(dwBytesToRead,ComStat.cbInQue);
 if(dwBytesRead>0)
  //读取数据
  if(!ReadFile(hComDev,lpInBuffer,dwBytesRead,&dwBytesRead,NULL))
   dwBytesRead=0;
 return dwBytesRead;
}


  事件监视线程

  事件监视线程对串口事件进行监视,当监视的事件发生时,监视线程可将这个事件发送(SendMessage)或登记(PostMessage)到对事件进行处理的窗口类(由pParam指定)中。
UINT CommWatchProc(LPVOID pParam)
{
 DWORD dwEventMask=0; //发生的事件;
 while(bOpen)
 {
  //等待监视的事件发生
  WaitCommEvent(hComDev, &dwEventMask,NULL);
  if ((dwEventMask & EV?RXCHAR)==EV?RXCHAR)
   ……//接收到字符事件后,可以将此消息登记到由pParam有指定的窗口类中进行处理
  if(dwEventMask & EV?ERR)==EV?ERROR)
   ……//发生错误时的处理
 }
 SetEvent(hEvent);
 //发信号,指示监视线程结束
 return 0;
}


  关闭串行设备

  在整个应用程序结束或不再使用串行设备时,应将串行设备关闭,包括取消事件监视,将设备打开标志bOpen置为FALSE以使事件监视线程结束,清除发送/接收缓冲区和关闭设备句柄。
void CloseSynComm()
{
 if(!bOpen) return;
 //结束事件监视线程
 bOpen=FALSE;
 SetCommMask(hComDev,0);
 //取消事件监视,此时监视线程中的WaitCommEvent将返回
 WaitForSingleObject(hEvent,INFINITE);
 //等待监视线程结束
 CloseHandle(hEvent); //关闭事件句柄
 //停止发送和接收数据,并清除发送和接收缓冲区
 PurgeComm(hComDev,PURGE?TXABORT| PURGE?RXABORT|PURGE?TXCLEAR|PURGE?RXCLEAR);
 //关闭设备句柄
 CloseHandle(hComDev);
}


  二、编程步骤

  1、 启动Visual C++6.0,生成一个基于对话框的的应用程序,将该程序命名为“SerealCom”;

  2、 按照图一的界面设计对话框,具体设置参见代码部分;

  3、 使用Class Wizard为对话框的按钮添加鼠标单击消息响应函数;

  4、 添加代码,编译运行程序。

      三、程序代码
//////////////////////////////////////////////////////////////

#if !defined(_COMM_ACCESS_FUNCTIONS_AND_DATA)
#define _COMM_ACCESS_FUNCTIONS_AND_DATA
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#define EVENTCHAR 0x0d
#define MAXBLOCKLENGTH 59

extern BYTE XwCom;
extern BYTE sCom1[5],sCom2[MAXBLOCKLENGTH+12];
extern sCom3[MAXBLOCKLENGTH+12];
extern BYTE opation;
extern short ComNum;

#define FC_DTRDSR 0x01
#define FC_RTSCTS 0x02
#define FC_XONXOFF 0x04
#define ASCII_BEL 0x07
#define ASCII_BS 0x08
#define ASCII_LF 0x0A
#define ASCII_CR 0x0D
#define ASCII_XON 0x11
#define ASCII_XOFF 0x13

class CComStatus
{
 public:
  HANDLE m_hCom;
  BYTE m_bComId;
  BYTE m_bByteSize;
  BYTE m_bStopBits;
  BYTE m_bParity;
  DWORD m_dwBaudRate;

  //WORD m_fChEvt;

  char m_bEvtChar;
  DWORD m_fBinary;
  BOOL m_bConnected;
  BOOL m_fXonXoff;
  BOOL m_bFlowCtrl;
  OVERLAPPED m_rdos;
  OVERLAPPED m_wtos;

  //functions

  CComStatus();
  CComStatus(BYTE bComId,BYTE bByteSize,BYTE bStopBits,BYTE bParity,
    DWORD dwBaudRate,/*WORD fChEvt,*/char bEvtChar,DWORD fBinary);
  BOOL OpenConnection();
  BOOL CloseConnection();
  BOOL SetupConnection();
  BOOL IsConnected();
};

UINT CommWatchProc( LPVOID lpData );
BOOL WriteCommBlock( CComStatus& comDev, LPSTR lpByte , DWORD dwBytesToWrite);
int ReadCommBlock(CComStatus& comDev,LPSTR lpszBlock, int nMaxLength );
int ReadCommBlockEx(CComStatus& comDev,LPSTR lpszBlock, int nMaxLength,DWORD dwTimeOut);
#endif

///////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "com232.h"

BYTE XwCom=0x40;
BYTE sCom1[5],sCom2[MAXBLOCKLENGTH+12],sCom3[MAXBLOCKLENGTH+12];
BYTE opation;
short ComNum;
CComStatus::CComStatus()
{
 m_hCom = NULL;
 m_bComId = (char)ComNum;//COM1
 m_bByteSize=8;
 m_bStopBits=ONESTOPBIT;
 m_bParity=NOPARITY;
 m_dwBaudRate=9600;
 m_bEvtChar=EVENTCHAR;
 m_fBinary=1;
 m_bConnected = FALSE;
 m_bFlowCtrl = FC_XONXOFF ;
 m_fXonXoff = FALSE;
}

CComStatus::CComStatus(BYTE bComId,BYTE bByteSize,BYTE bStopBits,BYTE bParity,DWORD dwBaudRate,/*WORD fChEvt,*/char bEvtChar,DWORD fBinary)
{
 m_hCom = NULL;
 m_bComId = bComId;
 m_bByteSize=bByteSize;
 m_bStopBits=bStopBits;
 m_bParity=bParity;
 m_dwBaudRate=dwBaudRate;
 m_bEvtChar=bEvtChar;
 m_fBinary=fBinary;
 m_bConnected = FALSE;
 m_bFlowCtrl = FC_XONXOFF ;
 m_fXonXoff = FALSE;
}

BOOL CComStatus::OpenConnection()
{
 char csCom[10];
 COMMTIMEOUTS CommTimeOuts ;
 if((m_bComId < 0) || (m_bComId > 4))
  return FALSE;//从COM1到COM4
 if(m_hCom)//if already open
 return FALSE;

 //OVERLAPPED包含异步I/O信息

 m_rdos.Offset = 0;
 m_rdos.OffsetHigh = 0;
 m_rdos.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
 if(m_rdos.hEvent == NULL)
  return FALSE;
 m_wtos.Offset = 0;
 m_wtos.OffsetHigh = 0;
 m_wtos.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
 if(m_wtos.hEvent == NULL)
 {
  CloseHandle(m_rdos.hEvent);
  return FALSE;
 }

 wsprintf(csCom,"COM%d",m_bComId);

 m_hCom = CreateFile(csCom,GENERIC_READ | GENERIC_WRITE, 0,NULL, OPEN_EXISTING,ILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,NULL);

 if(m_hCom == INVALID_HANDLE_VALUE) {
  //dwError = GetLastError();
  // handle error
  return FALSE;
 }
 else
 {
  SetCommMask( m_hCom, EV_RXCHAR ) ; // get any early notifications
  SetupComm( m_hCom, 4096, 4096 ) ; // setup device buffers
  // purge any information in the buffer

  PurgeComm( m_hCom, PURGE_TXABORT | PURGE_RXABORT |PURGE_TXCLEAR | PURGE_RXCLEAR ) ;

  // set up for overlapped I/O

  DWORD dwTemp = 1000 / (this->m_dwBaudRate / 8);
  CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF ;
  CommTimeOuts.ReadTotalTimeoutMultiplier = 0;//((dwTemp > 0) ? dwTemp : 1);
  CommTimeOuts.ReadTotalTimeoutConstant = 1000 ;

  // CBR_9600 is approximately 1byte/ms. For our purposes, allow
  // double the expected time per character for a fudge factor.

  CommTimeOuts.WriteTotalTimeoutMultiplier =2*CBR_9600/this->m_dwBaudRate;//( npTTYInfo ) ;
  CommTimeOuts.WriteTotalTimeoutConstant = 0;//1000 ;

  SetCommTimeouts( m_hCom, &CommTimeOuts ) ;
 }
 if(!SetupConnection())
 {
  CloseConnection();
  return FALSE;
 }
 EscapeCommFunction( m_hCom, SETDTR );
 m_bConnected = TRUE;
 return TRUE;
}

BOOL CComStatus::CloseConnection()
{
 if (NULL == m_hCom)
  return ( TRUE ) ;
 // set connected flag to FALSE
 m_bConnected = FALSE;
 // disable event notification and wait for thread
 // to halt
 SetCommMask( m_hCom, 0 ) ;
 EscapeCommFunction( m_hCom, CLRDTR ) ;
 // purge any outstanding reads/writes and close device handle
 PurgeComm( m_hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ) ;
 CloseHandle( m_hCom ) ;
 m_hCom = NULL;

 // change the selectable items in the menu

 CloseHandle(m_rdos.hEvent);
 CloseHandle(m_wtos.hEvent);
 return ( TRUE ) ;
}

BOOL CComStatus::SetupConnection()
{
 BOOL fRetVal ;
 BYTE bSet ;
 DCB dcb ;
 if(m_hCom == NULL)
  return FALSE;
 dcb.DCBlength = sizeof( DCB ) ;
 GetCommState( m_hCom, &dcb ) ;
 dcb.BaudRate = this->m_dwBaudRate;
 dcb.ByteSize = this->m_bByteSize;
 dcb.Parity = this->m_bParity;
 dcb.StopBits = this->m_bStopBits ;
 dcb.EvtChar = this->m_bEvtChar ;
 // setup hardware flow control
 bSet = (BYTE) ((m_bFlowCtrl & FC_DTRDSR) != 0) ;
 dcb.fOutxDsrFlow = bSet ;
 if (bSet)
  dcb.fDtrControl = DTR_CONTROL_HANDSHAKE ;
 else
  dcb.fDtrControl = DTR_CONTROL_ENABLE ;
 bSet = (BYTE) ((m_bFlowCtrl & FC_RTSCTS) != 0) ;
 dcb.fOutxCtsFlow = bSet ;
 if (bSet)
  dcb.fRtsControl = RTS_CONTROL_HANDSHAKE ;
 else
  dcb.fRtsControl = RTS_CONTROL_ENABLE ;
 // setup software flow control
 bSet = (BYTE) ((m_bFlowCtrl & FC_XONXOFF) != 0) ;
 dcb.fInX = dcb.fOutX = bSet ;
 dcb.XonChar = ASCII_XON ;
 char xon = ASCII_XON ;
 dcb.XoffChar = ASCII_XOFF ;
 char xoff = ASCII_XOFF ;
 dcb.XonLim = 100 ;
 dcb.XoffLim = 100 ;
 // other various settings
 dcb.fBinary = TRUE ;
 dcb.fParity = TRUE ;
 fRetVal = SetCommState( m_hCom, &dcb ) ;
 return ( fRetVal ) ;
} // end of SetupConnection()

BOOL CComStatus::IsConnected()
{
 return m_bConnected;
}

UINT CommWatchProc( LPVOID lpData )
{
 DWORD dwEvtMask ;
 //NPTTYINFO npTTYInfo = (NPTTYINFO) lpData ;
 OVERLAPPED os ;
 int nLength ;
 //BYTE abIn[ MAXBLOCK + 1] ;

 CComStatus * pCom = (CComStatus *)lpData;
 memset( &os, 0, sizeof( OVERLAPPED ) ) ;
 // create I/O event used for overlapped read

 os.hEvent = CreateEvent( NULL, // no security
  TRUE, // explicit reset req
  FALSE, // initial event reset
  NULL ) ; // no name

 if (os.hEvent == NULL)
 {
  MessageBox( NULL, "Failed to create event for thread!", "TTY Error!",MB_ICONEXCLAMATION | MB_OK ) ;
  return ( FALSE ) ;
 }
 if (!SetCommMask( pCom->m_hCom, EV_RXCHAR ))
  return ( FALSE ) ;
 char buf[256];
 while ( pCom->m_bConnected )
 {
  dwEvtMask = 0 ;
  WaitCommEvent( pCom->m_hCom, &dwEvtMask, NULL );
  if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR)
  {
   if ((nLength = ReadCommBlock( *pCom, (LPSTR) buf, 255 )))
   {
    //WriteTTYBlock( hTTYWnd, (LPSTR) abIn, nLength ) ;
    buf[nLength]='"0';
    AfxMessageBox(buf);
   }
  }
 }
 CloseHandle( os.hEvent ) ;
 return( TRUE ) ;
} // end of CommWatchProc()

int ReadCommBlock(CComStatus& comDev,LPSTR lpszBlock, int nMaxLength )
{
 BOOL fReadStat ;
 COMSTAT ComStat ;
 DWORD dwErrorFlags;
 DWORD dwLength;
 DWORD dwError;

 char szError[ 10 ] ;

 // only try to read number of bytes in queue

 ClearCommError( comDev.m_hCom, &dwErrorFlags, &ComStat ) ;
 dwLength = min( (DWORD) nMaxLength, ComStat.cbInQue ) ;

 if (dwLength > 0)
 {
  fReadStat = ReadFile( comDev.m_hCom, lpszBlock,dwLength, &dwLength, &(comDev.m_rdos) ) ;
  if (!fReadStat)
  {
   if (GetLastError() == ERROR_IO_PENDING)
   {
    OutputDebugString(""n"rIO Pending");
    while(!GetOverlappedResult( comDev.m_hCom ,&(comDev.m_rdos), &dwLength, TRUE ))
    {
     dwError = GetLastError();
     if(dwError == ERROR_IO_INCOMPLETE)
      // normal result if not finished
      continue;
     else
     {
      // an error occurred, try to recover
      wsprintf( szError, "<CE-%u>", dwError ) ;
      ClearCommError( comDev.m_hCom , &dwErrorFlags, &ComStat ) ;
      break;
     }
    }
   }
   else
   {
    // some other error occurred
    dwLength = 0 ;
    ClearCommError( comDev.m_hCom , &dwErrorFlags, &ComStat ) ;
   }
  }
 }
 return ( dwLength ) ;
} // end of ReadCommBlock()

int ReadCommBlockEx(CComStatus& comDev,LPSTR lpszBlock, int nMaxLength,DWORD dwTimeOut)
{
 LPSTR lpOffset=lpszBlock;
 int nReadCount = 0;
 char chBuf;
 //time_t beginTime,endTime;
 if(!comDev.m_hCom)
  return 0;
 if(dwTimeOut <= 0)
  return 0;
 MSG msg;
 //time(&beginTime);
 DWORD dwLastTick,dwNowTick,dwGoneTime;
 dwGoneTime = 0;
 dwLastTick = GetTickCount();
 dwNowTick = dwLastTick;
 // double diftime;
 do
 {
  if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
  {
   ::TranslateMessage(&msg);
   ::DispatchMessage(&msg);
  }
  if(ReadCommBlock(comDev,&chBuf,1) > 0)
  {
   //TRACE("----get a char----"n");
   *lpOffset = chBuf;
   lpOffset ++;
   nReadCount ++;
  }
  dwNowTick = GetTickCount();
  if(dwNowTick < dwLastTick)
  {
   dwLastTick = dwNowTick;
  }

  dwGoneTime = dwNowTick - dwLastTick;

  //TRACE("gon time = %lu"n",dwGoneTime);

 }while((nReadCount < nMaxLength) && (dwGoneTime < dwTimeOut));
 return (nReadCount);
}//end ReadCommBlockEx

BOOL WriteCommBlock( CComStatus& comDev, LPSTR lpByte , DWORD dwBytesToWrite)
{
 BOOL fWriteStat ;
 DWORD dwBytesWritten ;
 DWORD dwErrorFlags;
 DWORD dwError;
 DWORD dwBytesSent=0;
 COMSTAT ComStat;

 char szError[ 128 ] ;

 fWriteStat = WriteFile( comDev.m_hCom , lpByte, dwBytesToWrite,&dwBytesWritten, &( comDev.m_wtos) ) ;
 if (!fWriteStat)
 {
  if(GetLastError() == ERROR_IO_PENDING)
  {
   while(!GetOverlappedResult( comDev.m_hCom,&(comDev.m_wtos), &dwBytesWritten, TRUE ))
   {
    dwError = GetLastError();
    if(dwError == ERROR_IO_INCOMPLETE)
    {
     // normal result if not finished
     dwBytesSent += dwBytesWritten;
     continue;
    }
    else
    {
     // an error occurred, try to recover
     wsprintf( szError, "<CE-%u>", dwError ) ;
     ClearCommError( comDev.m_hCom, &dwErrorFlags, &ComStat ) ;
     break;
    }
   }
   dwBytesSent += dwBytesWritten;
   if( dwBytesSent != dwBytesToWrite )
    wsprintf(szError,""nProbable Write Timeout: Total of %ld bytes sent", dwBytesSent);
   else
    wsprintf(szError,""n%ld bytes written", dwBytesSent);
   OutputDebugString(szError);
  }
  else
  {
   // some other error occurred
   ClearCommError( comDev.m_hCom, &dwErrorFlags, &ComStat ) ;
   return ( FALSE );
  }
 }
 return ( TRUE ) ;
} // end of WriteCommBlock()


  四、小结

  以上给出了用Win32 API设计串行通信的基本思路,在实际应用中,我们可以利用Win32 API设计出满足各种需要的串行通信程序。

 

标签:软件开发 | 浏览数(450) | 评论数(0) | 2006-11-06

ASCII 打印字符

数字 32–126 分配给了能在键盘上找到的字符,当您查看或打印文档时就会出现。数字 127 代表 DELETE 命令。
十进制 字符  十进制 字符
32 space  80 P
33 !  81 Q
34 "  82 R
35 #  83 S
36 $  84 T
37 %  85 U
38 &  86 V
39 '  87 w
40 (  88 X
41 )  89 Y
42 *  90 Z
43 +  91 [
44 ,  92 "
45 -  93 ]
46 .  94 ^
47 /  95 _
48 0  96 `
49 1  97 a
50 2  98 b
51 3  99 c
52 4  100 d
53 5  101 e
54 6  102 f
55 7  103 g
56 8  104 h
57 9  105 i
58 :  106 j
59 ;  107 k
60 <  108 l
61 =  109 m
62 >  110 n
63 ?  111 o
64 @  112 p
65 A  113 q
66 B  114 r
67 C  115 s
68 D  116 t
69 E  117 u
70 F  118 v
71 G  119 w
72 H  120 x
73 I  121 y
74 J  122 z
75 K  123 {
76 L  124 |
77 M  125 }
78 N  126 ~
79 O  127 DEL

 

 

扩展 ASCII 打印字符

扩展的 ASCII 字符满足了对更多字符的需求。扩展的 ASCII 包含 ASCII 中已有的 128 个字符(数字 0–32 显示在下图中),又增加了 128 个字符,总共是 256 个。即使有了这些更多的字符,许多语言还是包含无法压缩到 256 个字符中的符号。因此,出现了一些 ASCII 的变体来囊括地区性字符和符号。

例如,许多软件程序把 ASCII 表(又称作 ISO 8859-1)用于北美、西欧、澳大利亚和非洲的语言。

扩展的 ASCII 打印字符表十进制 字符  十进制 字符
128 ?nbsp; 192 └
129 ?nbsp; 193 ┴
130 ?nbsp; 194 ┬
131 ?nbsp; 195 ├
132 ?nbsp; 196 ─
133 ?nbsp; 197 ┼
134 ?nbsp; 198 ╞
135 ?nbsp; 199 ╟
136 ?nbsp; 200 ╚
137 ?nbsp; 201 ╔
138 ?nbsp; 202 ╩
139 ?nbsp; 203 ╦
140 ?nbsp; 204 ╠
141 ?nbsp; 205 ═
142 ?nbsp; 206 ╬
143 ?nbsp; 207 ╧
144 ?nbsp; 208 ╨
145 ?nbsp; 209 ╤
146 ?nbsp; 210 ╥
147 ?nbsp; 211 ╙
148 ?nbsp; 212 ?br /> 149 ?nbsp; 213 ╒
150 ?nbsp; 214 ╓
151 ?nbsp; 215 ╫
152   216 ╪
153 ?nbsp; 217 ┘
154 ?nbsp; 218 ┌
155 ?nbsp; 219 ?euro;
156 ?nbsp; 220 ▄
157 ?nbsp; 221 ?bdquo;
158 ?  222 ?
159 ƒ  223 ?
160 ?nbsp; 224 α
161 ?nbsp; 225 ?br /> 162 ?nbsp; 226 Γ
163 ?nbsp; 227 π
164 ?nbsp; 228 Σ
165 ?nbsp; 229 σ
166 ?nbsp; 230 ?br /> 167 ?nbsp; 231 τ
168 ?nbsp; 232 Φ
169 ?  233 Θ
170 ?nbsp; 234 Ω
171 ?nbsp; 235 δ
172 ?nbsp; 236 ∞
173 ?nbsp; 237 φ
174 ?nbsp; 238 ε
175 ?nbsp; 239 ∩
176 ?  240 ≡
177 ?  241 ?br /> 178 ?circ;  242 ≥
179 │  243 ≤
180 ┤  244 ?
181 ╡  245 ?
182 ╢  246 ?br /> 183 ╖  247 ≈
184 ╕  248 ≈
185 ╣  249 ?
186 ║  250 ?br /> 187 ╗  251 √
188 ╝  252 ?
189 ╜  253 ?br /> 190 ╛  254 ■
191 ┐  255 

 

 

ASCII 非打印控制字符

ASCII 表上的数字 0–31 分配给了控制字符,用于控制像打印机等一些外围设备。例如,12 代表换页/新页功能。此命令指示打印机跳到下一页的开头。

ASCII 非打印控制字符表十进制 字符  十进制 字符
0 空  16 数据链路转意
1 头标开始  17 设备控制 1
2 正文开始  18 设备控制 2
3 正文结束  19 设备控制 3
4 传输结束  20 设备控制 4
5 查询  21 反确认
6 确认  22 同步空闲
7 震铃  23 传输块结束
8 backspace  24 取消
9 水平制表符  25 媒体结束
10 换行/新行  26 替换
11 竖直制表符  27 转意
12 换页/新页  28 文件分隔符
13 回车  29 组分隔符
14 移出  30 记录分隔符
15 移入  31 单元分隔符

 

 

ASCII 之外

另一个更新的字符表称为 Unicode (Unicode:Unicode Consortium 开发的一种字符编码标准。该标准采用多(于一)个字节代表每一字符,实现了使用单个字符集代表世界上几乎所有书面语言。)。 因为 Unicode 表大得多,它可以表示 65,536 个字符,而 ASCII 表只能表示 128 个字符,扩展的 ASCII 表也只能表示 256 个字符。这一更大的容量使不同语言的大多数字符都能包含在同一个字符集中。

 

标签:软件开发 | 浏览数(552) | 评论数(0) | 2006-11-02

电脑各种中英文信息对照及错误信息总汇

一、BIOS中的提示信息


提示信息说明
DriveAerror驱动器A错误
Systemhalt系统挂起
Keyboardcontrollererror键盘控制器错误
Keyboarderrorornokeyboardpresent键盘错误或者键盘不存在
BIOSROMchecksumerrorBIOS ROM校验错误
Singlehardiskcablefail当硬盘使用Cable选项时硬盘安装位置不正确
FDDControllerFailureBIOS软盘控制器错误
HDDControllerFailureBIOS硬盘控制器错误
DriverError驱动器错误
CacheMemoryBad,DonotEnableCache高速缓存Cache损坏,不能使用
Error:UnabletocontrolA20line错误提示:不能使用A20地址控制线
Memorywrite/Readfailure内存读写失败
Memoryallocationerror内存定位错误
CMOSBatterystateLowCMOS没电了
Keyboardinterfaceerror键盘接口错误
Harddiskdrivefailure加载硬盘失败
Harddisknotpresent硬盘不存在
Floppydisk(s)fail(40)软盘驱动器加载失败,一般是数据线插反,电源线没有插接,CMOS内部软驱设置错误
CMOSchecksumerror-efaultsloaded.CMOS校验错误,装入缺省(默认)设置


二、BIOS刷新失败后,Bootblock启动时出现的提示信息


提示信息说明
DetectingfloppydriveAmedia...检测软驱A的格式
Drivemediais:1.44Mb1.2Mb720Kb360K驱动器格式是1.44Mb、12Mb、720kb、360kb的一种
DISKBOOTFAILURE,INSERTSYSTEMDISKANDPRESSENTER磁盘引导失败,插入系统盘后按任意键继续


三、MBR主引导区提示信息


提示信息说明
Invalidpartitiontable无效的分区表
Errorloadingoperatingsystem不能装入引导系统
Missingoperatingsystem系统引导文件丢失

  说明:如果在计算机启动过程中,在硬件配置清单下方(也就时在平时正常启动时出现StartingWindows98…的地方)出现不可识别字符,此时可判断硬盘分区表损坏。如果你的硬盘上有重要资料,这时你不要轻易进行分区,可找专业的数据恢复公司。


四、DOS活动分区中的提示信息


提示信息说明
Invalidsystemdisk无效的系统盘
DiskI/Oerror,Replacethediskandpressanykey.磁盘I/O错误,替换磁盘后按任意键(当C盘系统文件丢失或被破坏时出现该提示信息。这时可能SYSC:为修复系统文件)
InvalidMediaTypereachingDriveC:无效的C盘媒体格式说明,也就是C盘没有格式化或者是其他操作系统的磁盘格式如NTFS
InvalidBootDisketteBootFailure无效的启动盘,启动失败


五、IO.SYS中的提示信息


提示信息说明
Insertdiskettefordriveandpressanykeywhenready插入磁盘到驱动器中后按任意键
Yourprogramcausedadivideoverflowerror
Iftheproblempersists,contactyourprogramvendor你的程序导致溢出错误。如果该问题还存在,请联系你的程序供应商
WindowshasdisableddirectdiskaccesstoprotectyourlongfilenamesWindows不能直接访问受保护的长文件名
Thesystemhasbeenhalted.PressCtrl Alt Deltorestartyourcomputer系统挂起,按Ctrl Alt Del重新启动你的计算机
YoustartedyourcomputerwithaversionofMS-DOSincompatiblewiththisversionofWindows.InsertaStartupdiskettematchingthisversionofWindowsandthenrestart你启动的MS-DOS版本与Windows版本不兼容,请插入与Windows版本匹配的系统盘后再重启
ThisversionofWindowsrequiresa386orbetterprocessor该Windows版本需要386以后的CPU支持
A20hardwareerror.Contacttechnicalsupporttoidentifytheproblem地址线A20错误。联系技术支持来识别该问题
StartingWindows98...正在启动Windows98……
Windows98isnowstartingyourMS-DOS-basedprogramWindows98正在启动基于MS-DOS的程序
Windows98isnowrestarting...Windows98正在重新启动
PressEscnowtocancelMS-DOSmodeandrestartWindows98...按Esc退出MS-DOS模式,重新启动Windows98…
ThereisanunrecognizedcommandinyourCONFIG.SYSfile在你的CONFIG.SYS文件中有不可识别的命令
ThefollowingcommandinyourCONFIG.SYSfileisincorrect:在你的CONFIG.SYS有下列错误命令
Thesectorsizespecifiedinthisfileistoolarge:该文件中指定的扇区太大
Thefollowingfileismissingorcorrupted:WIN.COMCOMMAND.COM下列文件丢失或被破坏:WIN.COM和COMMAND.COM
ThereisaninvalidcountrycodeorcodepageinyourCONFIG.SYSfile在你的CONFIG.SYS里有一个无效的国家代码
ThereisanerrorintheCOUNTRYcommandinyourCONFIG.SYSfile在你的CONFIG.SYS里有一个错误的设置国家命令
ThereisnotenoughmemoryfortheCOUNTRY.SYSfile没有足够的内存来加载COUNTRY.SYS文件
RemovesomedriversfromyourCONFIG.SYSfile,andthentryagain请从CONFIG.SYS文件中删除部分驱动程序,然后再试
Removesomedrivers,andthentryagain去除一些驱动程序,然后再试。这里的驱动程序是指在CONFIG.SYS中使用device或devicehigh命令加载的程序文件
RemovesomediskdriversfromyourCONFIG.SYSfile,andthentryagain从你的(CONFIG.SYS)移去部分程序,然后再试
TheconfigurationspecifiedinyourCONFIG.SYSfileistoolargeformemory调整你的CONFIG.SYS文件以获得足够大的内存
YouhavetoomanyblockdevicesspecifiedinyourCONFIG.SYSfile调整你的CONFIG.SYS文件以获得blockdevices
TheSTACKSsetting(s)inyourCONFIG.SYSfileareincorrect在你的CONFIG.SYS中堆栈设置不正确
Defaultstacksettingswillbeusedinstead缺省的堆栈设置将被替代使用
ThereisanerrorinyourCONFIG.SYSfileonlineXX在你的CONFIG.SYS里XX行错误
Warning:LogicaldrivespastZexistandwillbeignored警告:逻辑驱动器Z已经存在,将被忽略
TypethenameoftheCommandInterpreter(e.g.,C:"WINDOWS"COMMAND.COM)Pressanykeytocontinue…请输入命令解释器的文件名(如:C:"WINDOWS"COMMAND.COM)后,按任意键继续
Windowsisbypassingyourstartupfiles
MinimalnetworksupportwillbeloadedifavailableWindows正在跳过你的启动文件。如果网络可用,最小网络支持将被加载
WindowsisstartingthecommandpromptonlyWindows正在启动命令字符模式
WindowswillpromptyoutoconfirmeachstartupcommandWindows将提示你确认每一个启动命令
Thecompressiondrivercannotbesetupcorrectly压缩驱动程序不能被正确加载
GetaversionfromyourvendorthatiscompatiblewiththisversionofWindows从你的供应商那里获得一个与Windows版本相兼容的版本
Processthesystemregistry运行系统注册表
Createastartuplogfile(BOOTLOG.TXT)创建启动日志BOOTLOG.TXT文件
Processyourstartupdevicedrivers(CONFIG.SYS)运行CONFIG.SYS文件中的设备驱动
Processyourstartupcommandfile(AUTOEXEC.BAT)运行AUTOEXEC.BAT中的启动命令
LoadtheWindowsgraphicaluserinterface装入WINDOWS图形用户界面(GDI)
Warning:Windowshasdetectedaregistry/configurationerror.Choose,Commandpromptonly,andrunSCANREG警告:Windows检测到一个注册表或配置错误选择DOS操作模式,运行SCANREG
Warning:Windowshasdetectedacompresseddriveaccesserror.ChooseSafemodecommandpromptonly,tohelpyouidentifytheproblem警告:Windows检测到一个访问压缩驱动器错误,选择安全模式下的MS-DOS,来帮助你识别这个问题
Warning:Windowsdidnotfinishloadingonthepreviousattempt.ChooseSafemode,tostartWindowswithaminimalsetofdrivers警告:Windows在刚才尝试后不能够完成加载,选择安全模式,用最小驱动启动你的电脑
Warning:Windowsmulti-bootmaynotfunctioncorrectly
Checkforsystemfilesinyourrootdirectorywithconflictingextensions警告:Windows不能够正确完成多系统启动,在你的ROOT目录里检查不一致的系统文件
Warning:thesystemconfigurationmanagerfailedtorun.Someofyourreal-modedevicedriversmaynotinitializeproperly警告:系统管理器运行失败,一些实模式驱动可能没有正确初始化
TheBUFFERSsetting(s)inyourCONFIG.SYSfilearetoolarge.Defaultbuffersettingswillbeusedinstead在你的CONFIG.SYS里BUFFER设置太大。默认的BUFFER缓冲器设置将被使用
Amemoryallocationerroroccurredduringstartup
>RestartyourcomputerandselectInteractiveStarttoidentifytheproblem在启动过程中内存分配发生错误,请重新启动你的计算机然后选用交互式方式启动来确定故障原因
Warning:thehighmemoryarea(HMA)isnotavailableAdditionallowmemory(below640K)willbeusedinstead警告:高端内存不可用,低端内存将被代替使用
ThereisnotenoughmemoryforWindows.RemovesomedriversfromyourCONFIG.SYSfile,andthentryagain对Windows来说没有足够的内存,请从CONFIG.SYS文件中移去部分加载的程序,然后再试着启动你的读入计算机
YourpreviousMS-DOSfileswerenotfound.旧的MS-DOS文件没有找到,这常见于使用双启动的系统,另一个系统是DOS6
YourpreviousMS-DOSversionisnotsupported.MS-DOSstartupfailed旧的MS-DOS版本不支持。MS-DOS启动失败
NowloadingyourpreviousversionofMS-DOS,pleasewait现在正在加载旧的MS-DOS版本,请等待
InvalidsettingintheMSDOS.SYSfile:AninternalstackoverflowhascausedthissessiontobehaltedMSDOS.SYS文件中无效的设置选项,系统堆栈溢出,系统挂起
ChangetheSTACKSsettinginyourCONFIG.SYSfile,andthentryagain请改变CONFIG.SYS文件中的STACK设置,然后再试
Stackoverflow堆栈溢出
Integerdivideby0 整数被0除
Notenoughspaceforenvironment 没有足够的空间
Run-timeerror运行错误
Floating-pointsupportnotloaded 浮点运算支持没有装入
Nullpointerassignment 无效的断点分配


六、COMMAND中的提示信息


提示信息说明
Stackoverflow堆栈溢出
Integerdivideby0分母是0
Notenoughspaceforenvironment磁盘没有足够的空间
Run-timeerror时钟错误
Nofixeddiskspresent没有硬盘存在
Insufficientmemory内存不足
Invaliddrivespecification无效的驱动器名
Invalidcharactersinvolumelabel无效的盘符
InvalidVolumeID无效的卷标ID号
Notargetdrivespecified目标驱动器没有定义
missingoperatingsystem系统文件丢失
NonsystemDiskorDiskError不是系统盘或磁盘错误
ReplaceDiskandPressAnykeyToReboot替换磁盘,按任意键重新启动


七、SYS传送系统文件时的提示信息


提示信息说明
CannotfindSystemFiles不能找到系统文件
Systemtransferred系统传送完毕
Diskunsuitableforsystemdisk目标磁盘不适合做系统盘


八、FORMAT中的提示信息


提示信息说明
UnabletowriteBOOT.不能写引导扇区
Invalidmediatype无效的磁盘类型
Parametersnotsupported参数不支持
Cannotformatanetworkdrive不能格式化网络驱动器
InvalidmediaorTrack0bad-diskunusable无效的磁盘或0磁道损坏,磁盘不可用
Formatterminated格式化终止
DiskBootfailure磁盘BOOT区错误
Invalidsystemdisk无效的系统磁盘
DiskI/Oerror,Replacethedisk,andthen
pressanykey磁盘I/O错误,请换盘后按任意键。通常在读写软盘时出现此类错误
Non-Systemdiskordiskerror,Replaceandstrikeanykeywhenready无系统的磁盘或磁盘错误,请替换后按任意键
Toomanyopenfiles打开的文件太多
AccessdeniedInsufficientmemory没有足够的内存访问被拒绝
Invaliddrivespecification无效的盘符说明
ParseErrorXX分析错误XX
IncorrectMS-DOSversion不正确的MS-DOS版本
X%percentcompleted格式化正在进行,已经完成X%
Formatcomplete.格式化完成
XXbytesinbadsectorsXX字节的坏扇区
XXbytestotaldiskspace磁盘的全睰占湮猉X字节
XXbytesavailableondisk磁盘的可使用空间为XX字
Volumelabel(11characters,ENTERfornone)?请输入磁盘卷标(总共11个字符,按回车为空)
FormatnotsupportedondriveXFormat命令不支持驱动器X
Invaliddeviceparametersfromdevicedriver无效的设备驱动参数
ErrorwritingFAT写FAT表错误
Errorwritingdirectory写DIR目录表错误
CannotformatanASSIGNorSUBSTdrive不能格式化ASSIGN和SUBST指定的驱动器
CannotfindSystemFiles没有找到系统文件
Cannotformatanetworkdrive不能格式化网络驱动器
Invalidcharactersinvolumelabel磁盘卷标中存在非法字符
Parametersnotsupported参数不支持
Formatterminated格式化意外终止
Diskunsuitableforsystemdisk这个磁盘不适合作为系统盘
UnabletowriteBOOT不能写BOOT区
Errorreadingdirectory读DIR目录区错误
Notargetdrivespecified目的驱动器没有说明
andpressENTERwhenready准备好后按回车键
Systemtransferred系统传送完毕
EntercurrentvolumelabelfordriveX请为驱动器X输入磁盘的卷标
Parametersnotcompatiblewithfixeddisk参数与硬盘不兼容
VolumeSerialNumberisXXXX分区格式化时随机产生的序列号是:XXXX
Formatbroken格式化被强行终止
FormatnotavailableondriveXX不能在驱动器XX上使用格式化命令
on-Systemdiskordiskerror没有系统盘或磁盘错误
BadPartitionTable错误的硬盘分区表
Parametersnotsupportedbydrive该驱动器不支持这些参数
InsertDOSdiskindriveA请在驱动器A中插入DOS系统盘
WARNING,ALLDATAONNON-REMOVABLEDISKDRIVE%1:WILLBELOST!Proceedwith警告:硬盘分区X中的所有数据都将丢失,确定执行格式化命令吗?
Formatanother(Y/N)?!还格式化其他盘吗
Errorreadingpartitiontable读硬盘分区表错误
Errorwritingpartitiontable写硬盘分区表错误
Parametersnotcompatible参数不匹配
XXallocationunitsavailableondisk磁盘上有XX可用簇
XXbytesineachallocationunit每个簇有大小为XX字节
Sameparameterenteredtwice相同的参数被输入两次
Mustenterboth/Tand/Nparameters/T和/N参数必须同时使用
Thereisnotenoughroomtocreatearestorefile没有足够的空间来建立恢复文件
YouwillnotbeabletousetheUNFORMATutility你将不能使用UNFORMAT工具来恢复你的硬盘数据
Thereisnotenoughdiskspaceforsystemfiles没有足够的空间来存储系统文件
Thisdiskcannotbeunformatted这个磁盘不能被反格式化
Therewasanerrorcreatingtheformatrecoveryfile在建立恢复文件时产生了一个错误
Volumelabelisnotsupportedwith/8parameter该驱动器不支持/8参数
Insufficientmemorytoloadsystemfiles没有足够的内存装入系统文件
Insufficientmemory.内存不足
Calculatingfreespace(thismaytakeseveralminutes)...正在统计磁盘空间(可能要花上几分钟时间)


九、FDISK中的提示信息


提示信息说明
DeletePrimaryDOSPartition删除主DOS分区
DeleteExtendedDOSPartition删除扩展DOS分区
DeleteLogicalDOSDrive(s)intheExtendedDOSPartition删除逻辑DOS分区
WARNING!DatainthedeletedPrimaryDOSPartitionwillbelost警告:主DOS分区的数据将全部丢失
WARNING!DatainthedeletedExtendedDOSPartitionwillbelost.Doyouwishtocontinue(Y/N)?警告:扩展分区中的数据将全部丢失,你真的继续吗
DeleteLogicalDOSDrive(s)intheExtendedDOSPartition删除扩展分区下的逻辑分区
WARNING!DatainadeletedLogicalDOSDrivewillbelostWhatdrivedoyouwanttodelete?Areyousure(Y/N)?警告:逻辑分区中的所有数据都将丢失,你删除哪个驱动器?你真的确定吗
DisplayPartitionInformation显示分区内容信息
TheExtendedDOSPartitioncontainsLogicalDOSDrives.扩展DOS分区中包含DOS逻辑分区
Doyouwanttodisplaythelogicaldriveinformation(Y/N)?你想显示逻辑分区的信息吗
DisplayLogicalDOSDriveInformation显示逻辑分区信息
EnterpartitionsizeinMbytesorpercentofdiskspace(%)tocreateaPrimaryDOSPartition...输入所建立主DOS分区的容量,单位是百分比
EnterlogicaldrivesizeinMbytesorpercentofdiskspace(%)...请输入逻辑分区容量大小或磁盘空间的百分比
EnterVolumeLabel?请输入卷标
EnterpartitionsizeinMbytesorpercentofdiskspace(%)tocreateanExtendedDOSPartition....请输入扩展DOS分区的容量大小或磁盘空间的百分比
DoyouwishtousethemaximumavailablesizeforaPrimaryDOSPartition你想使用全部磁盘空间作为主DOS分区吗
ChangeCurrentFixedDiskDrive.EnterFixedDiskDriveNumber(X)当你挂接两个以上硬盘时,在FDISK的系统菜单中将多一个第5项,选择第5项时,将提示:请输入硬盘的序号
FixedDiskDriveStatus硬盘状态
DatainthedeletedPrimaryDOSPartitionwillbelost.Whatprimarypartitiondoyouwanttodelete?Doyouwishtocontinue(Y/N)?DOS主分区中的数据将全部丢失。你准备删除哪个主分区?你想继续吗
DatainthedeletedNon-DOSPartitionwillbelost.WhatNon-DOSpartitiondoyouwanttodelete?Doyouwishtocontinue(Y/N)?非DOS分区的数据将全部丢失。你准备删除哪一个非DOS分区?你要继续吗
YouMUSTrestartyoursystemforyourchangestotakeeffect你必须重新启动你的计算机你所做的所有改变才有效
Anydrivesyouhavecreatedorchangedmustbeformatted你新建的分区或改变的分区必须被格式化后才可用
ShutdownWindowsbeforerestarting在重启之前请关闭Windows
Yourcomputerhasadisklargerthan512MB.ThisversionofWindowsincludesimprovedsupportforlargedisks,resultinginmoreefficientuseofdiskspaceonlargedrives,andallowingdisksover2GBtobe formattedasasingledrive你的计算机挂接了一个大于512M的硬盘。该版本的Windows支持大硬盘,能够更有效的使用大硬盘上的磁盘空间,并充许单个分区大于2GB
PressEsctoexitFDISK按Esc退出FDISK
ThisdriveisFAT16bydefault,switchtoFAT32(Y/N)?这个驱动器默认是FAT16模式,真的转换为FAT32模式吗
ThisdrivemustbeFAT32becauseitssizeis>2048MB当分区的空间大于2048MB时,这个分区必须使用FAT32磁盘数据格式
ThisdrivemustbeFAT16becauseitssizeistoosmalltobeFAT32因为这个分区对于FAT32格式来说太小,所以这个分区只能使用FAT16格式
Verifyingdriveintegrity,complete正在校验分区的完整性,完成
YourcomputerhasNTFSpartitionswhichmayrequirelargedrivesupport.Ifyouareusinganotheroperatingsystem,suchasWindowsNT,whichsupportslargedrivesyoushouldenabletreatingthesepartitionsaslarge你的计算机有一个NTFS分区可能需要大分区支持,如果你正在使用其他操作系统,如WindowsNT,你应该把这些分区分大一些
NOTE:IfyouanswerYandthepartitiondisplaylooksincorrectorahangorcrashoccursdonothing,runFDISKagain,andanswerNtothisquestion注意:如果你回答Y,可能显示的分区信息看上去不正确或系统挂起或什么动作也没有,这时你可再次运行FDISK,对这个问题用N回答
ShouldNTFSpartitionsonalldrivesbetreatedaslarge(Y/N)?对所有的NTFS分区使用大分区模式
Drivedeleted驱动器被删除
Partitionmadeactive分区X被设置为活动
PrimaryDOSPartitioncreated主DOS分区被建立
ExtendedDOSPartitioncreated扩展DOS分区被建立
LogicalDOSDrivecreated,driveletterschangedoradded逻辑分区被建立,驱动器的盘符可能被改变或有新的增加
Nopartitionsdefined没有分区被定义
Nologicaldrivesdefined没有逻辑驱动器被说明
Drivelettershavebeenchangedordeleted驱动器盘符已经被改变或删除
Noactivepartitions没有设置活动分区,该硬盘不能被启动
Nofixeddiskspresent没有硬盘
Errorreadingfixeddisk读硬盘错误
Errorwritingfixeddisk写硬盘错误
Writeprotecterrorwritingfixeddisk写硬盘写保护错误
IncorrectDOSversion不正确的DOS版本
CannotFDISKwithnetworkloaded不能对网络驱动器使用FDISK
NospacetocreateaDOSpartition没有足够的空间建立DOS分区
Requestedlogicaldrivesizeexceedsthemaximumavailablespace输入的逻辑驱动器的容量超过了最大可用的磁盘空间容量
Requestedpartitionsizeexceedsthemaximumavailablespace输入的分区的容量超过了最大可用的磁盘空间容量
Nopartitionstodelete已经没有分区可以被删除
TheonlystartablepartitiononDriveisalreadysetactive硬盘X上的惟一的可启动分区已经被标记为活动(扩展分区不能被设置为活动)
Nopartitionstomakeactive没有分区可以被标记为活动分区
Partitionselectedisnotstartable,activepartitionnotchanged输入的分区X不可启动,当前的活动分区没有被改变
CannotcreateExtendedDOSPartitionwithoutPrimaryDOSPartitionondisk X在硬盘X上没有主DOS分区时不能建立扩展DOS分区
AllavailablespaceintheExtendedDOSPartitionisassignedtologicaldrives扩展分区的全部有效空间都分配给逻辑驱动器吗?还可以再分为主分区或其他分区
CannotdeleteExtendedDOSPartitionwhilelogicaldrivesexist因为扩展分区下有逻辑分区存在,所以扩展分区不能被删除
AlllogicaldrivesdeletedintheExtendedDOSPartitionisnotachoice.PleaseenterX确定删除哪一个逻辑分区,请输入X
WARNING!Thepartitionsetactiveisnotstartable警告:被设置为活动的分区是不可启动的
Onlynon-startablepartitionsexist只有不可启动的分区存在
OnlypartitionsonDrive1canbemadeactive只有硬盘1上的分区可被设置为活动。当你挂接两个以上硬盘时,使用FDISK命令时只能设置硬盘1中的某个主分区为活动,其他硬盘上的主分区都不能设置为活动
MaximumnumberofLogicalDOSDrivesinstalled逻辑驱动器的所有盘符都使用,意思是你在分区时驱动器的盘符超过了Z
Cannotcreateazerosizepartition不能建立大小空间为0的分区
UnabletoaccessDriveX不能访问驱动器X
CannotdeletePrimaryDOSPartitionondrive1whenanExtendedDOSPartitionexists当扩展分区存在时不能删除硬盘1上的主DOS分区
Invalidentry无效的输入
Volumelabeldoesnotmatch标不匹配,当你删除分区时,系统会提示输入该分区的卷标,如果输入的巻标和你要删除的分区的卷标不一致时,操作将不能进行
CannotcreateLogicalDOSDrivewithoutanExtendedDOSPartitiononthecurrentdrive没有扩展分区时不能在当前硬盘上建立逻辑驱动器
Couldnotchangepartitions,becausethediskcouldnotbelocked不能改变分区,因为该硬盘不能被锁定
Internalerror内部错误
IncorrectMS-DOSversion不正确的MS-DOS版本
Invalidparameter无效的参数
CannotFDISKwithnetworkloaded不能对网络驱动器进行FDISK
ThemasterbootcodehasNOTbeenupdated
/STATUSDisplayspartitioninformation主引导区代码没有被更新。显示分区信息
/XIgnoresextendeddisk-accesssupport.Usethisswitch不使用扩展磁盘访问指令支持。当你使用
ifyoureceivediskaccessorstackoverflowmessages这个参数时你将看到堆栈溢出的信息
/MBRand/CMBRcannotbothbespecified/MBR和/CMBR这两个参数不能同时使用
/MBRonlyoperatesondrive1,use/CMBRforotherdrives只能对硬盘1使用,/CMBR对其他硬盘使用/MBR
Youmustspecifyadrivenumberwith/CMBR
Invalidpartitiontable在使用/CMBR参数时必须说明硬盘的序号。无效的硬盘分区表
Errorloadingoperatingsystem在装入操作系统时错误
Missingoperatingsystem操作系统文件丢失
stackoverflow堆栈溢出
integerdivideby0整数被0除
run-timeerror运行错误
notenoughspaceforenvironment没有足够的空间

 

标签:电脑 | 浏览数(663) | 评论数(0) | 2006-10-26

使用GDI+在内存中转换图片类型(修正)

作者:卢伟

  微软新推出的GDI+功能强大,本文仅对图片转换加以讨论,不足之处请大家指出,本人QQ:394777271。

需要的GdiPlus.h等头文件和GdiPlus.lib等文件可以在.NET的安装文件夹C:"Program Files"Microsoft Visual Studio 8"VC"PlatformSDK"Include和C:"Program Files"Microsoft Visual Studio 8"VC"PlatformSDK"Lib中找到。(附件中已上传!!!)

如果使用的是VC6.0,把头文件放置到C:"Program Files"Microsoft Visual Studio 6.0"VC98"Include。

lib文件放置到C:"Program Files"Microsoft Visual Studio 6.0"VC98"Lib或者放置到项目所在的位置。

图片类型的转换支持:bmp、dib、png、gif、jpeg/jpg、tiff、emf等。以下是详细步骤。

  首先,在StdAfx.h中静态调用diplus.lib,即由编译系统完成对DLL的加载,应用程序结束时卸载DLL的编码。如下:
#ifndef ULONG_PTR
#define ULONG_PTR unsigned long*
#include "GdiPlus.h"
using namespace Gdiplus;
#pragma comment(lib, "gdiplus.lib")
#include <atlconv.h>
#endif

在类的头文件中定义,以下成员变量,用来初始化GDI+的使用和结束使用。
GdiplusStartupInput m_gdiplusStartupInput; 
ULONG_PTR m_gdiplusToken;
然后在OnCreate()函数中加入初始化GDI+的函数:
GdiplusStartup(&m_gdiplusToken, &m_gdiplusStartupInput, NULL);
在OnDestroy()函数中加入结束GDI+使用的函数:

GdiplusShutdown(m_gdiplusToken);

接下来定义GetImageCLSID函数:
BOOL GetImageCLSID(const WCHAR* format, CLSID* pCLSID);
接着,定义转换函数:
BOOL MBmpToMImage(CMemFile& cbfBmp, CMemFile& cbfImage, CString strType);
其中:
CMemFile& cbfBmp表示原位图文件;
CMemFile& cbfImage表示转换后的图形文件;
CString strType表示转换的图片类型。
该函数中主要的处理为以下几步:
  1. 将原位图文件转换为IStream
  2. 定义Image类实例,并使用第1步获得的IStream初始化
  3. 获取转换的图片类型的CLSID
  4. 将Image以转换的图片类型保存到IStream中
  5. 将IStream转换为CMemFile内存文件(也可为CFile)

详细代码如下:

 

BOOL GetImageCLSID(const WCHAR* format, CLSID* pCLSID)

{

         UINT num = 0;

         UINT size = 0;

        

         ImageCodecInfo* pImageCodecInfo = NULL;

         GetImageEncodersSize(&num, &size);

         if(size == 0){

                   return FALSE;

         }

         pImageCodecInfo = (ImageCodecInfo *)(malloc(size));

         if(pImageCodecInfo == NULL)

                   return FALSE;

         GetImageEncoders(num, size, pImageCodecInfo);

        

         // Find for the support of format for image in the windows

         for(UINT i = 0; i < num; ++i)

         {

                  //MimeType: Depiction for the program image 

                  if( wcscmp(pImageCodecInfo[i].MimeType, format) == 0)

                   { 

                            *pCLSID = pImageCodecInfo[i].Clsid;

                            free(pImageCodecInfo);

                            return TRUE

                   } 

         } 

         free(pImageCodecInfo); 

         return FALSE;

}

 

BOOL MBmpToMImage(CMemFile& cbfBmp, CMemFile& cbfImage, CString strType)

{

         int iBmpSize = cbfBmp.GetLength();

         HGLOBAL hMemBmp = GlobalAlloc(GMEM_FIXED, iBmpSize);

         if (hMemBmp == NULL) return FALSE;

         IStream* pStmBmp = NULL;

         CreateStreamOnHGlobal(hMemBmp, FALSE, &pStmBmp);

         if (pStmBmp == NULL)

         {

                  GlobalFree(hMemBmp);

                   return FALSE;

         }

         BYTE* pbyBmp = (BYTE *)GlobalLock(hMemBmp);

         cbfBmp.SeekToBegin();

         cbfBmp.Read(pbyBmp, iBmpSize);

        

         Image* imImage = NULL;

         imImage = Image::FromStream(pStmBmp, FALSE);

         if (imImage == NULL)

         {

                  GlobalUnlock(hMemBmp);

                  GlobalFree(hMemBmp);

                   return FALSE;

         }

         USES_CONVERSION;

         CLSID clImageClsid;

         GetImageCLSID(A2W(("image/"+strType).GetBuffer(0)), &clImageClsid);        

        

         HGLOBAL hMemImage = GlobalAlloc(GMEM_MOVEABLE, 0);

         if (hMemImage == NULL)

         {

                  pStmBmp->Release();

                  GlobalUnlock(hMemBmp);

                  GlobalFree(hMemBmp);

                   if (imImage != NULL) delete imImage;

                   return FALSE;

         }

         IStream* pStmImage = NULL;

         CreateStreamOnHGlobal(hMemImage, TRUE, &pStmImage);

         if (pStmImage == NULL)

         {

                  pStmBmp->Release();

                  GlobalUnlock(hMemBmp);

                  GlobalFree(hMemBmp);

                  GlobalFree(hMemImage);

                   if (imImage != NULL) delete imImage;

                            return FALSE;

         }      

         imImage->Save(pStmImage, &clImageClsid);

         if (pStmImage == NULL)

         {

                  pStmBmp->Release();

                  pStmImage->Release();

                  GlobalUnlock(hMemBmp);

                  GlobalFree(hMemBmp);

                  GlobalFree(hMemImage);

                   if (imImage != NULL) delete imImage;

                   return FALSE;

         }

         LARGE_INTEGER liBegin = {0};

         pStmImage->Seek(liBegin, STREAM_SEEK_SET, NULL);

         BYTE* pbyImage = (BYTE *)GlobalLock(hMemImage);

         cbfImage.SeekToBegin();

         cbfImage.Write(pbyImage, GlobalSize(hMemImage));

        

         if (imImage != NULL) delete imImage;

         pStmBmp->Release();

         pStmImage->Release();

         GlobalUnlock(hMemBmp);

         GlobalUnlock(hMemImage);

         GlobalFree(hMemBmp);

         GlobalFree(hMemImage);

         return TRUE;

}

 

标签:软件开发 | 浏览数(642) | 评论数(0) | 2006-10-24

CONVERT BMP TO JPEG 

The code has been improved to handle all bitmap formats. The earlier version did not handle 32-bit or 16-bit DIB's.

Acknowledgements: This code depends on the jpeg.lib code written by the Independent JPEG Group (Thomas G. Lane and company).

The code assumes that you have installed their jpeg.lib library project.

To get jpeg.lib:

1) Go to Ulrich von Zadow's  "Class library for image file decoding" article, and click on the "Links" link.

2) Click on the "LIBJPEG ver 6b" link to download the "vanilla" libjpeg.

3) Do not use Mr. von Zadow's "Paintlib" version of libjpeg, because he seems to have made some internal changes for his own use.

The jpeg.lib project (LibJpeg) is set up to compile under numerous operating systems. The download includes a file called install.doc. Read the sections relevant to VC++/Windows. You have to rename a couple of files, etc.

The first time I installed and compiled libjpeg, it took a long time and was very aggravating. I then re-read the instructions, and it took about fifteen minutes. I'm sure there's a lesson here...

Testing Notes:

To test the code, I used MS Paint to create bitmap files in the four supported formats.

I then set up a VC++ project which read the bitmap files, and used the functions below to turn them into JPEG files (.jpg).

I then used Julian Smart's CImage demo application to display them.

I also used Internet Explorer as well as MS Word to display them.

I also created a jpeg file by loading an internal resource (see the example below). The largest bitmap file I dealt with was about 390 KB.

Error trapping:

libjpeg includes a number of provisions for improving error detection, and reporting them in a good manner. I have not investigated these, and the code does not include any of them. Right now, an internal error will simply cause the code to stop cold.

To get at jpeg.lib properly in your project:

1.       Under tools->options, directories tab:

a.      Set the Include Files to include the path to your jpeg.lib project

b.      Set the Library Files to include the path to your jpeg.lib release directory

2.       Under Project->Settings, Link tab:

a.      Add jpeg.lib to the Object/library modules list

Note on Quality Settings:

For the IDB_TEST resource, I tried two quality settings: 10 and 100. At "10", the image had a lot of black spottiness, and took up 1 KB. At "100" the image looked very good, and took up 4 KB.

 
/////////////////////////////////////////////////////////////
//Example of use:
/////////////////////////////////////////////////////////////
void MakeJpeg()
{
 CBitmap cBitmap;
 BITMAP bm;
 CString csMsg = "";
 
 cBitmap.LoadBitmap(IDB_TEST);
 cBitmap.GetBitmap(&bm);
 
 HANDLE hDib = DDBToDIB((HBITMAP)cBitmap,
 BI_RGB,
  NULL); //Use default palette
 
 //Turn DIB into JPEG file
 if (hDib != NULL)
 {
 if (!JpegFromDib(hDib,
 100, //Quality setting
 "test.jpg",
 &csMsg))
  {
 AfxMessageBox(csMsg);
 }
 
 else
 AfxMessageBox("test.jpg created");
 
 ::GlobalFree(hDib);
 }
 
 else
 AfxMessageBox("Failed to load IDB_TEST");
}
 
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
//Header file code
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
 
 
//In the relevant header file (up near the top):
 
#include "jpeglib.h"
 
 
BOOL JpegFromDib(HANDLE hDib, //Handle to DIB
 int nQuality, //JPEG quality (0-100)
 CString csJpeg, //Pathname to target jpeg file
 CString* pcsMsg); //Error msg to return
 
BOOL BuildSamps(HANDLE hDib,
 int nSampsPerRow,
 struct jpeg_compress_struct cinfo,
 JSAMPARRAY jsmpArray,
  CString* pcsMsg);
 
RGBQUAD QuadFromWord(WORD b16);
 
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
//Source file code
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
 
extern "C"
{
#include "jpeglib.h"
}
 
#include 
 
struct ima_error_mgr
{
 struct jpeg_error_mgr pub; //"public" fields
 jmp_buf setjmp_buffer;  //for return to caller
};
 
////////////////////////////////////////////////////////////////////////////
//This function takes the contents of a DIB
//and turns it into a JPEG file.
//
//The DIB may be monochrome, 16-color, 256-color, or 24-bit color.
//
//Any functions or data items beginning with "jpeg_" belong to jpeg.lib,
//and are not included here.
//
//The function assumes 3 color components per pixel.
/////////////////////////////////////////////////////////////////////////////
BOOL JpegFromDib(HANDLE hDib, //Handle to DIB
 int nQuality, //JPEG quality (0-100)
 CString csJpeg, //Pathname to jpeg file
 CString* pcsMsg) //Error msg to return
{
 //Basic sanity checks...
 if (nQuality < 0 || nQuality > 100 ||
 hDib == NULL ||
 pcsMsg == NULL ||
 csJpeg == "")
 {
 if (pcsMsg != NULL)
 *pcsMsg = "Invalid input data";
 
 return FALSE;
 }
 
 *pcsMsg = "";
 
 LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER)hDib;
 
 byte *buf2 = 0;
 
 //Use libjpeg functions to write scanlines to disk in JPEG format
 struct jpeg_compress_struct cinfo;
 struct jpeg_error_mgr jerr;
 
 FILE* pOutFile; //Target file
 int nSampsPerRow; //Physical row width in image buffer
 JSAMPARRAY jsmpArray; //Pixel RGB buffer for JPEG file
 
 cinfo.err = jpeg_std_error(&jerr); //Use default error handling (ugly!)
 
 jpeg_create_compress(&cinfo);
 
 if ((pOutFile = fopen(csJpeg, "wb")) == NULL)
 {
 *pcsMsg = "Cannot open ";
  *pcsMsg += csJpeg;
 jpeg_destroy_compress(&cinfo);
 return FALSE;
 }
 
 jpeg_stdio_dest(&cinfo, pOutFile);
 
 cinfo.image_width = lpbi->biWidth; //Image width and height, in pixels
 cinfo.image_height = lpbi->biHeight;
 cinfo.input_components = 3; //Color components per pixel
 //(RGB_PIXELSIZE - see jmorecfg.h)
 cinfo.in_color_space = JCS_RGB;   //Colorspace of input image
 
 jpeg_set_defaults(&cinfo);
 
 jpeg_set_quality(&cinfo,
 nQuality, //Quality: 0-100 scale
 TRUE); //Limit to baseline-JPEG values
 
 jpeg_start_compress(&cinfo, TRUE);
 
 //JSAMPLEs per row in output buffer
 nSampsPerRow = cinfo.image_width * cinfo.input_components;
 
 //Allocate array of pixel RGB values
 jsmpArray = (*cinfo.mem->alloc_sarray)
 ((j_common_ptr) &cinfo,
  JPOOL_IMAGE,
 nSampsPerRow,
 cinfo.image_height);
 
 if (DibToSamps(hDib,
 nSampsPerRow,
 cinfo,
 jsmpArray,
 pcsMsg))
 {
 //Write the array of scan lines to the JPEG file
 (void)jpeg_write_scanlines(&cinfo,
 jsmpArray,
 cinfo.image_height);
 }
 
 jpeg_finish_compress(&cinfo); //Always finish
 
 fclose(pOutFile);
 
 jpeg_destroy_compress(&cinfo); //Free resources
 
 if (*pcsMsg != "")
 return FALSE;
 
 else
 return TRUE;
}
 

////////////////////////////////////////////////////////////////
//This function fills a jsmpArray with the RGB values
//for the CBitmap.
//
//It has been improved to handle all legal bitmap formats.
//
//A jsmpArray is a big array of RGB values, 3 bytes per value.
//
//Note that rows of pixels are processed bottom to top:
//The data in the jsamp array must be arranged top to bottom.
////////////////////////////////////////////////////////////////
BOOLCOperateBitmap::DibToSamps(HANDLE      hDib,
                int                         nSampsPerRow,
                structjpeg_compress_structcinfo,
                JSAMPARRAY                  jsmpPixels,
                CString*                    pcsMsg)
{
   //Sanity...
   if (hDib == NULL    ||
     nSampsPerRow <= 0 || pcsMsg == NULL)
   {
     if (pcsMsg !=NULL)
        *pcsMsg="Invalid input data";
     returnFALSE;
   }
 
   intr=0, p=0, q=0, b=0, n=0,
       nUnused=0, nBytesWide=0, nUsed=0, nLastBits=0, nLastNibs=0, nCTEntries=0,
       nRow=0, nByte=0, nPixel=0;
   BYTEbytCTEnt=0;
   LPBITMAPINFOHEADERpbBmHdr= (LPBITMAPINFOHEADER)hDib; //The bit count tells you the format of the bitmap: //Decide how many entries will be in the color table (if any)
 
   switch (pbBmHdr->biBitCount)
   {
      case 1:
         nCTEntries = 2;   //Monochrome
         break;
 
      case 4:
         nCTEntries = 16; //16-color
         break;
 
      case 8:
         nCTEntries = 256; //256-color
         break;
 
      case 16:
      case 24:
      case 32:
         nCTEntries = 0;   //No color table needed
         break;
 
      default:
         *pcsMsg = "Invalid bitmap bit count";
         returnFALSE; //Unsupported format
   }
 
   //Point to the color table and pixels
   DWORD     dwCTab = (DWORD)pbBmHdr + pbBmHdr->biSize;
   LPRGBQUADpCTab = (LPRGBQUAD)(dwCTab);
   LPSTR     lpBits = (LPSTR)pbBmHdr +
                      (WORD)pbBmHdr->biSize +
                      (WORD)(nCTEntries * sizeof(RGBQUAD));
 
   //Different formats for the image bits
   LPBYTE   lpPixels = (LPBYTElpBits;
   RGBQUAD* pRgbQs   = (RGBQUAD*)lpBits;
   WORD*    wPixels = (WORD*)   lpBits;
 
   //Set up the jsamps according to the bitmap's format.
   //Note that rows are processed bottom to top, because
   //that's how bitmaps are created.
   switch (pbBmHdr->biBitCount)
   {
      case 1:
         nUsed      = (pbBmHdr->biWidth + 7) / 8;
         nUnused    = (((nUsed + 3) / 4) * 4) - nUsed;
         nBytesWide = nUsed + nUnused;
         nLastBits = 8 - ((nUsed * 8) - pbBmHdr->biWidth);
 
         for (r=0; r < pbBmHdr->biHeight; r++)
         {
            for (p=0,q=0; p < nUsed; p++)
            {
               nRow=(pbBmHdr->biHeight-r-1) * nBytesWide;
               nByte nRow + p;
                                     //下面代码怀疑有误2001.5.14                       
               intnBUsed = (p <(nUsed*1)) ? 8 : nLastBits;
                              
                               for(b=0;b<nBUsed;b++)
               {
                  bytCTEnt = lpPixels[nByte] << b;
                  bytCTEnt = bytCTEnt >> 7;
 
                  jsmpPixels[r][q+0] = pCTab[bytCTEnt].rgbRed;
                  jsmpPixels[r][q+1] = pCTab[bytCTEnt].rgbGreen;
                  jsmpPixels[r][q+2] = pCTab[bytCTEnt].rgbBlue;
                  q += 3;
                                      
               }
                              
            }
         }
         break;
 
      case 4:
         nUsed      = (pbBmHdr->biWidth + 1) / 2;
         nUnused    = (((nUsed + 3) / 4) * 4) - nUsed;
         nBytesWide = nUsed + nUnused;
         nLastNibs = 2 - ((nUsed * 2) - pbBmHdr->biWidth);
 
         for (r=0; r < pbBmHdr->biHeight;r++)
         {
            for (p=0,q=0; p < nUsed;p++)
            {
               nRow=(pbBmHdr->biHeight-r-1) * nBytesWide;
               nByte = nRow + p;
 
               intnNibbles = (p > (4-(n*4)));
 
                  jsmpPixels[r][q+0] = pCTab[bytCTEnt].rgbRed;
                  jsmpPixels[r][q+1] = pCTab[bytCTEnt].rgbGreen;
                  jsmpPixels[r][q+2] = pCTab[bytCTEnt].rgbBlue;
 
                  q += 3;
               }
          }        
         break;     
      case 8: //Each byte is a pointer to a pixel color
         nUnused = (((pbBmHdr->biWidth + 3) / 4) * 4) -
                   pbBmHdr->biWidth;
 
         for (r=0;r < pbBmHdr->biHeight; r++)
         {
            for (p=0,q=0; p < pbBmHdr->biWidth; p++,q+=3)
            {
               nRow   = (pbBmHdr->biHeight-r-1) * (pbBmHdr->biWidth + nUnused);
               nPixel nRow + p;
 
               jsmpPixels[r][q+0] = pCTab[lpPixels[nPixel]].rgbRed;
               jsmpPixels[r][q+1] = pCTab[lpPixels[nPixel]].rgbGreen;
               jsmpPixels[r][q+2] = pCTab[lpPixels[nPixel]].rgbBlue;
            }
         }
         break;
 
      case 16: //Hi-color (16 bits per pixel)
         for (r=0;r < pbBmHdr->biHeight; r++)
         {
            for (p=0,q=0; p < pbBmHdr->biWidth; p++,q+=3)
            {
               nRow    = (pbBmHdr->biHeight-r-1) * pbBmHdr->biWidth;
              nPixel = nRow + p;
 
               RGBQUADquad = QuadFromWord(wPixels[nPixel]);
 
               jsmpPixels[r][q+0] = quad.rgbRed;
               jsmpPixels[r][q+1] = quad.rgbGreen;
               jsmpPixels[r][q+2] = quad.rgbBlue;
            }
         }
         break;
 
      case 24:
         nBytesWide = (pbBmHdr->biWidth*3);
         nUnused    = (((nBytesWide + 3) / 4) * 4) -
                       nBytesWide;
         nBytesWide += nUnused;
 
         for (r=0;r < pbBmHdr->biHeight;r++)
         {
            for (p=0,q=0;p < (nBytesWide-nUnused); p+=3,q+=3)
            {
               nRow = (pbBmHdr->biHeight-r-1) * nBytesWide;
               nPixel = nRow + p;
 
               jsmpPixels[r][q+0] = lpPixels[nPixel+2]; //Red
               jsmpPixels[r][q+1] = lpPixels[nPixel+1]; //Green
               jsmpPixels[r][q+2] = lpPixels[nPixel+0]; //Blue
            }
         }
         break;
 
      case 32:
         for (r=0; r < pbBmHdr->biHeight; r++)
         {
            for (p=0,q=0; p < pbBmHdr->biWidth; p++,q+=3)
            {
               nRow    = (pbBmHdr->biHeight-r-1) *
                          pbBmHdr->biWidth;
               nPixel = nRow + p;
 
               jsmpPixels[r][q+0] = pRgbQs[nPixel].rgbRed;
               jsmpPixels[r][q+1] = pRgbQs[nPixel].rgbGreen;
               jsmpPixels[r][q+2] = pRgbQs[nPixel].rgbBlue;
            }
         }
         break;
   }   //end switch
 
returnTRUE;
}
 
////////////////////////////////////////
//This function turns a 16-bit pixel
//into an RGBQUAD value.
////////////////////////////////////////
RGBQUAD QuadFromWord(WORD b16)
{
 BYTE bytVals[] =
 {
 0, 16, 24, 32, 40, 48, 56, 64,
 72, 80, 88, 96, 104,112,120,128,
 136,144,152,160,168,176,184,192,
 200,208,216,224,232,240,248,255
 };
 
 WORD wR = b16;
 WORD wG = b16;
 WORD wB = b16;
 
 wR <<= 1; wR >>= 11;
 wG <<= 6; wG >>= 11;
 wB <<= 11; wB >>= 11;
 
 RGBQUAD rgb;
 
 rgb.rgbReserved = 0;
 rgb.rgbBlue = bytVals[wB];
 rgb.rgbGreen = bytVals[wG];
 rgb.rgbRed = bytVals[wR];
 
 return rgb;
}

 

 

标签:软件开发 | 浏览数(791) | 评论数(0) | 2006-10-24

VC中使用Gdi+合并jpg图片

合并两张jpg图片为一张jpg图片,思路是先把两张图片jpg图片都转化成bmp图片,然后把两张bmp图片合并成一张bmp图片,然后是把这张bmp图片转化为jpg图片。

一。jpg,bmp互相转化

/*********************************

format:bmp转为jpg formatimage/jpeg,jpg转为bmp,formatimage/bmp

strDst为最终转化结果的图片路径

strSrc为原来图片的路径

**********************************/

BOOL ConvertPic(const WCHAR *format, const CString &strDst, const CString &strSrc)

{

         BOOL bConvert = false;

         CLSID clsid;

         int nRet = 0;

         nRet = GetEncoderClsid(format,&clsid);  //得到CLSID

         USES_CONVERSION;

         if (nRet>=0)

         {

                   Image image(A2W(strSrc));

                  image.Save(A2W(strDst),&clsid,NULL);

                  bConvert = true;

         }

         return bConvert;

}

//其中GetEncoderClsid函数如下:

/*****************************************************

返回值为-1表示失败,其他为成功

******************************************************/

int GetEncoderClsid(const WCHAR *format, CLSID *pClsid)

{

         int nRet = -1;

         ImageCodecInfo * pCodecInfo = NULL;

         UINT nNum = 0,nSize = 0;

         GetImageEncodersSize(&nNum,&nSize);

         if (nSize<0)

         {

                   return nRet;

         }

         pCodecInfo = new ImageCodecInfo[nSize];

         if (pCodecInfo==NULL)

         {

                   return nRet;

         }

         GetImageEncoders(nNum,nSize,pCodecInfo);

         for (UINT i=0; i<nNum; i++)

         {

                   if (wcscmp(pCodecInfo[i].MimeType,format)==0)

                   {

                            *pClsid = pCodecInfo[i].Clsid;

                            nRet = i;

                           

                            delete[] pCodecInfo;

                            return nRet;

                   }

                   else

                   {

                            continue;

                   }

         }

         delete[] pCodecInfo;

         return nRet;

}

//bmp转化为jpg

ConvertPic(L"image/jpeg","c:"1.jpg","c:"1.bmp")

//jpg转化为bmp

ConvertPic(L"image/bmp","c:"1.bmp","c:"1.jpg")

 

//二。bmp图片合并

BOOL CombinePic(const WCHAR *format, const CString &strDst, const CString &strPic1,

                                     const CString &strPic2)

{

         BOOL bCombine = false;

         int nRet = 0;

         CLSID clsid;

         nRet = GetEncoderClsid(format,&clsid);

         if (nRet>=0)

         {

                  USES_CONVERSION;

                   Bitmap bmp1(A2W(strPic1));

                   Bitmap bmp2(A2W(strPic2));

                  

                   int nWidth = 0, nHeight = 0;

                  nWidth = bmp1.GetWidth();   //假设两图片大小同

                  nHeight = bmp1.GetHeight();

                   Bitmap bmpCombine(2*nWidth,nHeight);  //高不变,宽*2,水平合并

                  Graphics * pG = NULL;

                   pG = Graphics::FromImage(&bmpCombine);

                   if (pG!=NULL)

                   {

                            pG->DrawImage(&bmp1,0,0);

                            pG->DrawImage(&bmp2,nWidth,0);

                            bmpCombine.Save(A2W(strDst),&clsid,NULL);

                   }

         }

         return bCombine;

}

 

 

有了上面的功能,其他的就没问题了。

例子:
CombinePic(L"image/bmp","12.bmp","1.bmp","2.bmp");

 

标签:软件开发 | 浏览数(447) | 评论数(0) | 2006-10-24

 

//TCP的服务器端:(TcpSrv.cpp)

#include "Winsock2.h"

#include "stdio.h"

 

void main()

{

         //初始化socket编程环境

         WORD wVersionRequested;

         WSADATA wsaData;

         int err;

         wVersionRequested = MAKEWORD( 1, 1 );

         err = WSAStartup( wVersionRequested, &wsaData );

         if ( err != 0 )

         {

                   return;

         }

         if ( LOBYTE( wsaData.wVersion ) != 1 ||

        HIBYTE( wsaData.wVersion ) != 1 )

         {

                  WSACleanup( );

                   return;

         }

        

         //创建Tcp服务器socket

         SOCKET sockSrv = socket( AF_INET , SOCK_STREAM , 0 );

        

         //服务器地址

         SOCKADDR_IN addrSrv ;

         addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY) ;

         addrSrv.sin_family = AF_INET ;

         addrSrv.sin_port = htons(4000) ;

        

         //socket与地址绑定在一起

         bind( sockSrv ,(SOCKADDR*)&addrSrv , sizeof(SOCKADDR) ) ;

        

         //开始监听客户端请求,最大连接数为5

         listen( sockSrv , 5 ) ;

        

         //用于存放客户端地址

         SOCKADDR_IN addrClient ;

         int len = sizeof( SOCKADDR_IN ) ;

        

         //不断接收客户端发送的请求

         while ( 1 )

         {

                   //接收到的客户端请求socket

                  SOCKET sockConn = accept( sockSrv , (SOCKADDR *)&addrClient , &len ) ;

                  

                   //发送数据

                   char sendBuf[100] ;

                  sprintf( sendBuf , "Weclome %s to here:" , inet_ntoa(addrClient.sin_addr) ) ;

                  send( sockConn , sendBuf , strlen(sendBuf)+1 , 0 ) ;

                  

                   //接收数据

                   char recvBuf[100] ;

                  recv( sockConn , recvBuf , 100 , 0 ) ;

                  printf("%s"n" , recvBuf ) ;

                  

                   //关闭socket

                  closesocket( sockConn ) ;

         }

         //清理socket编程环境

         WSACleanup() ;

}

 

//TCP的客户端:(TcpClient.cpp)

 

# include "Winsock2.h"

# include "stdio.h"

 

void main()

{

         //初始化socket编程环境

         WORD wVersionRequested;

         WSADATA wsaData;

         int err;

         wVersionRequested = MAKEWORD( 1, 1 );

         err = WSAStartup( wVersionRequested, &wsaData );

         if ( err != 0 )

         {

                   return;

         }

         if ( LOBYTE( wsaData.wVersion ) != 1 ||

        HIBYTE( wsaData.wVersion ) != 1 )

         {

                  WSACleanup( );

                   return;

         }

        

         //建立客户端socket

         SOCKET sockClient = socket( AF_INET ,SOCK_STREAM , 0 ) ;

        

         //服务器地址

         SOCKADDR_IN addrSrv ;

         addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1") ;

         addrSrv.sin_family = AF_INET ;

         addrSrv.sin_port = htons( 4000 ) ;

        

         //连接服务器

         connect( sockClient , (SOCKADDR*)&addrSrv , sizeof(SOCKADDR)) ;

        

         //等待接收服务器的响应

         char recvBuf[100];

         recv( sockClient , recvBuf , 100 , 0 ) ;

         printf( "%s"n" , recvBuf ) ;

        

         char sendBuf[100] ;

         sprintf( sendBuf , "%s" , "this is zhang san" ) ;

         send( sockClient , sendBuf , strlen(sendBuf)+1 , 0 ) ;

        

         closesocket( sockClient ) ;

         WSACleanup() ;

}

 

 

标签:软件开发 | 浏览数(1009) | 评论数(0) | 2006-10-23

Operating System Error Codes

Last reviewed: 04/09/2004
Article ID: R10307

Code

Description

Name

0

The operation completed successfully.

ERROR_SUCCESS

1

Incorrect function.

ERROR_INVALID_FUNCTION

2

The system cannot find the file specified.

ERROR_FILE_NOT_FOUND

3

The system cannot find the path specified.

ERROR_PATH_NOT_FOUND

4

The system cannot open the file.

ERROR_TOO_MANY_OPEN_FILES

5

Access is denied.

ERROR_ACCESS_DENIED

6

The handle is invalid.

ERROR_INVALID_HANDLE

7

The storage control blocks were destroyed.

ERROR_ARENA_TRASHED

8

Not enough storage is available to process this command.

ERROR_NOT_ENOUGH_MEMORY

9

The storage control block address is invalid.

ERROR_INVALID_BLOCK

10

The environment is incorrect.

ERROR_BAD_ENVIRONMENT

11

An attempt was made to load a program with an incorrect format.

ERROR_BAD_FORMAT

12

The access code is invalid.

ERROR_INVALID_ACCESS

13

The data is invalid.

ERROR_INVALID_DATA

14

Not enough storage is available to complete this operation.

ERROR_OUTOFMEMORY

15

The system cannot find the drive specified.

ERROR_INVALID_DRIVE

16

The directory cannot be removed.

ERROR_CURRENT_DIRECTORY

17

The system cannot move the file to a different disk drive.

ERROR_NOT_SAME_DEVICE

18

There are no more files.

ERROR_NO_MORE_FILES

19

The media is write protected.

ERROR_WRITE_PROTECT

20

The system cannot find the device specified.

ERROR_BAD_UNIT

21

The device is not ready.

ERROR_NOT_READY

22

The device does not recognize the command.

ERROR_BAD_COMMAND

23

Data error (cyclic redundancy check).

ERROR_CRC

24

The program issued a command but the command length is incorrect.

ERROR_BAD_LENGTH

25

The drive cannot locate a specific area or track on the disk.

ERROR_SEEK

26

The specified disk or diskette cannot be accessed.

ERROR_NOT_DOS_DISK

27

The drive cannot find the sector requested.

ERROR_SECTOR_NOT_FOUND

28

The printer is out of paper.

ERROR_OUT_OF_PAPER

29

The system cannot write to the specified device.

ERROR_WRITE_FAULT

30

The system cannot read from the specified device.

ERROR_READ_FAULT

31

A device attached to the system is not functioning.

ERROR_GEN_FAILURE

32

The process cannot access the file because it is being used by another process.

ERROR_SHARING_VIOLATION

33

The process cannot access the file because another process has locked a portion of the file.

ERROR_LOCK_VIOLATION

34

The wrong diskette is in the drive. Insert %2 (Volume Serial Number: %3) into drive %1.

ERROR_WRONG_DISK

36

Too many files opened for sharing.

ERROR_SHARING_BUFFER_EXCEEDED

38

Reached the end of the file.

ERROR_HANDLE_EOF

39

The disk is full.

ERROR_HANDLE_DISK_FULL

50

The network request is not supported.

ERROR_NOT_SUPPORTED

51

The remote computer is not available.

ERROR_REM_NOT_LIST

52

A duplicate name exists on the network.

ERROR_DUP_NAME

53

The network path was not found.

ERROR_BAD_NETPATH

54

The network is busy.

ERROR_NETWORK_BUSY

55

The specified network resource or device is no longer available.

ERROR_DEV_NOT_EXIST

56

The network BIOS command limit has been reached.

ERROR_TOO_MANY_CMDS

57

A network adapter hardware error occurred.

ERROR_ADAP_HDW_ERR

58

The specified server cannot perform the requested operation.

ERROR_BAD_NET_RESP

59

An unexpected network error occurred.

ERROR_UNEXP_NET_ERR

60

The remote adapter is not compatible.

ERROR_BAD_REM_ADAP

61

The printer queue is full.

ERROR_PRINTQ_FULL

62

Space to store the file waiting to be printed is not available on the server.

ERROR_NO_SPOOL_SPACE

63

Your file waiting to be printed was deleted.

ERROR_PRINT_CANCELLED

64

The specified network name is no longer available.

ERROR_NETNAME_DELETED

65

Network access is denied.

ERROR_NETWORK_ACCESS_DENIED

66

The network resource type is not correct.

ERROR_BAD_DEV_TYPE

67

The network name cannot be found.

ERROR_BAD_NET_NAME

68

The name limit for the local computer network adapter card was exceeded.

ERROR_TOO_MANY_NAMES

69

The network BIOS session limit was exceeded.

ERROR_TOO_MANY_SESS

70

The remote server has been paused or is in the process of being started.

ERROR_SHARING_PAUSED

71

No more connections can be made to this remote computer at this time because there are already as many connections as the computer can accept.

ERROR_REQ_NOT_ACCEP

72

The specified printer or disk device has been paused.

ERROR_REDIR_PAUSED

80

The file exists.

ERROR_FILE_EXISTS

82

The directory or file cannot be created.

ERROR_CANNOT_MAKE

83

Fail on INT 24.

ERROR_FAIL_I24

84

Storage to process this request is not available.

ERROR_OUT_OF_STRUCTURES

85

The local device name is already in use.

ERROR_ALREADY_ASSIGNED

86

The specified network password is not correct.

ERROR_INVALID_PASSWORD

87

The parameter is incorrect.

ERROR_INVALID_PARAMETER

88

A write fault occurred on the network.

ERROR_NET_WRITE_FAULT

89

The system cannot start another process at this time.

ERROR_NO_PROC_SLOTS

100

Cannot create another system semaphore.

ERROR_TOO_MANY_SEMAPHORES

101

The exclusive semaphore is owned by another process.

ERROR_EXCL_SEM_ALREADY_OWNED

102

The semaphore is set and cannot be closed.

ERROR_SEM_IS_SET

103

The semaphore cannot be set again.

ERROR_TOO_MANY_SEM_REQUESTS

104

Cannot request exclusive semaphores at interrupt time.

ERROR_INVALID_AT_INTERRUPT_TIME

105

The previous ownership of this semaphore has ended.

ERROR_SEM_OWNER_DIED

106

Insert the diskette for drive %1.

ERROR_SEM_USER_LIMIT

107

The program stopped because an alternate diskette was not inserted.

ERROR_DISK_CHANGE

108

The disk is in use or locked by another process.

ERROR_DRIVE_LOCKED

109

The pipe has been ended.

ERROR_BROKEN_PIPE

110

The system cannot open the device or file specified.

ERROR_OPEN_FAILED

111

The file name is too long.

ERROR_BUFFER_OVERFLOW

112

There is not enough space on the disk.

ERROR_DISK_FULL

113

No more internal file identifiers available.

ERROR_NO_MORE_SEARCH_HANDLES

114

The target internal file identifier is incorrect.

ERROR_INVALID_TARGET_HANDLE

117

The IOCTL call made by the application program is not correct.

ERROR_INVALID_CATEGORY

118

The verify-on-write switch parameter value is not correct.

ERROR_INVALID_VERIFY_SWITCH

119

The system does not support the command requested.

ERROR_BAD_DRIVER_LEVEL

120

This function is not valid on this platform.

ERROR_CALL_NOT_IMPLEMENTED

121

The semaphore time-out period has expired.

ERROR_SEM_TIMEOUT

122

The data area passed to a system call is too small.

ERROR_INSUFFICIENT_BUFFER

123

The filename, directory name, or volume label syntax is incorrect.

ERROR_INVALID_NAME

124

The system call level is not correct.

ERROR_INVALID_LEVEL

125

The disk has no volume label.

ERROR_NO_VOLUME_LABEL

126

The specified module could not be found.

ERROR_MOD_NOT_FOUND

127

The specified procedure could not be found.

ERROR_PROC_NOT_FOUND

128

There are no child processes to wait for.

ERROR_WAIT_NO_CHILDREN

129

The %1 application cannot be run in Windows NT mode.

ERROR_CHILD_NOT_COMPLETE

130

Attempt to use a file handle to an open disk partition for an operation other than raw disk I/O.

ERROR_DIRECT_ACCESS_HANDLE

131

An attempt was made to move the file pointer before the beginning of the file.

ERROR_NEGATIVE_SEEK

132

The file pointer cannot be set on the specified device or file.

ERROR_SEEK_ON_DEVICE

133

A JOIN or SUBST command cannot be used for a drive that contains previously joined drives.

ERROR_IS_JOIN_TARGET

134

An attempt was made to use a JOIN or SUBST command on a drive that has already been joined.

ERROR_IS_JOINED

135

An attempt was made to use a JOIN or SUBST command on a drive that has already been substituted.

ERROR_IS_SUBSTED

136

The system tried to delete the JOIN of a drive that is not joined.

ERROR_NOT_JOINED

137

The system tried to delete the substitution of a drive that is not substituted.

ERROR_NOT_SUBSTED

138

The system tried to join a drive to a directory on a joined drive.

ERROR_JOIN_TO_JOIN

139

The system tried to substitute a drive to a directory on a substituted drive.

ERROR_SUBST_TO_SUBST

140

The system tried to join a drive to a directory on a substituted drive.

ERROR_JOIN_TO_SUBST

141

The system tried to SUBST a drive to a directory on a joined drive.

ERROR_SUBST_TO_JOIN

142

The system cannot perform a JOIN or SUBST at this time.

ERROR_BUSY_DRIVE

143

The system cannot join or substitute a drive to or for a directory on the same drive.

ERROR_SAME_DRIVE

144

The directory is not a subdirectory of the root directory.

ERROR_DIR_NOT_ROOT

145

The directory is not empty.

ERROR_DIR_NOT_EMPTY

146

The path specified is being used in a substitute.

ERROR_IS_SUBST_PATH

147

Not enough resources are available to process this command.

ERROR_IS_JOIN_PATH

148

The path specified cannot be used at this time.

ERROR_PATH_BUSY

149

An attempt was made to join or substitute a drive for which a directory on the drive is the target of a previous substitute.

ERROR_IS_SUBST_TARGET

150

System trace information was not specified in your CONFIG.SYS file, or tracing is disallowed.

ERROR_SYSTEM_TRACE

151

The number of specified semaphore events for DosMuxSemWait is not correct.

ERROR_INVALID_EVENT_COUNT

152

DosMuxSemWait did not execute; too many semaphores are already set.

ERROR_TOO_MANY_MUXWAITERS

153

The DosMuxSemWait list is not correct.

ERROR_INVALID_LIST_FORMAT

154

The volume label you entered exceeds the label character limit of the target file system.

ERROR_LABEL_TOO_LONG

155

Cannot create another thread.

ERROR_TOO_MANY_TCBS

156

The recipient process has refused the signal.

ERROR_SIGNAL_REFUSED

157

The segment is already discarded and cannot be locked.

ERROR_DISCARDED

158

The segment is already unlocked.

ERROR_NOT_LOCKED

159

The address for the thread ID is not correct.

ERROR_BAD_THREADID_ADDR

160

The argument string passed to DosExecPgm is not correct.

ERROR_BAD_ARGUMENTS

161

The specified path is invalid.

ERROR_BAD_PATHNAME

162

A signal is already pending.

ERROR_SIGNAL_PENDING

164

No more threads can be created in the system.

ERROR_MAX_THRDS_REACHED

167

Unable to lock a region of a file.

ERROR_LOCK_FAILED

170

The requested resource is in use.

ERROR_BUSY

173

A lock request was not outstanding for the supplied cancel region.

ERROR_CANCEL_VIOLATION

174

The file system does not support atomic changes to the lock type.

ERROR_ATOMIC_LOCKS_NOT_SUPPORTED

180

The system detected a segment number that was not correct.

ERROR_INVALID_SEGMENT_NUMBER

182

The operating system cannot run %1.

ERROR_INVALID_ORDINAL

183

Cannot create a file when that file already exists.

ERROR_ALREADY_EXISTS

186

The flag passed is not correct.

ERROR_INVALID_FLAG_NUMBER

187

The specified system semaphore name was not found.

ERROR_SEM_NOT_FOUND

188

The operating system cannot run %1.

ERROR_INVALID_STARTING_CODESEG

189

The operating system cannot run %1.

ERROR_INVALID_STACKSEG

190

The operating system cannot run %1.

ERROR_INVALID_MODULETYPE

191

Cannot run %1 in Windows NT mode.

ERROR_INVALID_EXE_SIGNATURE

192

The operating system cannot run %1.

ERROR_EXE_MARKED_INVALID

193

Is not a valid application.

ERROR_BAD_EXE_FORMAT

194

The operating system cannot run %1.

ERROR_ITERATED_DATA_EXCEEDS_64k

195

The operating system cannot run %1.

ERROR_INVALID_MINALLOCSIZE

196

The operating system cannot run this application program.

ERROR_DYNLINK_FROM_INVALID_RING

197

The operating system is not presently configured to run this application.

ERROR_IOPL_NOT_ENABLED

198

The operating system cannot run %1.

ERROR_INVALID_SEGDPL

199

The operating system cannot run this application program.

ERROR_AUTODATASEG_EXCEEDS_64k

200

The code segment cannot be greater than or equal to 64K.

ERROR_RING2SEG_MUST_BE_MOVABLE

201

The operating system cannot run %1.

ERROR_RELOC_CHAIN_XEEDS_SEGLIM

202

The operating system cannot run %1.

ERROR_INFLOOP_IN_RELOC_CHAIN

203

The system could not find the environment option that was entered.

ERROR_ENVVAR_NOT_FOUND

205

No process in the command subtree has a signal handler.

ERROR_NO_SIGNAL_SENT

206

The filename or extension is too long.

ERROR_FILENAME_EXCED_RANGE

207

The ring 2 stack is in use.

ERROR_RING2_STACK_IN_USE

208

The global filename characters, * or ?, are entered incorrectly or too many global filename characters are specified.

ERROR_META_EXPANSION_TOO_LONG

209

The signal being posted is not correct.

ERROR_INVALID_SIGNAL_NUMBER

210

The signal handler cannot be set.

ERROR_THREAD_1_INACTIVE

212

The segment is locked and cannot be reallocated.

ERROR_LOCKED

214

Too many dynamic-link modules are attached to this program or dynamic-link module.

ERROR_TOO_MANY_MODULES

215

Can't nest calls to LoadModule.

ERROR_NESTING_NOT_ALLOWED

216

The image file %1 is valid, but is for a machine type other than the current machine.

ERROR_EXE_MACHINE_TYPE_MISMATCH

230

The pipe state is invalid.

ERROR_BAD_PIPE

231

All pipe instances are busy.

ERROR_PIPE_BUSY

232

The pipe is being closed.

ERROR_NO_DATA

233

No process is on the other end of the pipe.

ERROR_PIPE_NOT_CONNECTED

234

More data is available.

ERROR_MORE_DATA

240

The session was canceled.

ERROR_VC_DISCONNECTED

254

The specified extended attribute name was invalid.

ERROR_INVALID_EA_NAME

255

The extended attributes are inconsistent.

ERROR_EA_LIST_INCONSISTENT

259

No more data is available.

ERROR_NO_MORE_ITEMS

266

The copy functions cannot be used.

ERROR_CANNOT_COPY

267

The directory name is invalid.

ERROR_DIRECTORY

275

The extended attributes did not fit in the buffer.

ERROR_EAS_DIDNT_FIT

276

The extended attribute file on the mounted file system is corrupt.

ERROR_EA_FILE_CORRUPT

277

The extended attribute table file is full.

ERROR_EA_TABLE_FULL

278

The specified extended attribute handle is invalid.

ERROR_INVALID_EA_HANDLE

282

The mounted file system does not support extended attributes.

ERROR_EAS_NOT_SUPPORTED

288

Attempt to release mutex not owned by caller.

ERROR_NOT_OWNER

298

Too many posts were made to a semaphore.

ERROR_TOO_MANY_POSTS

299

Only part of a ReadProcessMemory or WriteProcessMemory request was completed.

ERROR_PARTIAL_COPY

317

The system cannot find message text for message number 0x%1 in the message file for %2.

ERROR_MR_MID_NOT_FOUND

487

Attempt to access invalid address.

ERROR_INVALID_ADDRESS

534

Arithmetic result exceeded 32 bits.

ERROR_ARITHMETIC_OVERFLOW

535

There is a process on other end of the pipe.

ERROR_PIPE_CONNECTED

536

Waiting for a process to open the other end of the pipe.

ERROR_PIPE_LISTENING

994

Access to the extended attribute was denied.

ERROR_EA_ACCESS_DENIED

995

The I/O operation has been aborted because of either a thread exit or an application request.

ERROR_OPERATION_ABORTED

996

Overlapped I/O event is not in a signaled state.

ERROR_IO_INCOMPLETE

997

Overlapped I/O operation is in progress.

ERROR_IO_PENDING

998

Invalid access to memory location.

ERROR_NOACCESS

999

Error performing inpage operation.

ERROR_SWAPERROR

1001

Recursion too deep; the stack overflowed.

ERROR_STACK_OVERFLOW

1002

The window cannot act on the sent message.

ERROR_INVALID_MESSAGE

1003

Cannot complete this function.

ERROR_CAN_NOT_COMPLETE

1004

Invalid flags.

ERROR_INVALID_FLAGS

1005

The volume does not contain a recognized file system. Please make sure that all required file system drivers are loaded and that the volume is not corrupted.

ERROR_UNRECOGNIZED_VOLUME

1006

The volume for a file has been externally altered so that the opened file is no longer valid.

ERROR_FILE_INVALID

1007

The requested operation cannot be performed in full-screen mode.

ERROR_FULLSCREEN_MODE

1008

An attempt was made to reference a token that does not exist.

ERROR_NO_TOKEN

1009

The configuration registry database is corrupt.

ERROR_BADDB

1010

The configuration registry key is invalid.

ERROR_BADKEY

1011

The configuration registry key could not be opened.

ERROR_CANTOPEN

1012

The configuration registry key could not be read.

ERROR_CANTREAD

1013

The configuration registry key could not be written.

ERROR_CANTWRITE

1014

One of the files in the registry database had to be recovered by use of a log or alternate copy. The recovery was successful.

ERROR_REGISTRY_RECOVERED

1015

The registry is corrupted. The structure of one of the files that contains registry data is corrupted, or the system's image of the file in memory is corrupted, or the file could not be recovered because the alternate copy or log was absent or corrupted.

ERROR_REGISTRY_CORRUPT

1016

An I/O operation initiated by the registry failed unrecoverably. The registry could not read in, or write out, or flush, one of the files that contain the system's image of the registry.

ERROR_REGISTRY_IO_FAILED

1017

The system has attempted to load or restore a file into the registry, but the specified file is not in a registry file format.

ERROR_NOT_REGISTRY_FILE

1018

Illegal operation attempted on a registry key that has been marked for deletion.

ERROR_KEY_DELETED

1019

System could not allocate the required space in a registry log.

ERROR_NO_LOG_SPACE

1020

Cannot create a symbolic link in a registry key that already has subkeys or values.

ERROR_KEY_HAS_CHILDREN

1021

Cannot create a stable subkey under a volatile parent key.

ERROR_CHILD_MUST_BE_VOLATILE

1022

A notify change request is being completed and the information is not being returned in the caller's buffer. The caller now needs to enumerate the files to find the changes.

ERROR_NOTIFY_ENUM_DIR

1051

A stop control has been sent to a service that other running services are dependent on.

ERROR_DEPENDENT_SERVICES_RUNNING

1052

The requested control is not valid for this service.

ERROR_INVALID_SERVICE_CONTROL

1053

The service did not respond to the start or control request in a timely fashion.

ERROR_SERVICE_REQUEST_TIMEOUT

1054

A thread could not be created for the service.

ERROR_SERVICE_NO_THREAD

1055

The service database is locked.

ERROR_SERVICE_DATABASE_LOCKED

1056

An instance of the service is already running.

ERROR_SERVICE_ALREADY_RUNNING

1057

The account name is invalid or does not exist.

ERROR_INVALID_SERVICE_ACCOUNT

1058

The specified service is disabled and cannot be started.

ERROR_SERVICE_DISABLED

1059

Circular service dependency was specified.

ERROR_CIRCULAR_DEPENDENCY

1060

The specified service does not exist as an installed service.

ERROR_SERVICE_DOES_NOT_EXIST

1061

The service cannot accept control messages at this time.

ERROR_SERVICE_CANNOT_ACCEPT_CTRL

1062

The service has not been started.

ERROR_SERVICE_NOT_ACTIVE

1063

The service process could not connect to the service controller.

ERROR_FAILED_SERVICE_CONTROLLER_CONNECT

1064

An exception occurred in the service when handling the control request.

ERROR_EXCEPTION_IN_SERVICE

1065

The database specified does not exist.

ERROR_DATABASE_DOES_NOT_EXIST

1066

The service has returned a service-specific error code.

ERROR_SERVICE_SPECIFIC_ERROR

1067

The process terminated unexpectedly.

ERROR_PROCESS_ABORTED

1068

The dependency service or group failed to start.

ERROR_SERVICE_DEPENDENCY_FAIL

1069

The service did not start due to a logon failure.

ERROR_SERVICE_LOGON_FAILED

1070

After starting, the service hung in a start-pending state.

ERROR_SERVICE_START_HANG

1071

The specified service database lock is invalid.

ERROR_INVALID_SERVICE_LOCK

1072

The specified service has been marked for deletion.

ERROR_SERVICE_MARKED_FOR_DELETE

1073

The specified service already exists.

ERROR_SERVICE_EXISTS

1074

The system is currently running with the last-known-good configuration.

ERROR_ALREADY_RUNNING_LKG

1075

The dependency service does not exist or has been marked for deletion.

ERROR_SERVICE_DEPENDENCY_DELETED

1076

The current boot has already been accepted for use as the last-known-good control set.

ERROR_BOOT_ALREADY_ACCEPTED

1077

No attempts to start the service have been made since the last boot.

ERROR_SERVICE_NEVER_STARTED

1078

The name is already in use as either a service name or a service display name.

ERROR_DUPLICATE_SERVICE_NAME

1079

The account specified for this service is different from the account specified for other services running in the same process.

ERROR_DIFFERENT_SERVICE_ACCOUNT

1100

The physical end of the tape has been reached.

ERROR_END_OF_MEDIA

1101

A tape access reached a filemark.

ERROR_FILEMARK_DETECTED

1102

The beginning of the tape or partition was encountered.

ERROR_BEGINNING_OF_MEDIA

1103

A tape access reached the end of a set of files.

ERROR_SETMARK_DETECTED

1104

No more data is on the tape.

ERROR_NO_DATA_DETECTED

1105

Tape could not be partitioned.

ERROR_PARTITION_FAILURE

1106

When accessing a new tape of a multivolume partition, the current block size is incorrect.

ERROR_INVALID_BLOCK_LENGTH

1107

Tape partition information could not be found when loading a tape.

ERROR_DEVICE_NOT_PARTITIONED

1108

Unable to lock the media eject mechanism.

ERROR_UNABLE_TO_LOCK_MEDIA

1109

Unable to unload the media.

ERROR_UNABLE_TO_UNLOAD_MEDIA

1110

The media in the drive may have changed.

ERROR_MEDIA_CHANGED

1111

The I/O bus was reset.

ERROR_BUS_RESET

1112

No media in drive.

ERROR_NO_MEDIA_IN_DRIVE

1113

No mapping for the Unicode character exists in the target multibyte code page.

ERROR_NO_UNICODE_TRANSLATION

1114

A dynamic link library (DLL) initialization routine failed.

ERROR_DLL_INIT_FAILED

1115

A system shutdown is in progress.

ERROR_SHUTDOWN_IN_PROGRESS

1116

Unable to abort the system shutdown because no shutdown was in progress.

ERROR_NO_SHUTDOWN_IN_PROGRESS

1117

The request could not be performed because of an I/O device error.

ERROR_IO_DEVICE

1118

No serial device was successfully initialized. The serial driver will unload.

ERROR_SERIAL_NO_DEVICE

1119

Unable to open a device that was sharing an interrupt request (IRQ) with other devices. At least one other device that uses that IRQ was already opened.

ERROR_IRQ_BUSY

1120

A serial I/O operation was completed by another write to the serial port. The IOCTL_SERIAL_XOFF_COUNTER reached zero.)

ERROR_MORE_WRITES

1121

A serial I/O operation completed because the time-out period expired. (The IOCTL_SERIAL_XOFF_COUNTER did not reach zero.)

ERROR_COUNTER_TIMEOUT

1122

No ID address mark was found on the floppy disk.

ERROR_FLOPPY_ID_MARK_NOT_FOUND

1123

Mismatch between the floppy disk sector ID field and the floppy disk controller track address.

ERROR_FLOPPY_WRONG_CYLINDER

1124

The floppy disk controller reported an error that is not recognized by the floppy disk driver.

ERROR_FLOPPY_UNKNOWN_ERROR

1125

The floppy disk controller returned inconsistent results in its registers.

ERROR_FLOPPY_BAD_REGISTERS

1126

While accessing the hard disk, a recalibrate operation failed, even after retries.

ERROR_DISK_RECALIBRATE_FAILED

1127

While accessing the hard disk, a disk operation failed even after retries.

ERROR_DISK_OPERATION_FAILED

1128

While accessing the hard disk, a disk controller reset was needed, but even that failed.

ERROR_DISK_RESET_FAILED

1129

Physical end of tape encountered.

ERROR_EOM_OVERFLOW

1130

Not enough server storage is available to process this command.

ERROR_NOT_ENOUGH_SERVER_MEMORY

1131

A potential deadlock condition has been detected.

ERROR_POSSIBLE_DEADLOCK

1132

The base address or the file offset specified does not have the proper alignment.

ERROR_MAPPED_ALIGNMENT

1140

An attempt to change the system power state was vetoed by another application or driver.

ERROR_SET_POWER_STATE_VETOED

1141

The system BIOS failed an attempt to change the system power state.

ERROR_SET_POWER_STATE_FAILED

1142

An attempt was made to create more links on a file than the file system supports.

ERROR_TOO_MANY_LINKS

1150

The specified program requires a newer version of Windows.

ERROR_OLD_WIN_VERSION

1151

The specified program is not a Windows or MS-DOS program.

ERROR_APP_WRONG_OS

1152

Cannot start more than one instance of the specified program.

ERROR_SINGLE_INSTANCE_APP

1153

The specified program was written for an earlier version of Windows.

ERROR_RMODE_APP

1154

One of the library files needed to run this application is damaged.

ERROR_INVALID_DLL

1155

No application is associated with the specified file for this operation.

ERROR_NO_ASSOCIATION

1156

An error occurred in sending the command to the application.

ERROR_DDE_FAIL

1157

One of the library files needed to run this application cannot be found.

ERROR_DLL_NOT_FOUND

1200

The specified device name is invalid.

ERROR_BAD_DEVICE

1201

The device is not currently connected but it is a remembered connection.

ERROR_CONNECTION_UNAVAIL

1202

An attempt was made to remember a device that had previously been remembered.

ERROR_DEVICE_ALREADY_REMEMBERED

1203

No network provider accepted the given network path.

ERROR_NO_NET_OR_BAD_PATH

1204

The specified network provider name is invalid.

ERROR_BAD_PROVIDER

1205

Unable to open the network connection profile.

ERROR_CANNOT_OPEN_PROFILE

1206

The network connection profile is corrupt.

ERROR_BAD_PROFILE

1207

Cannot enumerate a noncontainer.

ERROR_NOT_CONTAINER

1208

An extended error has occurred.

ERROR_EXTENDED_ERROR

1209

The format of the specified group name is invalid.

ERROR_INVALID_GROUPNAME

1210

The format of the specified computer name is invalid.

ERROR_INVALID_COMPUTERNAME

1211

The format of the specified event name is invalid.

ERROR_INVALID_EVENTNAME

1212

The format of the specified domain name is invalid.

ERROR_INVALID_DOMAINNAME

1213

The format of the specified service name is invalid.

ERROR_INVALID_SERVICENAME

1214

The format of the specified network name is invalid.

ERROR_INVALID_NETNAME

1215

The format of the specified share name is invalid.

ERROR_INVALID_SHARENAME

1216

The format of the specified password is invalid.

ERROR_INVALID_PASSWORDNAME

1217

The format of the specified message name is invalid.

ERROR_INVALID_MESSAGENAME

1218

The format of the specified message destination is invalid.

ERROR_INVALID_MESSAGEDEST

1219

The credentials supplied conflict with an existing set of credentials.

ERROR_SESSION_CREDENTIAL_CONFLICT

1220

An attempt was made to establish a session to a network server, but there are already too many sessions established to that server.

ERROR_REMOTE_SESSION_LIMIT_EXCEEDED

1221

The workgroup or domain name is already in use by another computer on the network.

ERROR_DUP_DOMAINNAME

1222

The network is not present or not started.

ERROR_NO_NETWORK

1223

The operation was canceled by the user.

ERROR_CANCELLED

1224

The requested operation cannot be performed on a file with a user-mapped section open.

ERROR_USER_MAPPED_FILE

1225

The remote system refused the network connection.

ERROR_CONNECTION_REFUSED

1226

The network connection was gracefully closed.

ERROR_GRACEFUL_DISCONNECT

1227

The network transport endpoint already has an address associated with it.

ERROR_ADDRESS_ALREADY_ASSOCIATED

1228

An address has not yet been associated with the network endpoint.

ERROR_ADDRESS_NOT_ASSOCIATED

1229

An operation was attempted on a nonexistent network connection.

ERROR_CONNECTION_INVALID

1230

An invalid operation was attempted on an active network connection.

ERROR_CONNECTION_ACTIVE

1231

The remote network is not reachable by the transport.

ERROR_NETWORK_UNREACHABLE

1232

The remote system is not reachable by the transport.

ERROR_HOST_UNREACHABLE

1233

The remote system does not support the transport protocol.

ERROR_PROTOCOL_UNREACHABLE

1234

No service is operating at the destination network endpoint on the remote system.

ERROR_PORT_UNREACHABLE

1235

The request was aborted.

ERROR_REQUEST_ABORTED

1236

The network connection was aborted by the local system.

ERROR_CONNECTION_ABORTED

1237

The operation could not be completed. A retry should be performed.

ERROR_RETRY

1238

A connection to the server could not be made because the limit on the number of concurrent connections for this account has been reached.

ERROR_CONNECTION_COUNT_LIMIT

1239

Attempting to log in during an unauthorized time of day for this account.

ERROR_LOGIN_TIME_RESTRICTION

1240

The account is not authorized to log in from this station.

ERROR_LOGIN_WKSTA_RESTRICTION

1241

The network address could not be used for the operation requested.

ERROR_INCORRECT_ADDRESS

1242

The service is already registered.

ERROR_ALREADY_REGISTERED

1243

The specified service does not exist.

ERROR_SERVICE_NOT_FOUND

1244

The operation being requested was not performed because the user has not been authenticated.

ERROR_NOT_AUTHENTICATED

1245

The operation being requested was not performed because the user has not logged on to the network. The specified service does not exist.

ERROR_NOT_LOGGED_ON

1246

Caller to continue with work in progress.

ERROR_CONTINUE

1247

An attempt was made to perform an initialization operation when initialization has already been completed.

ERROR_ALREADY_INITIALIZED

1248

No more local devices.

ERROR_NO_MORE_DEVICES

1300

Not all privileges referenced are assigned to the caller.

ERROR_NOT_ALL_ASSIGNED

1301

Some mapping between account names and security IDs was not done.

ERROR_SOME_NOT_MAPPED

1302

No system quota limits are specifically set for this account.

ERROR_NO_QUOTAS_FOR_ACCOUNT

1303

No encryption key is available. A well-known encryption key was returned.

ERROR_LOCAL_USER_SESSION_KEY

1304

The password is too complex to be converted to a LAN Manager password. The LAN Manager password returned is a NULL string.

ERROR_NULL_LM_PASSWORD

1305

The revision level is unknown.

ERROR_UNKNOWN_REVISION

1306

Indicates two revision levels are incompatible.

ERROR_REVISION_MISMATCH

1307

This security ID may not be assigned as the owner of this object.

ERROR_INVALID_OWNER

1308

This security ID may not be assigned as the primary group of an object.

ERROR_INVALID_PRIMARY_GROUP

1309

An attempt has been made to operate on an impersonation token by a thread that is not currently impersonating a client.

ERROR_NO_IMPERSONATION_TOKEN

1310

The group may not be disabled.

ERROR_CANT_DISABLE_MANDATORY

1311

There are currently no logon servers available to service the logon request.

ERROR_NO_LOGON_SERVERS

1312

A specified logon session does not exist. It may already have been terminated.

ERROR_NO_SUCH_LOGON_SESSION

1313

A specified privilege does not exist.

ERROR_NO_SUCH_PRIVILEGE

1314

A required privilege is not held by the client.

ERROR_PRIVILEGE_NOT_HELD

1315

The name provided is not a properly formed account name.

ERROR_INVALID_ACCOUNT_NAME

1316

The specified user already exists.

ERROR_USER_EXISTS

1317

The specified user does not exist.

ERROR_NO_SUCH_USER

1318

The specified group already exists.

ERROR_GROUP_EXISTS

1319

The specified group does not exist.

ERROR_NO_SUCH_GROUP

1320

Either the specified user account is already a member of the specified group, or the specified group cannot be deleted because it contains a member.

ERROR_MEMBER_IN_GROUP

1321

The specified user account is not a member of the specified group account.

ERROR_MEMBER_NOT_IN_GROUP

1322

The last remaining administration account cannot be disabled or deleted.

ERROR_LAST_ADMIN

1323

Unable to update the password. The value provided as the current password is incorrect.

ERROR_WRONG_PASSWORD

1324

Unable to update the password. The value provided for the new password contains values that are not allowed in passwords.

ERROR_ILL_FORMED_PASSWORD

1325

Unable to update the password because a password update rule has been violated.

ERROR_PASSWORD_RESTRICTION

1326

Logon failure: unknown user name or bad password.

ERROR_LOGON_FAILURE

1327

Logon failure: user account restriction.

ERROR_ACCOUNT_RESTRICTION

1328

Logon failure: account logon time restriction violation.

ERROR_INVALID_LOGON_HOURS

1329

Logon failure: user not allowed to log on to this computer.

ERROR_INVALID_WORKSTATION

1330

Logon failure: the specified account password has expired.

ERROR_PASSWORD_EXPIRED

1331

Logon failure: account currently disabled.

ERROR_ACCOUNT_DISABLED

1332

No mapping between account names and security IDs was done.

ERROR_NONE_MAPPED

1333

Too many local user identifiers (LUIDs) were requested at one time.

ERROR_TOO_MANY_LUIDS_REQUESTED

1334

No more local user identifiers (LUIDs) are available.

ERROR_LUIDS_EXHAUSTED

1335

The subauthority part of a security ID is invalid for this particular use.

ERROR_INVALID_SUB_AUTHORITY

1336

The access control list (ACL) structure is invalid.

ERROR_INVALID_ACL

1337

The security ID structure is invalid.

ERROR_INVALID_SID

1338

The security descriptor structure is invalid.

ERROR_INVALID_SECURITY_DESCR

1340

The inherited access control list (ACL) or access control entry (ACE) could not be built.

ERROR_BAD_INHERITANCE_ACL

1341

The server is currently disabled.

ERROR_SERVER_DISABLED

1342

The server is currently enabled.

ERROR_SERVER_NOT_DISABLED

1343

The value provided was an invalid value for an identifier authority.

ERROR_INVALID_ID_AUTHORITY

1344

No more memory is available for security information updates.

ERROR_ALLOTTED_SPACE_EXCEEDED

1345

The specified attributes are invalid, or incompatible with the attributes for the group as a whole.

ERROR_INVALID_GROUP_ATTRIBUTES

1346

Either a required impersonation level was not provided, or the provided impersonation level is invalid.

ERROR_BAD_IMPERSONATION_LEVEL

1347

Cannot open an anonymous level security token.

ERROR_CANT_OPEN_ANONYMOUS

1348

The validation information class requested was invalid.

ERROR_BAD_VALIDATION_CLASS

1349

The type of the token is inappropriate for its attempted use.

ERROR_BAD_TOKEN_TYPE

1350

Unable to perform a security operation on an object that has no associated security.

ERROR_NO_SECURITY_ON_OBJECT

1351

Indicates a Windows NT Server could not be contacted or that objects within the domain are protected such that necessary information could not be retrieved.

ERROR_CANT_ACCESS_DOMAIN_INFO

1352

The security account manager (SAM) or local security authority (LSA) server was in the wrong state to perform the security operation.

ERROR_INVALID_SERVER_STATE

1353

The domain was in the wrong state to perform the security operation.

ERROR_INVALID_DOMAIN_STATE

1354

This operation is only allowed for the Primary Domain Controller of the domain.

ERROR_INVALID_DOMAIN_ROLE

1355

The specified domain did not exist.

ERROR_NO_SUCH_DOMAIN

1356

The specified domain already exists.

ERROR_DOMAIN_EXISTS

1357

An attempt was made to exceed the limit on the number of domains per server.

ERROR_DOMAIN_LIMIT_EXCEEDED

1358

Unable to complete the requested operation because of either a catastrophic media failure or a data structure corruption on the disk.

ERROR_INTERNAL_DB_CORRUPTION

1359

The security account database contains an internal inconsistency.

ERROR_INTERNAL_ERROR

1360

Generic access types were contained in an access mask which should already be mapped to nongeneric types.

ERROR_GENERIC_NOT_MAPPED

1361

A security descriptor is not in the right format (absolute or self-relative).

ERROR_BAD_DESCRIPTOR_FORMAT

1362

The requested action is restricted for use by logon processes only. The calling process has not registered as a logon process.

ERROR_NOT_LOGON_PROCESS

1363

Cannot start a new logon session with an ID that is already in use.

ERROR_LOGON_SESSION_EXISTS

1364

A specified authentication package is unknown.

ERROR_NO_SUCH_PACKAGE

1365

The logon session is not in a state that is consistent with the requested operation.

ERROR_BAD_LOGON_SESSION_STATE

1366

The logon session ID is already in use.

ERROR_LOGON_SESSION_COLLISION

1367

A logon request contained an invalid logon type value.

ERROR_INVALID_LOGON_TYPE

1368

Unable to impersonate using a named pipe until data has been read from that pipe.

ERROR_CANNOT_IMPERSONATE

1369

The transaction state of a registry subtree is incompatible with the requested operation.

ERROR_RXACT_INVALID_STATE

1370

An internal security database corruption has been encountered.

ERROR_RXACT_COMMIT_FAILURE

1371

Cannot perform this operation on built-in accounts.

ERROR_SPECIAL_ACCOUNT

1372

Cannot perform this operation on this built-in special group.

ERROR_SPECIAL_GROUP

1373

Cannot perform this operation on this built-in special user.

ERROR_SPECIAL_USER

1374

The user cannot be removed from a group because the group is currently the user's primary group.

ERROR_MEMBERS_PRIMARY_GROUP

1375

The token is already in use as a primary token.

ERROR_TOKEN_ALREADY_IN_USE

1376

The specified local group does not exist.

ERROR_NO_SUCH_ALIAS

1377

The specified account name is not a member of the local group.

ERROR_MEMBER_NOT_IN_ALIAS

1378

The specified account name is already a member of the local group.

ERROR_MEMBER_IN_ALIAS

1379

The specified local group already exists.

ERROR_ALIAS_EXISTS

1380

Logon failure: the user has not been granted the requested logon type at this computer.

ERROR_LOGON_NOT_GRANTED

1381

The maximum number of secrets that may be stored in a single system has been exceeded.

ERROR_TOO_MANY_SECRETS

1382

The length of a secret exceeds the maximum length allowed.

ERROR_SECRET_TOO_LONG

1383

The local security authority database contains an internal inconsistency.

ERROR_INTERNAL_DB_ERROR

1384

During a logon attempt, the user's security context accumulated too many security IDs.

ERROR_TOO_MANY_CONTEXT_IDS

1385

Logon failure: the user has not been granted the requested logon type at this computer.

ERROR_LOGON_TYPE_NOT_GRANTED

1386

A cross-encrypted password is necessary to change a user password.

ERROR_NT_CROSS_ENCRYPTION_REQUIRED

1387

A new member could not be added to a local group because the member does not exist.

ERROR_NO_SUCH_MEMBER

1388

A new member could not be added to a local group because the member has the wrong account type.

ERROR_INVALID_MEMBER

1389

Too many security IDs have been specified.

ERROR_TOO_MANY_SIDS

1390

A cross-encrypted password is necessary to change this user password.

ERROR_LM_CROSS_ENCRYPTION_REQUIRED

1391

Indicates an ACL contains no inheritable components.

ERROR_NO_INHERITANCE

1392

The file or directory is corrupted and non-readable.

ERROR_FILE_CORRUPT

1393

The disk structure is corrupted and non-readable.

ERROR_DISK_CORRUPT

1394

There is no user session key for the specified logon session.

ERROR_NO_USER_SESSION_KEY

1395

The service being accessed is licensed for a particular number of connections. No more connections can be made to the service at this time because there are already as many connections as the service can accept.

ERROR_LICENSE_QUOTA_EXCEEDED

1400

Invalid window handle.

ERROR_INVALID_WINDOW_HANDLE

1401

Invalid menu handle.

ERROR_INVALID_MENU_HANDLE

1402

Invalid cursor handle.

ERROR_INVALID_CURSOR_HANDLE

1403

Invalid accelerator table handle.

ERROR_INVALID_ACCEL_HANDLE

1404

Invalid hook handle.

ERROR_INVALID_HOOK_HANDLE

1405

Invalid handle to a multiple-window position structure.

ERROR_INVALID_DWP_HANDLE

1406

Cannot create a top-level child window.

ERROR_TLW_WITH_WSCHILD

1407

Cannot find window class.

ERROR_CANNOT_FIND_WND_CLASS

1408

Invalid window, it belongs to another thread.

ERROR_WINDOW_OF_OTHER_THREAD

1409

Hot key is already registered.

ERROR_HOTKEY_ALREADY_REGISTERED

1410

Class already exists.

ERROR_CLASS_ALREADY_EXISTS

1411

Class does not exist.

ERROR_CLASS_DOES_NOT_EXIST

1412

Class still has open windows.

ERROR_CLASS_HAS_WINDOWS

1413

Invalid index.

ERROR_INVALID_INDEX

1414

Invalid icon handle.

ERROR_INVALID_ICON_HANDLE

1415

Using private DIALOG window words.

ERROR_PRIVATE_DIALOG_INDEX

1416

The list box identifier was not found.

ERROR_LISTBOX_ID_NOT_FOUND

1417

No wildcards were found.

ERROR_NO_WILDCARD_CHARACTERS

1418

Thread does not have a clipboard open.

ERROR_CLIPBOARD_NOT_OPEN

1419

Hot key is not registered.

ERROR_HOTKEY_NOT_REGISTERED

1420

The window is not a valid dialog window.

ERROR_WINDOW_NOT_DIALOG

1421

Control ID not found.

ERROR_CONTROL_ID_NOT_FOUND

1422

Invalid message for a combo box because it does not have an edit control.

ERROR_INVALID_COMBOBOX_MESSAGE

1423

The window is not a combo box.

ERROR_WINDOW_NOT_COMBOBOX

1424

Height must be less than 256.

ERROR_INVALID_EDIT_HEIGHT

1425

Invalid device context (DC) handle.

ERROR_DC_NOT_FOUND

1426

Invalid hook procedure type.

ERROR_INVALID_HOOK_FILTER

1427

Invalid hook procedure.

ERROR_INVALID_FILTER_PROC

1428

Cannot set nonlocal hook without a module handle.

ERROR_HOOK_NEEDS_HMOD

1429

This hook procedure can only be set globally.

ERROR_GLOBAL_ONLY_HOOK

1430

The journal hook procedure is already installed.

ERROR_JOURNAL_HOOK_SET

1431

The hook procedure is not installed.

ERROR_HOOK_NOT_INSTALLED

1432

Invalid message for single-selection list box.

ERROR_INVALID_LB_MESSAGE

1433

LB_SETCOUNT sent to non-lazy list box.

ERROR_SETCOUNT_ON_BAD_LB

1434

This list box does not support tab stops.

ERROR_LB_WITHOUT_TABSTOPS

1435

Cannot destroy object created by another thread.

ERROR_DESTROY_OBJECT_OF_OTHER_THREAD

1436

Child windows cannot have menus.

ERROR_CHILD_WINDOW_MENU

1437

The window does not have a system menu.

ERROR_NO_SYSTEM_MENU

1438

Invalid message box style.

ERROR_INVALID_MSGBOX_STYLE

1439

Invalid system-wide (SPI_*) parameter.

ERROR_INVALID_SPI_VALUE

1440

Screen already locked.

ERROR_SCREEN_ALREADY_LOCKED

1441

All handles to windows in a multiple-window position structure must have the same parent.

ERROR_HWNDS_HAVE_DIFF_PARENT

1442

The window is not a child window.

ERROR_NOT_CHILD_WINDOW

1443

Invalid GW_* command.

ERROR_INVALID_GW_COMMAND

1444

Invalid thread identifier.

ERROR_INVALID_THREAD_ID

1445

Cannot process a message from a window that is not a multiple document interface (MDI) window.

ERROR_NON_MDICHILD_WINDOW

1446

Popup menu already active.

ERROR_POPUP_ALREADY_ACTIVE

1447

The window does not have scroll bars.

ERROR_NO_SCROLLBARS

1448

Scroll bar range cannot be greater than 0x7FFF.

ERROR_INVALID_SCROLLBAR_RANGE

1449

Cannot show or remove the window in the way specified.

ERROR_INVALID_SHOWWIN_COMMAND

1450

Insufficient system resources exist to complete the requested service.

ERROR_NO_SYSTEM_RESOURCES

1451

Insufficient system resources exist to complete the requested service.

ERROR_NONPAGED_SYSTEM_RESOURCES

1452

Insufficient system resources exist to complete the requested service.

ERROR_PAGED_SYSTEM_RESOURCES

1453

Insufficient quota to complete the requested service.

ERROR_WORKING_SET_QUOTA

1454

Insufficient quota to complete the requested service.

ERROR_PAGEFILE_QUOTA

1455

The paging file is too small for this operation to complete.

ERROR_COMMITMENT_LIMIT

1456

A menu item was not found.

ERROR_MENU_ITEM_NOT_FOUND

1457

Invalid keyboard layout handle.

ERROR_INVALID_KEYBOARD_HANDLE

1458

Hook type not allowed.

ERROR_HOOK_TYPE_NOT_ALLOWED

1459

This operation requires an interactive window station.

ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION

1460

This operation returned because the time-out period expired.

ERROR_TIMEOUT

1500

The event log file is corrupted.

ERROR_EVENTLOG_FILE_CORRUPT

1501

No event log file could be opened, so the event logging service did not start.

ERROR_EVENTLOG_CANT_START

1502

The event log file is full.

ERROR_LOG_FILE_FULL

1503

The event log file has changed between read operations.

ERROR_EVENTLOG_FILE_CHANGED

1700

The string binding is invalid.

RPC_S_INVALID_STRING_BINDING

1701

The binding handle is not the correct type.

RPC_S_WRONG_KIND_OF_BINDING

1702

The binding handle is invalid.

RPC_S_INVALID_BINDING

1703

The RPC protocol sequence is not supported.

RPC_S_PROTSEQ_NOT_SUPPORTED

1704

The RPC protocol sequence is invalid.

RPC_S_INVALID_RPC_PROTSEQ

1705

The string universal unique identifier (UUID) is invalid.

RPC_S_INVALID_STRING_UUID

1706

The endpoint format is invalid.

RPC_S_INVALID_ENDPOINT_FORMAT

1707

The network address is invalid.

RPC_S_INVALID_NET_ADDR

1708

No endpoint was found.

RPC_S_NO_ENDPOINT_FOUND

1709

The time-out value is invalid.

RPC_S_INVALID_TIMEOUT

1710

The object universal unique identifier (UUID) was not found.

RPC_S_OBJECT_NOT_FOUND

1711

The object universal unique identifier (UUID) has already been registered.

RPC_S_ALREADY_REGISTERED

1712

The type universal unique identifier (UUID) has already been registered.

RPC_S_TYPE_ALREADY_REGISTERED

1713

The RPC server is already listening.

RPC_S_ALREADY_LISTENING

1714

No protocol sequences have been registered.

RPC_S_NO_PROTSEQS_REGISTERED

1715

The RPC server is not listening.

RPC_S_NOT_LISTENING

1716

The manager type is unknown.

RPC_S_UNKNOWN_MGR_TYPE

1717

The interface is unknown.

RPC_S_UNKNOWN_IF

1718

There are no bindings.

RPC_S_NO_BINDINGS

1719

There are no protocol sequences.

RPC_S_NO_PROTSEQS

1720

The endpoint cannot be created.

RPC_S_CANT_CREATE_ENDPOINT

1721

Not enough resources are available to complete this operation.

RPC_S_OUT_OF_RESOURCES

1722

The RPC server is unavailable.

RPC_S_SERVER_UNAVAILABLE

1723

The RPC server is too busy to complete this operation.

RPC_S_SERVER_TOO_BUSY

1724

The network options are invalid.

RPC_S_INVALID_NETWORK_OPTIONS

1725

There is not a remote procedure call active in this thread.

RPC_S_NO_CALL_ACTIVE

1726

The remote procedure call failed.

RPC_S_CALL_FAILED

1727

The remote procedure call failed and did not execute.

RPC_S_CALL_FAILED_DNE

1728

A remote procedure call (RPC) protocol error occurred.

RPC_S_PROTOCOL_ERROR

1730

The transfer syntax is not supported by the RPC server.

RPC_S_UNSUPPORTED_TRANS_SYN

1732

The universal unique identifier (UUID) type is not supported.

RPC_S_UNSUPPORTED_TYPE

1733

The tag is invalid.

RPC_S_INVALID_TAG

1734

The array bounds are invalid.

RPC_S_INVALID_BOUND

1735

The binding does not contain an entry name.

RPC_S_NO_ENTRY_NAME

1736

The name syntax is invalid.

RPC_S_INVALID_NAME_SYNTAX

1737

The name syntax is not supported.

RPC_S_UNSUPPORTED_NAME_SYNTAX

1739

No network address is available to use to construct a universal unique identifier (UUID).

RPC_S_UUID_NO_ADDRESS

1740

The endpoint is a duplicate.

RPC_S_DUPLICATE_ENDPOINT

1741

The authentication type is unknown.

RPC_S_UNKNOWN_AUTHN_TYPE

1742

The maximum number of calls is too small.

RPC_S_MAX_CALLS_TOO_SMALL

1743

The string is too long.

RPC_S_STRING_TOO_LONG

1744

The RPC protocol sequence was not found.

RPC_S_PROTSEQ_NOT_FOUND

1745

The procedure number is out of range.

RPC_S_PROCNUM_OUT_OF_RANGE

1746

The binding does not contain any authentication information.

RPC_S_BINDING_HAS_NO_AUTH

1747

The authentication service is unknown.

RPC_S_UNKNOWN_AUTHN_SERVICE

1748

The authentication level is unknown.

RPC_S_UNKNOWN_AUTHN_LEVEL

1749

The security context is invalid.

RPC_S_INVALID_AUTH_IDENTITY

1750

The authorization service is unknown.

RPC_S_UNKNOWN_AUTHZ_SERVICE

1751

The entry is invalid.

EPT_S_INVALID_ENTRY

1752

The server endpoint cannot perform the operation.

EPT_S_CANT_PERFORM_OP

1753

There are no more endpoints available from the endpoint mapper.

EPT_S_NOT_REGISTERED

1754

No interfaces have been exported.

RPC_S_NOTHING_TO_EXPORT

1755

The entry name is incomplete.

RPC_S_INCOMPLETE_NAME

1756

The version option is invalid.

RPC_S_INVALID_VERS_OPTION

1757

There are no more members.

RPC_S_NO_MORE_MEMBERS

1758

There is nothing to unexport.

RPC_S_NOT_ALL_OBJS_UNEXPORTED

1759

The interface was not found.

RPC_S_INTERFACE_NOT_FOUND

1760

The entry already exists.

RPC_S_ENTRY_ALREADY_EXISTS

1761

The entry is not found.

RPC_S_ENTRY_NOT_FOUND

1762

The name service is unavailable.

RPC_S_NAME_SERVICE_UNAVAILABLE

1763

The network address family is invalid.

RPC_S_INVALID_NAF_ID

1764

The requested operation is not supported.

RPC_S_CANNOT_SUPPORT

1765

No security context is available to allow impersonation.

RPC_S_NO_CONTEXT_AVAILABLE

1766

An internal error occurred in a remote procedure call (RPC).

RPC_S_INTERNAL_ERROR

1767

The RPC server attempted an integer division by zero.

RPC_S_ZERO_DIVIDE

1768

An addressing error occurred in the RPC server.

RPC_S_ADDRESS_ERROR

1769

A floating-point operation at the RPC server caused a division by zero.

RPC_S_FP_DIV_ZERO

1770

A floating-point underflow occurred at the RPC server.

RPC_S_FP_UNDERFLOW

1771

A floating-point overflow occurred at the RPC server.

RPC_S_FP_OVERFLOW

1772

The list of RPC servers available for the binding of auto handles has been exhausted.

RPC_X_NO_MORE_ENTRIES

1773

Unable to open the character translation table file.

RPC_X_SS_CHAR_TRANS_OPEN_FAIL

1774

The file containing the character translation table has fewer than 512 bytes.

RPC_X_SS_CHAR_TRANS_SHORT_FILE

1775

A null context handle was passed from the client to the host during a remote procedure call.

RPC_X_SS_IN_NULL_CONTEXT

1777

The context handle changed during a remote procedure call.

RPC_X_SS_CONTEXT_DAMAGED

1778

The binding handles passed to a remote procedure call do not match.

RPC_X_SS_HANDLES_MISMATCH

1779

The stub is unable to get the remote procedure call handle.

RPC_X_SS_CANNOT_GET_CALL_HANDLE

1780

A null reference pointer was passed to the stub.

RPC_X_NULL_REF_POINTER

1781

The enumeration value is out of range.

RPC_X_ENUM_VALUE_OUT_OF_RANGE

1782

The byte count is too small.

RPC_X_BYTE_COUNT_TOO_SMALL

1783

The stub received bad data.

RPC_X_BAD_STUB_DATA

1784

The supplied user buffer is not valid for the requested operation.

ERROR_INVALID_USER_BUFFER

1785

The disk media is not recognized. It may not be formatted.

ERROR_UNRECOGNIZED_MEDIA

1786

The workstation does not have a trust secret.

ERROR_NO_TRUST_LSA_SECRET

1787

The SAM database on the Windows NT Server does not have a computer account for this workstation trust relationship.

ERROR_NO_TRUST_SAM_ACCOUNT

1788

The trust relationship between the primary domain and the trusted domain failed.

ERROR_TRUSTED_DOMAIN_FAILURE

1789

The trust relationship between this workstation and the primary domain failed.

ERROR_TRUSTED_RELATIONSHIP_FAILURE

1790

The network logon failed.

ERROR_TRUST_FAILURE

1791

A remote procedure call is already in progress for this thread.

RPC_S_CALL_IN_PROGRESS

1792

An attempt was made to logon, but the network logon service was not started.

ERROR_NETLOGON_NOT_STARTED

1793

The user's account has expired.

ERROR_ACCOUNT_EXPIRED

1794

The redirector is in use and cannot be unloaded.

ERROR_REDIRECTOR_HAS_OPEN_HANDLES

1795

The specified printer driver is already installed.

ERROR_PRINTER_DRIVER_ALREADY_INSTALLED

1796

The specified port is unknown.

ERROR_UNKNOWN_PORT

1797

The printer driver is unknown.

ERROR_UNKNOWN_PRINTER_DRIVER

1798

The print processor is unknown.

ERROR_UNKNOWN_PRINTPROCESSOR

1799

The specified separator file is invalid.

ERROR_INVALID_SEPARATOR_FILE

1800

The specified priority is invalid.

ERROR_INVALID_PRIORITY

1801

The printer name is invalid.

ERROR_INVALID_PRINTER_NAME

1802

The printer already exists.

ERROR_PRINTER_ALREADY_EXISTS

1803

The printer command is invalid.

ERROR_INVALID_PRINTER_COMMAND

1804

The specified data type is invalid.

ERROR_INVALID_DATATYPE

1805

The environment specified is invalid.

ERROR_INVALID_ENVIRONMENT

1806

There are no more bindings.

RPC_S_NO_MORE_BINDINGS

1807

The account used is an interdomain trust account. Use your global user account or local user account to access this server.

ERROR_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT

1808

The account used is a computer account. Use your global user account or local user account to access this server.

ERROR_NOLOGON_WORKSTATION_TRUST_ACCOUNT

1809

The account used is a server trust account. Use your global user account or local user account to access this server.

ERROR_NOLOGON_SERVER_TRUST_ACCOUNT

1810

The name or security ID (SID) of the domain specified is inconsistent with the trust information for that domain.

ERROR_DOMAIN_TRUST_INCONSISTENT

1811

The server is in use and cannot be unloaded.

ERROR_SERVER_HAS_OPEN_HANDLES

1812

The specified image file did not contain a resource section.

ERROR_RESOURCE_DATA_NOT_FOUND

1813

The specified resource type cannot be found in the image file.

ERROR_RESOURCE_TYPE_NOT_FOUND

1814

The specified resource name cannot be found in the image file.

ERROR_RESOURCE_NAME_NOT_FOUND

1815

The specified resource language ID cannot be found in the image file.

ERROR_RESOURCE_LANG_NOT_FOUND

1816

Not enough quota is available to process this command.

ERROR_NOT_ENOUGH_QUOTA

1817

No interfaces have been registered.

RPC_S_NO_INTERFACES

1818

The server was altered while processing this call.

RPC_S_CALL_CANCELLED

1819

The binding handle does not contain all required information.

RPC_S_BINDING_INCOMPLETE

1820

Communications failure.

RPC_S_COMM_FAILURE

1821

The requested authentication level is not supported.

RPC_S_UNSUPPORTED_AUTHN_LEVEL

1822

No principal name registered.

RPC_S_NO_PRINC_NAME

1823

The error specified is not a valid Windows NT RPC error code.

RPC_S_NOT_RPC_ERROR

1824

A UUID that is valid only on this computer has been allocated.

RPC_S_UUID_LOCAL_ONLY

1825

A security package specific error occurred.

RPC_S_SEC_PKG_ERROR

1826

Thread is not canceled.

RPC_S_NOT_CANCELLED

1827

Invalid operation on the encoding/decoding handle.

RPC_X_INVALID_ES_ACTION

1828

Incompatible version of the serializing package.

RPC_X_WRONG_ES_VERSION

1829

Incompatible version of the RPC stub.

RPC_X_WRONG_STUB_VERSION

1830

The idl pipe object is invalid or corrupted.

RPC_X_INVALID_PIPE_OBJECT

1831

The operation is invalid for a given idl pipe object.

RPC_X_INVALID_PIPE_OPERATION

1832

The idl pipe version is not supported.

RPC_X_WRONG_PIPE_VERSION

1898

The group member was not found.

RPC_S_GROUP_MEMBER_NOT_FOUND

1899

The endpoint mapper database could not be created.

EPT_S_CANT_CREATE

1900

The object universal unique identifier (UUID) is the nil UUID.

RPC_S_INVALID_OBJECT

1901

The specified time is invalid.

ERROR_INVALID_TIME

1902

The specified form name is invalid.

ERROR_INVALID_FORM_NAME

1903

The specified form size is invalid.

ERROR_INVALID_FORM_SIZE

1904

The specified printer handle is already being waited on

ERROR_ALREADY_WAITING

1905

The specified printer has been deleted.

ERROR_PRINTER_DELETED

1906

The state of the printer is invalid.

ERROR_INVALID_PRINTER_STATE

1907

The user must change his password before he logs on the first time.

ERROR_PASSWORD_MUST_CHANGE

1908

Could not find the domain controller for this domain.

ERROR_DOMAIN_CONTROLLER_NOT_FOUND

1909

The referenced account is currently locked out and may not be logged on to.

ERROR_ACCOUNT_LOCKED_OUT

1910

The object exporter specified was not found.

OR_INVALID_OXID

1911

The object specified was not found.

OR_INVALID_OID

1912

The object resolver set specified was not found.

OR_INVALID_SET

1913

Some data remains to be sent in the request buffer.

RPC_S_SEND_INCOMPLETE

2000

The pixel format is invalid.

ERROR_INVALID_PIXEL_FORMAT

2001

The specified driver is invalid.

ERROR_BAD_DRIVER

2002

The window style or class attribute is invalid for this operation.

ERROR_INVALID_WINDOW_STYLE

2003

The requested metafile operation is not supported.

ERROR_METAFILE_NOT_SUPPORTED

2004

The requested transformation operation is not supported.

ERROR_TRANSFORM_NOT_SUPPORTED

2005

The requested clipping operation is not supported.

ERROR_CLIPPING_NOT_SUPPORTED

2202

The specified user name is invalid.

ERROR_BAD_USERNAME

2250

This network connection does not exist.

ERROR_NOT_CONNECTED

2401

This network connection has files open or requests pending.

ERROR_OPEN_FILES

2402

Active connections still exist.

ERROR_ACTIVE_CONNECTIONS

2404

The device is in use by an active process and cannot be disconnected.

ERROR_DEVICE_IN_USE

3000

The specified print monitor is unknown.

ERROR_UNKNOWN_PRINT_MONITOR

3001

The specified printer driver is currently in use.

ERROR_PRINTER_DRIVER_IN_USE

3002

The spool file was not found.

ERROR_SPOOL_FILE_NOT_FOUND

3003

A StartDocPrinter call was not issued.

ERROR_SPL_NO_STARTDOC

3004

An AddJob call was not issued.

ERROR_SPL_NO_ADDJOB

3005

The specified print processor has already been installed.

ERROR_PRINT_PROCESSOR_ALREADY_INSTALLED

3006

The specified print monitor has already been installed.

ERROR_PRINT_MONITOR_ALREADY_INSTALLED

3007

The specified print monitor does not have the required functions.

ERROR_INVALID_PRINT_MONITOR

3008

The specified print monitor is currently in use.

ERROR_PRINT_MONITOR_IN_USE

3009

The requested operation is not allowed when there are jobs queued to the printer.

ERROR_PRINTER_HAS_JOBS_QUEUED

3010

The requested operation is successful. Changes will not be effective until the system is rebooted.

ERROR_SUCCESS_REBOOT_REQUIRED

3011

The requested operation is successful. Changes will not be effective until the service is restarted.

ERROR_SUCCESS_RESTART_REQUIRED

4000

WINS encountered an error while processing the command.

ERROR_WINS_INTERNAL

4001

The local WINS can not be deleted.

ERROR_CAN_NOT_DEL_LOCAL_WINS

4002

The importation from the file failed.

ERROR_STATIC_INIT

4003

The backup failed. Was a full backup done before?

ERROR_INC_BACKUP

4004

The backup failed. Check the directory to which you are backing the database.

ERROR_FULL_BACKUP

4005

The name does not exist in the WINS database.

ERROR_REC_NON_EXISTENT

4006

Replication with a nonconfigured partner is not allowed.

ERROR_RPL_NOT_ALLOWED

6118

The list of servers for this workgroup is not currently available

ERROR_NO_BROWSER_SERVERS_FOUND


THE INFORMATION PROVIDED IN THE SOFTWARE TECHNOLOGY, INC. KNOWLEDGE BASE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. SOFTWARE TECHNOLOGY, INC. DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SOFTWARE TECHNOLOGY, INC. OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, EVEN IF SOFTWARE TECHNOLOGY, INC. OR ITS SUPPLIERS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES SO THE FOREGOING LIMITATION MAY NOT APPLY.

1999-2005 Software Technology, Inc.   All rights reserved. Terms of Use
The maker of Tabs3 and PracticeMaster
e-Mail Suggestions for the Knowledge Base to: kb@Tabs3.com
Technical Support via e-mail is not available.
Knowledge Base:   http://support.Tabs3.com
Web Site:   http://www.Tabs3.com

 

标签:软件开发 | 浏览数(524) | 评论数(0) | 2006-10-12

关于FLOAT类型结尾的零的问题

    在C++中,将float类型的数字转化为字符串一般用sprintf或strcpy等等的方法,这其中涉及到FLOAT类型的格式化问题。

    一般情况下,使用%f可以把FLOAT类型转化为字符串,不过这个转化是有缺点的,缺点就是这个数字是有固定的默认小数位数的,这个默认位数是6,即当你把FLOAT型数字1用%f格式化时,得到的字符串是1.000000。一定让人很不舒服。一般的解决方法是使用位数限定标记。比如当你想保留两位小数时使用%.2f,那麽得到的字符串就是1.00。是不是稍微好一点?但是仍然让人不满意,难道就没有方法能够去除小数结尾多余的零吗?

    有的。方法就是%g,使用这种格式化方法可以得到人们所熟悉的小数,不必再为多余的小数后的零操心了。而且%g的格式化方法和%f有相同之处,即在%f上使用的格式限定标记,在%g上同样适用。比如我们用%.4f来格式化小数1.01000,得到的字符串是1.0100,同样,使用%.4g来格式化1.01000得到的字符串是1.01。是不是很方便呢?

 

标签:软件开发 | 浏览数(525) | 评论数(0) | 2006-10-08

Effective STL条款31 理解你的排序操作

作者: winter

排序一直是数据结构中的常用算法,STL提供的排序算法非常丰富,如何有效使用就值得探讨。在网上没有找到条款31的翻译,于是我自己翻译了。--Winter

如何进行排序?让我数数有几种方法

一旦程序员需对容器元素进行排序,sort算法马上就会出现在他的脑海(可能有些程序员会想到qsort,但详细阅读条款46后,他们会放弃使用qsort的想法,转而使用sort算法)。

sort是一个非常优秀的算法,但并当你并不真正需要它的时候,其实就是一种浪费。有时你并不需要一个完整的排序(简称为全排序)。例如,你现有一个包含Widget对象(Widget意为“小挂件”)的vector容器,并且你想把其中质量最好的20个Widget送给你最好的客户,那么你需做的只是找出这20个质量最好的Widget元素,剩下的并不需关心它们的顺序。这时你需要的是部分排序(相对于全排序),恰好在算法中就有一个名副其实的部分排序函数函数:partial_sort:

 bool qualityCompare(const Widget& lhs, const Widget& rhs)
{
            // lhs的质量不比rhs的质量差时返回true,否则返回false
}

partial_sort (widgets.begin(),                         // 把质量最好的20元素
                    widgets.begin() + 20,                 // 顺序放入widgets容器中
                    widgets.end(),                            
                    qualityCompare);
                                                                // 使用 widgets...

通过调用partial_sort,容器中开始的20个元素就是你需要的质量最好的20个Widget,并按顺序排列,质量排第一的就是widgets[0], 紧接着就是widgets[1],依次类推。这样你就可以把质量第一的Widget送给你最好的顾客,质量第二的Widget就可以送给下一个顾客,很方便吧,更方便的还在后面呢。

如果你只是想把这20个质量最好的Widget礼物送给你最好的20位顾客,而不需要给他们一一对应,partial_sort在这里就有些显得大材小用了。因为在这里,你只需要找出这20个元素,而不需要对它们本身进行排序。这时你需要的不是partial_sort,而是nth_element。

nth_element排序算法只是对一个区间进行排序,一直到你指定的第n个位置上放上正确的元素为止,也就是说,和你进行全排序和nth_element排序相比,其共同点就是第n个位置是同一个元素。当nth_element函数运行后,在全排序中应该在位置n之后的元素不会出现在n的前面,应该在位置n前面的元素也不会出现在n的后面。听起来有些费解,主要是我不得不谨慎地使用我的语言来描述nth_element的功能。别着急,呆会儿我会给你解释为什么,现在先让我们来看看nth_element是如何让质量最好的20个widget礼物排在vector容器的前面的:

nth_element (widgets.begin(),             // 把质量最好的20元素放在
                      widgets.begin() + 20,     // widgets容器的前面,
                      widgets.end(),                // 但并不关心这20个元素
                      qualityCompare);            //本身内部的顺序

你可以看出,调用nth_element函数和调用partial_sort函数在本质上没有区别,唯一的不同在于partial_sort把前20个元素还进行排列了,而nth_element并不关系他们内部的顺序。两个算法都实现了同样的功能:把质量最好的20个元素放在vector容器的开始部分。

这样引起了一个重要的问题:要是质量一样的元素,排序算法将会如何处理呢?假设有12个元素的质量都为1(最好等级),15个元素的质量等级为2(质量次之),如果要选择20个最好的Widget,则先选12个质量为1的元素,然后从15个中选出8个质量为2的元素。到底nth_element和partial_sort如何从15个中选出8个,依据何在?换句话说,当多个元素有同样的比较值时,排序算法如何决定谁先谁后?

对于partial_sort和nth_element算法来说,你无法控制它们,对于比较值相同的元素它们想怎么排就怎么排(查看条款19,了解两个值相等的定义)。在我们上面的例子中,面对需要从15个等级为2的元素选出8个增加到top 20中去,他们会任意选取。这样做也有它的道理:如果你要求得到20个质量最好的Widget,同时有些Widget的质量又一样,当你得到20个元素至少不比剩下的那些质量差,这已经达到你的要求,你就不能抱怨什么了。

假如对于全排序,你倒是可以得到更多的控制权。一些排序算法是“稳定的”(stable),在一个“稳定”的排序算法中,如果两个元素有相同的值,它们的相对位置在排序后也会保持不变。例如:如果在未排序时Widget A在Widget B之前,而且都有相同的质量等级,那么“稳定”排序算法就可以保证在排序之后,Widget A仍然在Widget B之前。而非“稳定”排序算法就不能保证这一点。

partial_sort和nth_element都不是“稳定”排序算法,真正的“稳定”排序算法是stable_sort,从名字上看就知道它是“稳定”的。如果你在排序的时候需要保证相同元素的相对位置,你最好使用stable_sort,在STL中没有为partial_sort和nth_element算法提供对应的“稳定”版本。

说到nth_element,名字确实很怪,但是功能却是不少,除了让你找到无关顺序的top n个元素外,它还能找到某个范围的中值,或者找到在某个特定百分点的值。

    vector<Widget>::iterator begin(widgets.begin());     // widgets的第一个
    vector<Widget>::iterator end(widgets.end());           //和最后一个迭代器
                                                                                      //
    vector<Widget>::iterator goalPosition;                      // 需要定位的那个迭代器
    

      //以下代码用来得到质量排在中间的那个元素的迭代器
    goalPosition = begin + widgets.size() / 2;             // 要找的那个元素应该
                                                                                 //在vector的中部。
   
nth_element(begin, goalPosition, end,         // 找到容器widgets元素的中值
                        qualityCompare);                      //
                                                                       // goalPosition现在指向中值元素
   
   
//以下代码用来得到质量排在75%的元素
    vector<Widget>::size_type goalOffset =               // 计算出要找的值
                                        0.25 * widgets.size();         //离begin迭代器的距离。
                                                                                 
//  
    nth_element( begin, begin + goalOffset, end,       // 得到质量排在75%的元素
                            qualityCompare);                           //
 

                                                   // goalPosition 现在指向质量排在75%的元素。

当你需要把一个集合由无序变成有序时,可选用sort, stable_sort或partial_sort,当你只需得到top n或者在某个特定位置的元素,你就可以使用nth_element。或许有时你的需求比nth_element提供的还要少,例如:你并不需要得到质量最好的前20个Widget,而只需要识别那些质量等级为1或者等级为2的Widget。当然,你可以对整个vector按照Widget的质量等级进行全排序,然后查找到第一个质量等级低于等级2的元素。

问题就在于全排序太耗费资源,而且大部分工作都是无用功。这种情况下最好选择partition算法,partition只是给你确定一个区间,把符合特定条件的元素放到这个区间中。举例来说,要把质量等级好于等于等级2的Widget的元素放在widget容器的前端,我们可以定义一个用于识别Widget质量等级的函数:

    bool hasAcceptableQuality(const Widget& w)
    {
        //如果w的质量等于或好于2,返回true,否则返回false
    }

然后把这个判断函数传递给partion算法:
    vector<Widget>::iterator goodEnd =   // 把所有满足hasAcceptableQuality
                    partition(widgets.begin(),     // 条件的放在widgets容器的前面,
                    widgets.end(),                     // 返回第一个不满足条件的
                    hasAcceptableQuality);       //元素的位置

这样一来,所有在迭代器widgets.begin()和迭代器goodEnd之间的元素都是满足需求的元素:其质量等级好于或等于2。而在 goodEnd widgets.end() 之间的元素的质量等级都会低于质量等级2。如果你对质量等级相同元素的相对位置很关心的话,你可以选择stable_partition算法来代替partition

需要注意的是sort, stable_sort, partial_sort, nth_element算法都需要以随机迭代器(random access
iterators)为参数,因此这些算法能只能用于vector, string, deque, array等容器,对于标准的关联容器map、set、multmap、multset等,这些算法就有必要用了,这些容器本身的比较函数使得容器内所有元素一直都是有序排列的。对于容器list,看似可以用这些排序算法,其实也是不可用的(其iterator的类型并不是随机迭代器),不过在需要的时候可以使用list自带的排序函数sort(有趣的是list::sort函数和一个“稳定”排序函数的效果一样)。如果你想对一个list容器使用partial_sortnth_element,你只能间接使用。一个可选的方法是把list中的元素拷贝到带有随机迭代器的容器中,然后再使用这些算法;另一种是生成一个包含list::iterator的容器,直接对容器内的list::iterator进行排序,然后通过list::iterator得到所指的元素;第三种方法,借助一个包含iterator的有序容器,然后反复把list中的元素连接到你想要链接的位置。看见了吧,你可以选择的方式还是比较多的。

partition stable_partition函数与sortstable_sortpartial_sortnth_element不一样,要求没有那么严格,输入参数只需是双向迭代器(bidirectional iterator)。因此你可以对所有的标准序列容器使用partitionstable_partition算法。

让我们来总结一下你的排序操作:

  • 若需对vector, string, deque, array容器进行全排序,你可选择sortstable_sort

  • 若只需对vector, string, deque, array容器中取得top n的元素,部分排序partial_sort是首选.

  • 若对于vector, string, deque, array容器,你需要找到第n个位置的元素或者你需要得到top n且不关系top n中的内部顺序,nth_element是最理想的;

  • 若你需要从标准序列容器或者array中把满足某个条件或者不满足某个条件的元素分开,你最好使用partitionstable_partition

  • 若使用的list容器,你可以直接使用partitionstable_partition算法,你可以使用list::sort代替sort和stable_sort排序。若你需要得到partial_sortnth_element的排序效果,你必须间接使用。正如上面介绍的有几种方式可以选择。

另外,你可以使用标准的关联容器来保证容器中所有元素在操作过程中一直有序。你还可考虑非标准的STL容器priority_queue,它同样可以保证其元素在所有操作过程中一直有序(priority_queue在传统意义上属于STL的一部分,但根据“STL”定义,需要STL容器支持迭代器,而priority_queue并不支持迭代器,故不能能称为标准STL容器)

这时你或许会问:“性能如何?”非常好的问题。广义的讲,算法做的工作越多,花的时间越长,“稳定”性排序算法比“非稳定”性排序算法要耗时。我们可以按照其消耗的资源(时间和空间)的多少,对本文中讨论的排序算法作个排序,消耗资源越少的排在前面:

  • 1. partition

  • 2. stable_partition

  • 3. nth_element

  • 4. partial_sort

  • 5. sort

  • 6. stable_sort

选择这些算法的依据是你的需求而不是它们的性能。若你能选择一个算法恰好满足你的需求(如用部分排序代替全排序),不仅会清晰表达你的意图,而且能高效的使用STL。
 

 

标签:软件开发 | 浏览数(552) | 评论数(0) | 2006-09-25

详细解说 STL 排序(Sort)

作者Winter 一切复杂的排序操作,都可以通过STL方便实现 !

0 前言: STL,为什么你必须掌握


对于程序员来说,数据结构是必修的一门课。从查找到排序,从链表到二叉树,几乎所有的算法和原理都需要理解,理解不了也要死记硬背下来。幸运的是这些理论都已经比较成熟,算法也基本固定下来,不需要你再去花费心思去考虑其算法原理,也不用再去验证其准确性。不过,等你开始应用计算机语言来工作的时候,你会发现,面对不同的需求你需要一次又一次去用代码重复实现这些已经成熟的算法,而且会一次又一次陷入一些由于自己疏忽而产生的bug中。这时,你想找一种工具,已经帮你实现这些功能,你想怎么用就怎么用,同时不影响性能。你需要的就是STL, 标准模板库!

西方有句谚语:不要重复发明轮子!

STL几乎封装了所有的数据结构中的算法,从链表到队列,从向量到堆栈,对hash到二叉树,从搜索到排序,从增加到删除......可以说,如果你理解了STL,你会发现你已不用拘泥于算法本身,从而站在巨人的肩膀上去考虑更高级的应用。

排序是最广泛的算法之一,本文详细介绍了STL中不同排序算法的用法和区别。

1 STL提供的Sort 算法


C++之所以得到这么多人的喜欢,是因为它既具有面向对象的概念,又保持了C语言高效的特点。STL 排序算法同样需要保持高效。因此,对于不同的需求,STL提供的不同的函数,不同的函数,实现的算法又不尽相同。

1.1 所有sort算法介绍

所有的sort算法的参数都需要输入一个范围,[begin, end)。这里使用的迭代器(iterator)都需是随机迭代器(RadomAccessIterator), 也就是说可以随机访问的迭代器,如:it+n什么的。(partition 和stable_partition 除外)

如果你需要自己定义比较函数,你可以把你定义好的仿函数(functor)作为参数传入。每种算法都支持传入比较函数。以下是所有STL sort算法函数的名字列表:

函数名 功能描述
sort 对给定区间所有元素进行排序
stable_sort 对给定区间所有元素进行稳定排序
partial_sort 对给定区间所有元素部分排序
partial_sort_copy 对给定区间复制并排序
nth_element 找出给定区间的某个位置对应的元素
is_sorted 判断一个区间是否已经排好序
partition 使得符合某个条件的元素放在前面
stable_partition 相对稳定的使得符合某个条件的元素放在前面
其中nth_element 是最不易理解的,实际上,这个函数是用来找出第几个。例如:找出包含7个元素的数组中排在中间那个数的值,此时,我可能不关心前面,也不关心后面,我只关心排在第四位的元素值是多少。

1.2 sort 中的比较函数

当你需要按照某种特定方式进行排序时,你需要给sort指定比较函数,否则程序会自动提供给你一个比较函数。
vector < int > vect;
//...
sort(vect.begin(), vect.end());
//此时相当于调用
sort(vect.begin(), vect.end(), less<int>() );

上述例子中系统自己为sort提供了less仿函数。在STL中还提供了其他仿函数,以下是仿函数列表:
名称 功能描述
equal_to 相等
not_equal_to 不相等
less 小于
greater 大于
less_equal 小于等于
greater_equal 大于等于
需要注意的是,这些函数不是都能适用于你的sort算法,如何选择,决定于你的应用。另外,不能直接写入仿函数的名字,而是要写其重载的()函数:
less<int>()
greater<int>()
当你的容器中元素时一些标准类型(int float char)或者string时,你可以直接使用这些函数模板。但如果你时自己定义的类型或者你需要按照其他方式排序,你可以有两种方法来达到效果:一种是自己写比较函数。另一种是重载类型的'<'操作赋。
#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>
using namespace std;
class myclass {
public:
myclass(int a, int b):first(a), second(b){}
int first;
int second;
bool operator < (const myclass &m)const {
return first < m.first;
}
};
bool less_second(const myclass & m1, const myclass & m2) {
return m1.second < m2.second;
}
int main() {
vector< myclass > vect;
for(int i = 0 ; i < 10 ; i ++){
myclass my(10-i, i*3);
vect.push_back(my);
}
for(int i = 0 ; i < vect.size(); i ++)
cout<<"("<<vect[i].first<<","<<vect[i].second<<")"n";
sort(vect.begin(), vect.end());
cout<<"after sorted by first:"<<endl;
for(int i = 0 ; i < vect.size(); i ++)
cout<<"("<<vect[i].first<<","<<vect[i].second<<")"n";
cout<<"after sorted by second:"<<endl;
sort(vect.begin(), vect.end(), less_second);
for(int i = 0 ; i < vect.size(); i ++)
cout<<"("<<vect[i].first<<","<<vect[i].second<<")"n";
return 0 ;
}

知道其输出结果是什么了吧:
(10,0)
(9,3)
(8,6)
(7,9)
(6,12)
(5,15)
(4,18)
(3,21)
(2,24)
(1,27)
after sorted by first:
(1,27)
(2,24)
(3,21)
(4,18)
(5,15)
(6,12)
(7,9)
(8,6)
(9,3)
(10,0)
after sorted by second:
(10,0)
(9,3)
(8,6)
(7,9)
(6,12)
(5,15)
(4,18)
(3,21)
(2,24)
(1,27)

1.3 sort 的稳定性

你发现有sort和stable_sort,还有 partition 和stable_partition, 感到奇怪吧。其中的区别是,带有stable的函数可保证相等元素的原本相对次序在排序后保持不变。或许你会问,既然相等,你还管他相对位置呢,也分不清楚谁是谁了?这里需要弄清楚一个问题,这里的相等,是指你提供的函数表示两个元素相等,并不一定是一摸一样的元素。

例如,如果你写一个比较函数:

bool less_len(const string &str1, const string &str2)
{
return str1.length() < str2.length();
}

此时,"apple" 和 "winter" 就是相等的,如果在"apple" 出现在"winter"前面,用带stable的函数排序后,他们的次序一定不变,如果你使用的是不带"stable"的函数排序,那么排序完后,"Winter"有可能在"apple"的前面。

 

1.4 全排序

全排序即把所给定范围所有的元素按照大小关系顺序排列。用于全排序的函数有

 

template <class RandomAccessIterator>
void sort(RandomAccessIterator first, RandomAccessIterator last);
template <class RandomAccessIterator, class StrictWeakOrdering>
void sort(RandomAccessIterator first, RandomAccessIterator last,
StrictWeakOrdering comp);
template <class RandomAccessIterator>
void stable_sort(RandomAccessIterator first, RandomAccessIterator last);
template <class RandomAccessIterator, class StrictWeakOrdering>
void stable_sort(RandomAccessIterator first, RandomAccessIterator last,
StrictWeakOrdering comp);

在第1,3种形式中,sort 和 stable_sort都没有指定比较函数,系统会默认使用operator< 对区间[first,last)内的所有元素进行排序, 因此,如果你使用的类型义军已经重载了operator<函数,那么你可以省心了。第2, 4种形式,你可以随意指定比较函数,应用更为灵活一些。来看看实际应用:

班上有10个学生,我想知道他们的成绩排名。

#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>
#include <string>
using namespace std;
class student{
public:
student(const string &a, int b):name(a), score(b){}
string name;
int score;
bool operator < (const student &m)const {
return score< m.score;
}
};
int main() {
vector< student> vect;
student st1("Tom", 74);
vect.push_back(st1);
st1.name="Jimy";
st1.score=56;
vect.push_back(st1);
st1.name="Mary";
st1.score=92;
vect.push_back(st1);
st1.name="Jessy";
st1.score=85;
vect.push_back(st1);
st1.name="Jone";
st1.score=56;
vect.push_back(st1);
st1.name="Bush";
st1.score=52;
vect.push_back(st1);
st1.name="Winter";
st1.score=77;
vect.push_back(st1);
st1.name="Andyer";
st1.score=63;
vect.push_back(st1);
st1.name="Lily";
st1.score=76;
vect.push_back(st1);
st1.name="Maryia";
st1.score=89;
vect.push_back(st1);
cout<<"------before sort..."<<endl;
for(int i = 0 ; i < vect.size(); i ++) cout<<vect[i].name<<":"t"<<vect[i].score<<endl;
stable_sort(vect.begin(), vect.end(),less<student>());
cout <<"-----after sort ...."<<endl;
for(int i = 0 ; i < vect.size(); i ++) cout<<vect[i].name<<":"t"<<vect[i].score<<endl;
return 0 ;
}

其输出是:
------before sort...
Tom:    74
Jimy:   56
Mary:   92
Jessy:  85
Jone:   56
Bush:   52
Winter: 77
Andyer: 63
Lily:   76
Maryia: 89
-----after sort ....
Bush:   52
Jimy:   56
Jone:   56
Andyer: 63
Tom:    74
Lily:   76
Winter: 77
Jessy:  85
Maryia: 89
Mary:   92
sort采用的是成熟的"快速排序算法"(目前大部分STL版本已经不是采用简单的快速排序,而是结合内插排序算法)。注1,可以保证很好的平均性能、复杂度为n*log(n),由于单纯的快速排序在理论上有最差的情况,性能很低,其算法复杂度为n*n,但目前大部分的STL版本都已经在这方面做了优化,因此你可以放心使用。stable_sort采用的是"归并排序",分派足够内存是,其算法复杂度为n*log(n), 否则其复杂度为n*log(n)*log(n),其优点是会保持相等元素之间的相对位置在排序前后保持一致。

1.5 局部排序

局部排序其实是为了减少不必要的操作而提供的排序方式。其函数原型为:
template <class RandomAccessIterator>
void partial_sort(RandomAccessIterator first,
RandomAccessIterator middle,
RandomAccessIterator last);
template <class RandomAccessIterator, class StrictWeakOrdering>
void partial_sort(RandomAccessIterator first,
RandomAccessIterator middle,
RandomAccessIterator last,
StrictWeakOrdering comp);
template <class InputIterator, class RandomAccessIterator>
RandomAccessIterator partial_sort_copy(InputIterator first, InputIterator last,
RandomAccessIterator result_first,
RandomAccessIterator result_last);
template <class InputIterator, class RandomAccessIterator,
class StrictWeakOrdering>
RandomAccessIterator partial_sort_copy(InputIterator first, InputIterator last,
RandomAccessIterator result_first,
RandomAccessIterator result_last, Compare comp);

理解了sort 和stable_sort后,再来理解partial_sort 就比较容易了。先看看其用途: 班上有10个学生,我想知道分数最低的5名是哪些人。如果没有partial_sort,你就需要用sort把所有人排好序,然后再取前5个。现在你只需要对分数最低5名排序,把上面的程序做如下修改:
stable_sort(vect.begin(), vect.end(),less<student>());
替换为:
partial_sort(vect.begin(), vect.begin()+5, vect.end(),less<student>());

输出结果为:
------before sort...
Tom:    74
Jimy:   56
Mary:   92
Jessy:  85
Jone:   56
Bush:   52
Winter: 77
Andyer: 63
Lily:   76
Maryia: 89
-----after sort ....
Bush:   52
Jimy:   56
Jone:   56
Andyer: 63
Tom:    74
Mary:   92
Jessy:  85
Winter: 77
Lily:   76
Maryia: 89
这样的好处知道了吗?当数据量小的时候可能看不出优势,如果是100万学生,我想找分数最少的5个人......

partial_sort采用的堆排序(heapsort),它在任何情况下的复杂度都是n*log(n). 如果你希望用partial_sort来实现全排序,你只要让middle=last就可以了。

partial_sort_copy其实是copy和partial_sort的组合。被排序(被复制)的数量是[first, last)和[result_first, result_last)中区间较小的那个。如果[result_first, result_last)区间大于[first, last)区间,那么partial_sort相当于copy和sort的组合。

1.6 nth_element 指定元素排序

nth_element一个容易看懂但解释比较麻烦的排序。用例子说会更方便:
班上有10个学生,我想知道分数排在倒数第4名的学生。
如果要满足上述需求,可以用sort排好序,然后取第4位(因为是由小到大排), 更聪明的朋友会用partial_sort, 只排前4位,然后得到第4位。其实这是你还是浪费,因为前两位你根本没有必要排序,此时,你就需要nth_element:
template <class RandomAccessIterator>
void nth_element(RandomAccessIterator first, RandomAccessIterator nth,
RandomAccessIterator last);
template <class RandomAccessIterator, class StrictWeakOrdering>
void nth_element(RandomAccessIterator first, RandomAccessIterator nth,
RandomAccessIterator last, StrictWeakOrdering comp);

对于上述实例需求,你只需要按下面要求修改1.4中的程序:
stable_sort(vect.begin(), vect.end(),less<student>());
替换为:
nth_element(vect.begin(), vect.begin()+3, vect.end(),less<student>());

运行结果为:
------before sort...
Tom:    74
Jimy:   56
Mary:   92
Jessy:  85
Jone:   56
Bush:   52
Winter: 77
Andyer: 63
Lily:   76
Maryia: 89
-----after sort ....
Jone:   56
Bush:   52
Jimy:   56
Andyer: 63
Jessy:  85
Mary:   92
Winter: 77
Tom:    74
Lily:   76
Maryia: 89
第四个是谁?Andyer,这个倒霉的家伙。为什么是begin()+3而不是+4? 我开始写这篇文章的时候也没有在意,后来在ilovevc 的提醒下,发现了这个问题。begin()是第一个,begin()+1是第二个,... begin()+3当然就是第四个了。

1.7 partition 和stable_partition

好像这两个函数并不是用来排序的,'分类'算法,会更加贴切一些。partition就是把一个区间中的元素按照某个条件分成两类。其函数原型为:
template <class ForwardIterator, class Predicate>
ForwardIterator partition(ForwardIterator first,
ForwardIterator last, Predicate pred)
template <class ForwardIterator, class Predicate>
ForwardIterator stable_partition(ForwardIterator first, ForwardIterator last,
Predicate pred);

看看应用吧:班上10个学生,计算所有没有及格(低于60分)的学生。你只需要按照下面格式替换1.4中的程序:
stable_sort(vect.begin(), vect.end(),less<student>());
替换为:
student exam("pass", 60);
stable_partition(vect.begin(), vect.end(), bind2nd(less<student>(), exam));

其输出结果为:
------before sort...
Tom:    74
Jimy:   56
Mary:   92
Jessy:  85
Jone:   56
Bush:   52
Winter: 77
Andyer: 63
Lily:   76
Maryia: 89
-----after sort ....
Jimy:   56
Jone:   56
Bush:   52
Tom:    74
Mary:   92
Jessy:  85
Winter: 77
Andyer: 63
Lily:   76
Maryia: 89
看见了吗,Jimy,Jone, Bush(难怪说美国总统比较笨 smile )都没有及格。而且使用的是stable_partition, 元素之间的相对次序是没有变.

2 Sort 和容器


STL中标准容器主要vector, list, deque, string, set, multiset, map, multimay, 其中set, multiset, map, multimap都是以树结构的方式存储其元素详细内容请参看:学习STL map, STL set之数据结构基础. 因此在这些容器中,元素一直是有序的。

这些容器的迭代器类型并不是随机型迭代器,因此,上述的那些排序函数,对于这些容器是不可用的。上述sort函数对于下列容器是可用的:

  • vector
  • string
  • deque
如果你自己定义的容器也支持随机型迭代器,那么使用排序算法是没有任何问题的。

对于list容器,list自带一个sort成员函数list::sort(). 它和算法函数中的sort差不多,但是list::sort是基于指针的方式排序,也就是说,所有的数据移动和比较都是此用指针的方式实现,因此排序后的迭代器一直保持有效(vector中sort后的迭代器会失效).

 

3 选择合适的排序函数


为什么要选择合适的排序函数?可能你并不关心效率(这里的效率指的是程序运行时间), 或者说你的数据量很小, 因此你觉得随便用哪个函数都无关紧要。

其实不然,即使你不关心效率,如果你选择合适的排序函数,你会让你的代码更容易让人明白,你会让你的代码更有扩充性,逐渐养成一个良好的习惯,很重要吧 smile

如果你以前有用过C语言中的qsort, 想知道qsort和他们的比较,那我告诉你,qsort和sort是一样的,因为他们采用的都是快速排序。从效率上看,以下几种sort算法的是一个排序,效率由高到低(耗时由小变大):

  1. partion
  2. stable_partition
  3. nth_element
  4. partial_sort
  5. sort
  6. stable_sort
记得,以前翻译过Effective STL的文章,其中对如何选择排序函数总结的很好:
  • 若需对vector, string, deque, 或 array容器进行全排序,你可选择sort或stable_sort;
  • 若只需对vector, string, deque, 或 array容器中取得top n的元素,部分排序partial_sort是首选.
  • 若对于vector, string, deque, 或array容器,你需要找到第n个位置的元素或者你需要得到top n且不关系top n中的内部顺序,nth_element是最理想的;
  • 若你需要从标准序列容器或者array中把满足某个条件或者不满足某个条件的元素分开,你最好使用partition或stable_partition;
  • 若使用的list容器,你可以直接使用partition和stable_partition算法,你可以使用list::sort代替sort和stable_sort排序。若你需要得到partial_sort或nth_element的排序效果,你必须间接使用。正如上面介绍的有几种方式可以选择。
总之记住一句话: 如果你想节约时间,不要走弯路, 也不要走多余的路!

4 小结


讨论技术就像个无底洞,经常容易由一点可以引申另外无数个技术点。因此需要从全局的角度来观察问题,就像观察STL中的sort算法一样。其实在STL还有make_heap, sort_heap等排序算法。本文章没有提到。本文以实例的方式,解释了STL中排序算法的特性,并总结了在实际情况下应如何选择合适的算法。
posted on 2008-10-12 17:25  leenshan  阅读(28219)  评论(0编辑  收藏  举报