VC的vc编译器是什么用的是LL还是LR

Loadrunner:安装LR11时提示缺少vc2005_sp1_with_atl_fix_redist - channy14 - 博客园
[问题现象]
安装LR11时提示缺少vc2005_sp1_with_atl_fix_redist:
[解决办法]
手动安装缺少的组件,LR安装包中已自带该组件,为何不自动捕捉异常去获取该自带的组件去安装呢?体验不好....该组件在安装程序自带的lrunner\Chs\prerequisites\vc2005_sp1_redist,双击运行,再重新安装LR就可以了。SFP-10G-SR是什么?10GBase-SR和10GBase-LR是两种不同的万兆以太网规范,10GBase-SR中的&SR&代表&短距离&(short range)的意思,该规范支持编码方式为64B/66B的短波(波长为850nm)多模光纤(MMF),有效传输距离为2~300m,要支持300m传输需要采用经过优化的50&m线径OM3(Optimized Multimode 3,优化的多模3)光纤(没有优化的线径50&m光纤称为OM2光纤,而线径为62.5&m的光纤称为OM1光纤)。这种规范具有最低成本、最低电源消耗和最小的光纤模块等优势,SFP-10G-SR光模块就是符合这一规范的光模块。SFP-10G-LR是什么?10GBase-LR中的&LR&指的是&长距离&(Long Range),该规范支持编码方式为64B/66B的长波(1310nm)单模光纤(SMF),有效传输距离为2m到10km,事实上最高可达到25km。SFP-10G-LR光模块就符合这一规范,具有小型化、低功耗、传输距离长的特点。SFP-10G-LR光模块和SFP-10G-SR光模块常见产品光模块型号光模块名称中心波长接口传输距离SFP-10GSR-85850nmLC双工300mSFP-10GSR-85850nmLC双工300mSFP-10GSR-85850nmLC双工300mSFP-10GLR-311310nmLC双工10kmSFP-10GLR-311310nmLC双工10kmSFP-10GLR-311310nmLC双工10kmSFP-10G-LR光模块和SFP-10G-SR光模块的对比和区别产品图片产品标题产品价格¥90.00¥180.00型号名SFP-10G-SRSFP-10G-LR封装类型SFP+SFP+速率10 Gbps10 Gbps波长850nm1310nm最大传输距离300m10km接口LC双工LC双工激光器类型VCSEL 850nmDFB 1310nm光纤类型MMFSMFDOMYESYES发射光功率-6~-1dBm-8.2~0.5dBm接收灵敏度& -11.1dBm& -14.4dBm商业温度0~ 70&C (32 ~ 158&F)0~ 70&C (32 ~ 158&F)特点最大传输速率10.3Gbps850纳米 VCESL激光器典型传输距离300米 (OM3 多模光纤)3.3V单电压,TTL逻辑接口双LC接口,支持热插拔最大传输速率10.3Gbps1310纳米 DFB-LD激光器典型传输距离10公里3.3V单电压,TTL逻辑接口双LC接口,支持热插拔应用范围仅用于短距离连接。主要用于长距离连接。:飞速(FS)是一家致力于光通讯产品研发设计并提供系统解决方案的公司,是全球光网络器件和光互联领域的市场创新者和应用技术开拓者。在过去四年里,我们建立了一支强大和专业的产品研究与开发、系统化对策解决及供应链管理团队。如今,我们已经同越来越多的世界知名企业建立了合作关系,如CloudFlare, EXFO, Apple, MRV, JDSU, ADTRAN, Avago, EMC等等,他们的云平台和数据中心都在大量使用我们的产品。对于我们的产品和服务,他们均给予了高度评价。今后,飞速(FS)仍会一如既往,为客户以及整个光通讯行业创造更大的价值!欲知更多,请点击或拨打。
微信公众号
扫一扫,轻松Get更多技术干货!原文地址为:原文:
在VC6中,Release版本的程序的,除了程序自身关联的各个Dll之外,只需添加42.dll即可。
在VC2005中,发生的变化包括:
1.MFC的版本发生变化,最新的版本为8.0,所有应该包括MFC80.dll
2.在VC2005的架构下,采用manifest进行dll的版本确认,因此需要添加MF所需的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) |
Sockets/Windows Sockets错误码Windows Sockets在头文件winsock.h中定义了所有的错误码,它们包括以“WSA”打头的Windows Sockets实现返回的错误码和Berkeley Sockets定义的错误码全集。定义BerkeleySockets错误码是为了确保原有软件的可移植性。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 InternetUDP协议不能和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 serviceprovider.服务提供者返回了一个假的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 signaledstate.应用程序试图检测一个没有完成的重叠操作的状态。应用程序使用函数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) |
发布版本时的收获(原创)
关键字:应用程序正常初始化(0xc0000135)失败、OLE支持 正文
浏览数(404) |
使用标准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:
virtual void foo();};
class D:public B{
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:
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}
在函数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_iN}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) |
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 TESTDATA READ WRITESECTIONS
.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 dwD//用户定义数据
DWORD cbD//数据大小
PVOID lpD//指向数据的指针}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//用户存放数据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) |
作者: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_ConnectM
enum {ext_dm_HostShutdown = 0,ext_dm_UserClosed = 1} ext_DisconnectM
interface _IDTExtensibility2 : IDispatch {[id(0x)]HRESULT OnConnection([in] IDispatch* Application,[in] ext_ConnectMode ConnectMode,[in] IDispatch* AddInInst,[in] SAFEARRAY(VARIANT)* custom);[id(0x)]HRESULT OnDisconnection([in] ext_DisconnectMode RemoveMode,[in] SAFEARRAY(VARIANT)* custom);[id(0x)]HRESULT OnAddInsUpdate([in] SAFEARRAY(VARIANT)* custom);[id(0x)]HRESULT OnStartupComplete([in] SAFEARRAY(VARIANT)* custom);[id(0x)]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 ''val CommandLineSafe = d ''}}}}}}}既然我们希望在程序启动的时候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_guidsusing namespace O
#import "C:"Program Files"Microsoft Office"Office"MSOUTL9.olb"rename_namespace("Outlook"), raw_interfaces_only, named_guidsusing namespace O注意:你应该改变这些路径,是他们匹配你安装的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& spCmdBCComPtr & Office::CommandBar& spCmdB
// QI() for _ApplicationCComQIPtr &Outlook::_Application& spApp(Application);ATLASSERT(spApp);// get the CommandBars interface that represents Outlook's//toolbars & menu items
CComPtr&Outlook::_Explorer& spEspApp-&ActiveExplorer(&spExplorer);
HRESULT hr = spExplorer-&get_CommandBars(&spCmdBars);if(FAILED(hr))ATLASSERT(spCmdBars);
// now we add a new toolband to Outlook// to which we'll add 2 buttonsCComVariant vName("OutlookAddin");CComPtr &Office::CommandBar& spNewCmdB
// position it below all toolbands//MsoBarPosition::msoBarTop = 1CComVariant vPos(1);
CComVariant vTemp(VARIANT_TRUE); // menu is temporaryCComVariant vEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);//Add a new toolband through Add method// vMenuTemp holds an unspecified parameter//spNewCmdBar points to the newly created toolbandspNewCmdBar = spCmdBars-&Add(vName, vPos, vEmpty, vTemp);
//now get the toolband's CommandBarControlsCComPtr & Office::CommandBarControls& spBarCspBarControls = spNewCmdBar-&GetControls();ATLASSERT(spBarControls);
//MsoControlType::msoControlButton = 1CComVariant vToolBarType(1);//show the toolbar?CComVariant vShow(VARIANT_TRUE);
CComPtr & Office::CommandBarControl& spNewBCComPtr & Office::CommandBarControl& spNewBar2;
// add first buttonspNewBar = spBarControls-&Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow);ATLASSERT(spNewBar);// add 2nd buttonspNewBar2 = 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 itCComQIPtr & 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 useHBITMAP 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 bitmapspCmdButton-&PutStyle(Office::msoButtonIconAndCaption);
HRESULT hr = spCmdButton-&PasteFace();if (FAILED(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 toolbandspNewCmdBar-&PutVisible(VARIANT_TRUE);
spCmdButton2-&PutStyle(Office::msoButtonIconAndCaption);
//specify predefined bitmapspCmdButton2-&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& spCmdCCComPtr & Office::CommandBarControls& spCmdBarCCComPtr & Office::CommandBarPopup& spCmdPCComPtr & Office::CommandBarControl& spCmdC
// get CommandBar that is Outlook's main menuhr = spCmdBars-&get_ActiveMenuBar(&spCmdBar);if (FAILED(hr))// get menu as CommandBarControlsspCmdCtrls = spCmdBar-&GetControls();ATLASSERT(spCmdCtrls);
// we want to add a menu entry to Outlook's 6th(Tools) menu //itemCComVariant vItem(5);spCmdCtrl= spCmdCtrls-&GetItem(vItem);ATLASSERT(spCmdCtrl);
IDispatchPtr spDspDisp = spCmdCtrl-&GetControl();
// a CommandBarPopup interface is the actual menu itemCComQIPtr & Office::CommandBarPopup& ppCmdPopup(spDisp);ATLASSERT(ppCmdPopup);
spCmdBarCtrls = ppCmdPopup-&GetControls();ATLASSERT(spCmdBarCtrls);
CComVariant vMenuType(1); // type of control - menuCComVariant vMenuPos(6);CComVariant vMenuEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);CComVariant vMenuShow(VARIANT_TRUE); // menu should be visibleCComVariant vMenuTemp(VARIANT_TRUE); // menu is temporary
CComPtr & Office::CommandBarControl& spNewM// now create the actual menu item and add itspNewMenu = 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 CommandBarButtonCComQIPtr & 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 menuspNewMenu-&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(0x), helpcontext(0x)]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 OnClickButtonI接着打开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(0x), helpcontext(0x0050df87)]void OptionsPagesAdd([in] PropertyPages* Pages);....}
[odl,uuid(0-),helpcontext(0x0053ec78),dual,oleautomation]........
interface PropertyPages : IDispatch {[id(0x), propget, helpcontext(0x004deb87)]HRESULT Application([out, retval] _Application** Application);........
[id(0x0000005f), helpcontext(0x)]HRESULT Add([in] VARIANT Page,[in, optional] BSTR Title);
[id(0x), helpcontext(0x)]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 OnOptionsAddPagesI
class ATL_NO_VTABLE CAddin :........public IDispEventSimpleImpl&4,CAddin,&__uuidof(Outlook::ApplicationEvents)&{public://typedef for applicationEvents sink implementationtypedef IDispEventSimpleImpl&/*nID =*/ 4,CAddin,&__uuidof(Outlook::ApplicationEvents)& AppE............BEGIN_SINK_MAP(CAddin)....SINK_ENTRY_INFO(4,__uuidof(Outlook::ApplicationEvents),/*dispid*/0xf005,OnOptionsAddPages,&OnOptionsAddPagesInfo)END_SINK_MAP()
public://callback method for OptionsAddPages eventvoid __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 controlCComVariant varProgId(OLESTR("OutlookAddin.PropPage"));
//tab textCComBSTR 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) |
函数调用方式分为两类:标准调用约定、C/C++调用约定。
标准调用约定(__stdcall):这些函数将在返回到调用者之间将参数从栈中删除。
C/C++调用约定(__cdecl):栈的清理工作由调用者来完成。
除C/C++之外的其他语言缺省情况下使用标准调用。
MICROSOFT平台上的COM接口提供的所有函数使用的均是标准调用约定,参数可变的函数使用的则是C调用约定。
WINDEF.H中
#define pascal __stdcall
OBJBASE.H中
#define STDMETHODCALLTYPE __stdcall
标签:软件开发 | 浏览数(342) |
该网站查询很方便,点击网页中的Office wiki标头位置即可进行查询
标签:软件开发 | 浏览数(404) |
导入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")
标签:软件开发 | 浏览数(585) |
摘 要:介绍了动态链接库这种模块复用方法及在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-为软件注册号 FileEncrypt1(加密源文件名,加密生成目标文件名,密码,"user-"); //释放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-为软件注册号 FileDecrypt2(解密源文件名,解密生成目标文件名,密码,"user-"); //释放DLL模块 ::AfxFreeLibrary(hdll);
  利用DLL这种模块复用方法可以减少软件工程开发的工作量,增强代码的可移植性,降低模块测试的复杂性,从总体上提高软件工程的开发效率。
标签:软件开发 | 浏览数(376) |
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 CMyO AfxBeginThread(MyThreadProc, pNewObject);   //新线程 UINT MyThreadProc( LPVOID pParam ) { CMyObject* pObject = (CMyObject*)pP   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的返回值}

我要回帖

更多关于 编译器 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信