1typedef ULONG (WINAPI *PFNNtUnmapViewOfSection)( IN HANDLE ProcessHandle,IN PVOID BaseAddress );
2
3BOOL UnmapViewOfModule ( DWORD dwProcessId, LPVOID lpBaseAddr )
4{
5 HMODULE hModule = GetModuleHandle ( L"ntdll.dll" ) ;
6 if ( hModule == NULL )
7 hModule = LoadLibrary ( L"ntdll.dll" ) ;
8
9 PFNNtUnmapViewOfSection pfnNtUnmapViewOfSection = (PFNNtUnmapViewOfSection)GetProcAddress ( hModule, "NtUnmapViewOfSection" ) ;
10
11 HANDLE hProcess = OpenProcess ( PROCESS_ALL_ACCESS, TRUE, dwProcessId ) ;
12 ULONG ret = pfnNtUnmapViewOfSection ( hProcess, lpBaseAddr ) ;
13 CloseHandle ( hProcess ) ;
14 return ret ? FALSE : TRUE;
15}
16
17
posted @
2009-02-21 22:32 幽幽 阅读(458) |
评论 (0) |
编辑 收藏
本文转自:
http://hi.baidu.com/csuhkx/blog/item/267418d3614cf9013bf3cf55.html这篇文章是翻译MSDN上一篇叫《Heap: Pleasures and Pains》的文章的
Murali R. Krishnan
Microsoft Corporation
1999 年 2 月
摘要: 讨论常见的堆性能问题以及如何防范它们。(共 9 页)
前言
您是否是动态分配的 C/C++ 对象忠实且幸运的用户?您是否在模块间的往返通信中频繁地使用了“自动化”?您的程序是否因堆分配而运行起来很慢?不仅仅您遇到这样的问题。几乎所有项目迟早都会遇到堆问题。大家都想说,“我的代码真正好,只是堆太慢”。那只是部分正确。更深入理解堆及其用法、以及会发生什么问题,是很有用的。
什么是堆?
(如果您已经知道什么是堆,可以跳到“什么是常见的堆性能问题?”部分)
在程序中,使用堆来动态分配和释放对象。在下列情况下,调用堆操作:
- 事先不知道程序所需对象的数量和大小。
- 对象太大而不适合堆栈分配程序。
堆使用了在运行时分配给代码和堆栈的内存之外的部分内存。下图给出了堆分配程序的不同层。
GlobalAlloc/GlobalFree:Microsoft Win32 堆调用,这些调用直接与每个进程的默认堆进行对话。
LocalAlloc/LocalFree:Win32 堆调用(为了与 Microsoft Windows NT 兼容),这些调用直接与每个进程的默认堆进行对话。
COM 的 IMalloc 分配程序(或 CoTaskMemAlloc / CoTaskMemFree):函数使用每个进程的默认堆。自动化程序使用“组件对象模型 (COM)”的分配程序,而申请的程序使用每个进程堆。
C/C++ 运行时 (CRT) 分配程序:提供了 malloc() 和 free() 以及 new 和 delete 操作符。如 Microsoft Visual Basic 和 Java 等语言也提供了新的操作符并使用垃圾收集来代替堆。CRT 创建自己的私有堆,驻留在 Win32 堆的顶部。
Windows NT 中,Win32 堆是 Windows NT 运行时分配程序周围的薄层。所有 API 转发它们的请求给 NTDLL。
Windows NT 运行时分配程序提供 Windows NT 内的核心堆分配程序。它由具有 128 个大小从 8 到 1,024 字节的空闲列表的前端分配程序组成。后端分配程序使用虚拟内存来保留和提交页。
在图表的底部是“虚拟内存分配程序”,操作系统使用它来保留和提交页。所有分配程序使用虚拟内存进行数据的存取。
分配和释放块不就那么简单吗?为何花费这么长时间?
堆实现的注意事项
传统上,操作系统和运行时库是与堆的实现共存的。在一个进程的开始,操作系统创建一个默认堆,叫做“进程堆”。如果没有其他堆可使用,则块的分配使用“进程堆”。语言运行时也能在进程内创建单独的堆。(例如,C 运行时创建它自己的堆。)除这些专用的堆外,应用程序或许多已载入的动态链接库 (DLL) 之一可以创建和使用单独的堆。Win32 提供一整套 API 来创建和使用私有堆。有关堆函数(英文)的详尽指导,请参见 MSDN。
当应用程序或 DLL 创建私有堆时,这些堆存在于进程空间,并且在进程内是可访问的。从给定堆分配的数据将在同一个堆上释放。(不能从一个堆分配而在另一个堆释放。)
在所有虚拟内存系统中,堆驻留在操作系统的“虚拟内存管理器”的顶部。语言运行时堆也驻留在虚拟内存顶部。某些情况下,这些堆是操作系统堆中的层,而语言运行时堆则通过大块的分配来执行自己的内存管理。不使用操作系统堆,而使用虚拟内存函数更利于堆的分配和块的使用。
典型的堆实现由前、后端分配程序组成。前端分配程序维持固定大小块的空闲列表。对于一次分配调用,堆尝试从前端列表找到一个自由块。如果失败,堆被迫从后端(保留和提交虚拟内存)分配一个大块来满足请求。通用的实现有每块分配的开销,这将耗费执行周期,也减少了可使用的存储空间。
Knowledge Base 文章 Q10758,“用 calloc() 和 malloc() 管理内存” (搜索文章编号), 包含了有关这些主题的更多背景知识。另外,有关堆实现和设计的详细讨论也可在下列著作中找到:“Dynamic Storage Allocation: A Survey and Critical Review”,作者 Paul R. Wilson、Mark S. Johnstone、Michael Neely 和 David Boles;“International Workshop on Memory Management”, 作者 Kinross, Scotland, UK, 1995 年 9 月(http://www.cs.utexas.edu/users/oops/papers.html)(英文)。
Windows NT 的实现(Windows NT 版本 4.0 和更新版本) 使用了 127 个大小从 8 到 1,024 字节的 8 字节对齐块空闲列表和一个“大块”列表。“大块”列表(空闲列表[0]) 保存大于 1,024 字节的块。空闲列表容纳了用双向链表链接在一起的对象。默认情况下,“进程堆”执行收集操作。(收集是将相邻空闲块合并成一个大块的操作。)收集耗费了额外的周期,但减少了堆块的内部碎片。
单一全局锁保护堆,防止多线程式的使用。(请参见“Server Performance and Scalability Killers”中的第一个注意事项, George Reilly 所著,在 “MSDN Online Web Workshop”上(站点:http://msdn.microsoft.com/workshop/server/iis/tencom.asp(英文)。)单一全局锁本质上是用来保护堆数据结构,防止跨多线程的随机存取。若堆操作太频繁,单一全局锁会对性能有不利的影响。
什么是常见的堆性能问题?
以下是您使用堆时会遇到的最常见问题:
竞争是在分配和释放操作中导致速度减慢的问题。理想情况下,希望使用没有竞争和快速分配/释放的堆。可惜,现在还没有这样的通用堆,也许将来会有。
在所有的服务器系统中(如 IIS、MSProxy、DatabaseStacks、网络服务器、 Exchange 和其他), 堆锁定实在是个大瓶颈。处理器数越多,竞争就越会恶化。
尽量减少堆的使用
现在您明白使用堆时存在的问题了,难道您不想拥有能解决这些问题的超级魔棒吗?我可希望有。但没有魔法能使堆运行加快—因此不要期望在产品出货之前的最后一星期能够大为改观。如果提前规划堆策略,情况将会大大好转。调整使用堆的方法,减少对堆的操作是提高性能的良方。
如何减少使用堆操作?通过利用数据结构内的位置可减少堆操作的次数。请考虑下列实例:
struct ObjectA {
// objectA 的数据
}
struct ObjectB {
// objectB 的数据
}
// 同时使用 objectA 和 objectB
//
// 使用指针
//
struct ObjectB {
struct ObjectA * pObjA;
// objectB 的数据
}
//
// 使用嵌入
//
struct ObjectB {
struct ObjectA pObjA;
// objectB 的数据
}
//
// 集合 – 在另一对象内使用 objectA 和 objectB
//
struct ObjectX {
struct ObjectA objA;
struct ObjectB objB;
}
- 避免使用指针关联两个数据结构。如果使用指针关联两个数据结构,前面实例中的对象 A 和 B 将被分别分配和释放。这会增加额外开销—我们要避免这种做法。
- 把带指针的子对象嵌入父对象。当对象中有指针时,则意味着对象中有动态元素(百分之八十)和没有引用的新位置。嵌入增加了位置从而减少了进一步分配/释放的需求。这将提高应用程序的性能。
- 合并小对象形成大对象(聚合)。聚合减少分配和释放的块的数量。如果有几个开发者,各自开发设计的不同部分,则最终会有许多小对象需要合并。集成的挑战就是要找到正确的聚合边界。
- 内联缓冲区能够满足百分之八十的需要(aka 80-20 规则)。个别情况下,需要内存缓冲区来保存字符串/二进制数据,但事先不知道总字节数。估计并内联一个大小能满足百分之八十需要的缓冲区。对剩余的百分之二十,可以分配一个新的缓冲区和指向这个缓冲区的指针。这样,就减少分配和释放调用并增加数据的位置空间,从根本上提高代码的性能。
- 在块中分配对象(块化)。块化是以组的方式一次分配多个对象的方法。如果对列表的项连续跟踪,例如对一个 {名称,值} 对的列表,有两种选择:选择一是为每一个“名称-值”对分配一个节点;选择二是分配一个能容纳(如五个)“名称-值”对的结构。例如,一般情况下,如果存储四对,就可减少节点的数量,如果需要额外的空间数量,则使用附加的链表指针。
块化是友好的处理器高速缓存,特别是对于 L1-高速缓存,因为它提供了增加的位置 —不用说对于块分配,很多数据块会在同一个虚拟页中。
- 正确使用 _amblksiz。C 运行时 (CRT) 有它的自定义前端分配程序,该分配程序从后端(Win32 堆)分配大小为 _amblksiz 的块。将 _amblksiz 设置为较高的值能潜在地减少对后端的调用次数。这只对广泛使用 CRT 的程序适用。
使用上述技术将获得的好处会因对象类型、大小及工作量而有所不同。但总能在性能和可升缩性方面有所收获。另一方面,代码会有点特殊,但如果经过深思熟虑,代码还是很容易管理的。
其他提高性能的技术
下面是一些提高速度的技术:
- 使用 Windows NT5 堆
由于几个同事的努力和辛勤工作,1998 年初 Microsoft Windows(R) 2000 中有了几个重大改进:
- 改进了堆代码内的锁定。堆代码对每堆一个锁。全局锁保护堆数据结构,防止多线程式的使用。但不幸的是,在高通信量的情况下,堆仍受困于全局锁,导致高竞争和低性能。Windows 2000 中,锁内代码的临界区将竞争的可能性减到最小,从而提高了可伸缩性。
- 使用 “Lookaside”列表。堆数据结构对块的所有空闲项使用了大小在 8 到 1,024 字节(以 8-字节递增)的快速高速缓存。快速高速缓存最初保护在全局锁内。现在,使用 lookaside 列表来访问这些快速高速缓存空闲列表。这些列表不要求锁定,而是使用 64 位的互锁操作,因此提高了性能。
- 内部数据结构算法也得到改进。
这些改进避免了对分配高速缓存的需求,但不排除其他的优化。使用 Windows NT5 堆评估您的代码;它对小于 1,024 字节 (1 KB) 的块(来自前端分配程序的块)是最佳的。GlobalAlloc() 和 LocalAlloc() 建立在同一堆上,是存取每个进程堆的通用机制。如果希望获得高的局部性能,则使用 Heap(R) API 来存取每个进程堆,或为分配操作创建自己的堆。如果需要对大块操作,也可以直接使用 VirtualAlloc() / VirtualFree() 操作。
上述改进已在 Windows 2000 beta 2 和 Windows NT 4.0 SP4 中使用。改进后,堆锁的竞争率显著降低。这使所有 Win32 堆的直接用户受益。CRT 堆建立于 Win32 堆的顶部,但它使用自己的小块堆,因而不能从 Windows NT 改进中受益。(Visual C++ 版本 6.0 也有改进的堆分配程序。)
- 使用分配高速缓存
分配高速缓存允许高速缓存分配的块,以便将来重用。这能够减少对进程堆(或全局堆)的分配/释放调用的次数,也允许最大限度的重用曾经分配的块。另外,分配高速缓存允许收集统计信息,以便较好地理解对象在较高层次上的使用。
典型地,自定义堆分配程序在进程堆的顶部实现。自定义堆分配程序与系统堆的行为很相似。主要的差别是它在进程堆的顶部为分配的对象提供高速缓存。高速缓存设计成一套固定大小(如 32 字节、64 字节、128 字节等)。这一个很好的策略,但这种自定义堆分配程序丢失与分配和释放的对象相关的“语义信息”。
与自定义堆分配程序相反,“分配高速缓存”作为每类分配高速缓存来实现。除能够提供自定义堆分配程序的所有好处之外,它们还能够保留大量语义信息。每个分配高速缓存处理程序与一个目标二进制对象关联。它能够使用一套参数进行初始化,这些参数表示并发级别、对象大小和保持在空闲列表中的元素的数量等。分配高速缓存处理程序对象维持自己的私有空闲实体池(不超过指定的阀值)并使用私有保护锁。合在一起,分配高速缓存和私有锁减少了与主系统堆的通信量,因而提供了增加的并发、最大限度的重用和较高的可伸缩性。
需要使用清理程序来定期检查所有分配高速缓存处理程序的活动情况并回收未用的资源。如果发现没有活动,将释放分配对象的池,从而提高性能。
可以审核每个分配/释放活动。第一级信息包括对象、分配和释放调用的总数。通过查看它们的统计信息可以得出各个对象之间的语义关系。利用以上介绍的许多技术之一,这种关系可以用来减少内存分配。
分配高速缓存也起到了调试助手的作用,帮助您跟踪没有完全清除的对象数量。通过查看动态堆栈返回踪迹和除没有清除的对象之外的签名,甚至能够找到确切的失败的调用者。
- MP 堆
MP 堆是对多处理器友好的分布式分配的程序包,在 Win32 SDK(Windows NT 4.0 和更新版本)中可以得到。最初由 JVert 实现,此处堆抽象建立在 Win32 堆程序包的顶部。MP 堆创建多个 Win32 堆,并试图将分配调用分布到不同堆,以减少在所有单一锁上的竞争。
本程序包是好的步骤 —一种改进的 MP-友好的自定义堆分配程序。但是,它不提供语义信息和缺乏统计功能。通常将 MP 堆作为 SDK 库来使用。如果使用这个 SDK 创建可重用组件,您将大大受益。但是,如果在每个 DLL 中建立这个 SDK 库,将增加工作设置。
- 重新思考算法和数据结构
要在多处理器机器上伸缩,则算法、实现、数据结构和硬件必须动态伸缩。请看最经常分配和释放的数据结构。试问,“我能用不同的数据结构完成此工作吗?”例如,如果在应用程序初始化时加载了只读项的列表,这个列表不必是线性链接的列表。如果是动态分配的数组就非常好。动态分配的数组将减少内存中的堆块和碎片,从而增强性能。
减少需要的小对象的数量减少堆分配程序的负载。例如,我们在服务器的关键处理路径上使用五个不同的对象,每个对象单独分配和释放。一起高速缓存这些对象,把堆调用从五个减少到一个,显著减少了堆的负载,特别当每秒钟处理 1,000 个以上的请求时。
如果大量使用“Automation”结构,请考虑从主线代码中删除“Automation BSTR”,或至少避免重复的 BSTR 操作。(BSTR 连接导致过多的重分配和分配/释放操作。)
摘要
对所有平台往往都存在堆实现,因此有巨大的开销。每个单独代码都有特定的要求,但设计能采用本文讨论的基本理论来减少堆之间的相互作用。
- 评价您的代码中堆的使用。
- 改进您的代码,以使用较少的堆调用:分析关键路径和固定数据结构。
- 在实现自定义的包装程序之前使用量化堆调用成本的方法。
- 如果对性能不满意,请要求 OS 组改进堆。更多这类请求意味着对改进堆的更多关注。
- 要求 C 运行时组针对 OS 所提供的堆制作小巧的分配包装程序。随着 OS 堆的改进,C 运行时堆调用的成本将减小。
- 操作系统(Windows NT 家族)正在不断改进堆。请随时关注和利用这些改进。
Murali Krishnan 是 Internet Information Server (IIS) 组的首席软件设计工程师。从 1.0 版本开始他就设计 IIS,并成功发行了 1.0 版本到 4.0 版本。Murali 组织并领导 IIS 性能组三年 (1995-1998), 从一开始就影响 IIS 性能。他拥有威斯康星州 Madison 大学的 M.S.和印度 Anna 大学的 B.S.。工作之外,他喜欢阅读、打排球和家庭烹饪。
posted @
2009-02-03 08:49 幽幽 阅读(472) |
评论 (0) |
编辑 收藏
现在是5:14, 外面正在下雨, 喜欢下雨, 因为听到噼噼啪啪的雨声, 我总能静下心来,
是该静下心来好好想想了, 最近总是很浮躁, 静不下心来, 是该静下心来看看书写写
代码了.....
posted @
2009-02-03 05:18 幽幽 阅读(196) |
评论 (0) |
编辑 收藏
导读:习惯的力量是惊人的。习惯能载着你走向成功,也能驮着你滑向失败。如何选择,完全取决于你自己。
1.习惯的力量:35岁以前养成好习惯
你想成功吗?那就及早培养有利于成功的好习惯。
习惯的力量是惊人的,35岁以前养成的习惯决定着你是否成功。
有这样一个寓言故事:
一位没有继承人的富豪死后将自己的一大笔遗产赠送给远房的一位亲戚,这位亲戚是一个常年靠乞讨为生的乞丐。这名接受遗产的乞丐立即身价一变,成了百万富翁。新闻记者便来采访这名幸运的乞丐:"你继承了遗产之后,你想做的第一件事是什么?"乞丐回答说:"我要买一只好一点的碗和一根结实的木棍,这样我以后出去讨饭时方便一些。"
可见,习惯对我们有着绝大的影响,因为它是一贯的,在不知不觉中,经年累月地影响着我们的行为,影响着我们的效率,左右着我们的成败。
一个人一天的行为中,大约只有5%是属于非习惯性的,而剩下的95%的行为都是习惯性的。即便是打破常规的创新,最终可以演变成为习惯性的创新。
根据行为心理学的研究结果:3周以上的重复会形成习惯;3个月以上的重复会形成稳定的习惯,即同一个动作,重复3周就会变成习惯性动作,形成稳定的习惯。
亚里士多德说:"人的行为总是一再重复。因此,卓越不是单一的举动,而是习惯。""人的行为总是一再重复。因此,卓越不是单一的举动,而是习惯。"所以,在实现成功的过程中,除了要不断激发自己的成功欲望,要有信心、有热情、有意志、有毅力等之外,还应该搭上习惯这一成功的快车,实现自己的目标。
有个动物学家做了一个实验:他将一群跳蚤放入实验用的大量杯里,上面盖上一片透明的玻璃。跳蚤习性爱跳,于是很多跳蚤都撞上了盖上的玻璃,不断地发叮叮冬冬的声音。过了一阵子,动物学家将玻璃片拿开,发现竟然所有跳蚤依然在跳,只是都已经将跳的高度保持在接近玻璃即止,以避免撞到头。结果竟然没有一只跳蚤能跳出来--依它们的能力不是跳不出来,只是它们已经适应了环境。
后来,那位动物学家就在量杯下放了一个酒精灯并且点燃了火。不到五分钟,量杯烧热了,所有跳蚤自然发挥求生的本能,每只跳蚤再也不管头是否会撞痛(因为它们以为还有玻璃罩),全部都跳出量杯以外。这个试验证明,跳蚤会为了适应环境,不愿改变习性,宁愿降低才能、封闭潜能去适应。
我想,人类之于环境也是如此。人类在适应外界大环境中,又创造出适合于自己的小环境,然后用习惯把自己困在自己所创造的环境中。所以,习惯决定着你的活动空间的大小,也决定着你的成败。养成好习惯对于你的成功非常重要。
心理学巨匠威廉·詹姆士说:"播下一个行动,收获一种习惯;播下一种习惯,收获一种性格;播下一种性格,收获一种命运。"
2.35岁以前成功必备的9大习惯
好习惯会使成功不期而至。我认为下面9个好习惯是成功必备的:
(1)积极思维的好习惯
有位秀才第三次进京赶考,住在一个经常住的店里。考试前两天他做了三个梦:第一个梦是梦到自己在墙上种白菜,第二个梦是下雨天,他戴了斗笠还打着伞,第三个梦是梦到跟心爱的表妹脱光了衣服躺在一起,但是背靠着背。临考之际做此梦,似乎有些深意,秀才第二天去找算命的解梦。算命的一听,连拍大腿说:"你还是回家吧。你想想,高墙上种菜不是白费劲吗?戴斗笠打雨伞不是多此一举吗?跟表妹脱光了衣服躺在一张床上,却背靠背,不是没戏吗?"秀才一听,心灰意冷,回店收拾包裹准备回家。店老板非常奇怪,问:"不是明天才考试吗?今天怎么就打道回府了?"秀才如此这般说了一番,店老板乐了:"唉,我也会解梦的。我倒觉得,你这次一定能考中。你想想,墙上种菜不是高种吗?戴斗笠打伞不是双保险吗?跟你表妹脱光了背靠背躺在床上,不是说明你翻身的时候就要到了吗?"秀才一听,更有道理,于是精神振奋地参加考试,居然中了个探花。
可见,事物本身并不影响人,人们只受到自己对事物看法的影响,人必须改变被动的思维习惯,养成积极的思维习惯。
怎样才算养成了积极思维的习惯呢?当你在实现目标的过程中,面对具体的工作和任务时,你的大脑里去掉了"不可能"三个字,而代之以"我怎样才能"时,可以说你就养成了积极思维的习惯了。
(2)高效工作的好习惯
一个人成功的欲望再强烈,也会被不利于成功的习惯所撕碎,而溶入平庸的日常生活中。所以说,思想决定行为,行为形成习惯,习惯决定性格,性格决定命运。你要想成功,就一定要养成高效率的工作习惯。
确定你的工作习惯是否有效率,是否有利于成功,我觉得可以用这个标准来检验:即在检省自己工作的时候,你是否为未完成工作而感到忧虑,即有焦灼感。如果你应该做的事情而没有做,或做而未做完,并经常为此而感到焦灼,那就证明你需要改变工作习惯,找到并养成一种高效率的工作习惯。
高效工作从办公室开始:
1)了解你每天的精力充沛期。通常人们在早晨9点左右工作效率最高,可以把最困难的工作放到这时来完成。
2)每天集中一、两个小时来处理手头紧急的工作,不接电话、不开会、不受打扰。这样可以事半功倍。
3)立刻回复重要的邮件,将不重要的丢弃。若任它们积累成堆,反而更费时间。
4)做个任务清单,将所有的项目和约定记在效率手册中。手头一定要带着效率手册以帮助自己按计划行事。一个人一天的行为中,大约只有5%是属于非习惯性的,而剩下的95%的行为都是习惯性的。
5)学会高效地利用零碎时间,用来读点东西或是构思一个文件,不要发呆或做白日梦。
6)减少回电话的时间。如果你需要传递的只是一个信息,不妨发个手机短信。
7)对可能打来的电话做到心中有数,这样在你接到所期待的电话后便可迅速找到所需要的各种材料,不必当时乱翻乱找。
8)学习上网高效搜寻的技能,以节省上网查询的时间。把你经常要浏览的网站收集起来以便随时找到。
9)用国际互联网简化商业旅行的安排。多数饭店和航线可以网上查询和预订。
10)只要情况允许就可委派别人分担工作。事必躬亲会使自己疲惫不堪,而且永远也做不完。不妨请同事帮忙,或让助手更努力地投入。
11)做灵活的日程安排,当你需要时便可以忙中偷闲。例如,在中午加班,然后早一小时离开办公室去健身,或是每天工作10个小时,然后用星期五来赴约会、看医生。
12)在离开办公室之前开列次日工作的清单,这样第二天早晨一来便可以全力以赴。
计划习惯,就等于计划成功。
凡事制定计划有个名叫约翰·戈达德的美国人,当他15岁的时候,就把自己一生要做的事情列了一份清单,被称做"生命清单"。在这份排列有序的清单中,他给自己所要攻克的127个具体目标。比如,探索尼罗河、攀登喜马拉雅山、读完莎士比亚的著作、写一本书等。在44年后,他以超人的毅力和非凡的勇气,在与命运的艰苦抗争中,终于按计划,一步一步地实现了106个目标,成为一名卓有成就的电影制片人、作家和演说家。
中国有句老话:"吃不穷,喝不穷,没有计划就受穷。"尽量按照自己的目标,有计划地做事,这样可以提高工作效率,快速实现目标。
(3)养成锻炼身体的好习惯
增强保健意识
计划习惯,就等于计划成功。如果你想成就一番事业,你就必须有一个健康的身体;要想身体健康,首先要有保健意识。
我认识一个大学教师,身体一直很健康。早些时候,我们经常在一起玩。在谈及各人身体状况时,他说肾部偶尔有轻微不适的感觉。我们曾劝他去医院检查一下,但他自恃身体健康,不以为意。直至后来感觉比较疼痛,其爱人才强迫他去检查。诊断结果是晚期肾癌。虽经手术化疗的等治疗措施,但终未能保住生命,死时才39岁。此前,他曾因学校分房、评职称不如意,心情一直抑郁,他的病和情绪有关,但如果他保健意识强,及早去检查,完全有可以进行预防,消患于未萌。保健意识差,让他付出了生命的代价。
如何落实保健意识呢?一是要有生命第一、健康第一的意识,有了这种意识,你就会善待自己的身体、自己的心理,而不会随意糟踏自己的身体。二是要注意掌握一些相关的知识。三是要使自己有一个对身体应变机制:定期去医院做身体检查;身体觉得有不适的地方,应及早去医院检查;在有条件的情况下,可以请一个保健医生,给自己的健康提出忠告。
▲有计划地锻炼身体
锻炼身体的重要性已经越来越多地为人们所接受,但我感觉很多人只停留在重视的意识阶段,而缺乏相应的行动。我认为锻炼既要针对特定工作姿势所能引发的相应疾病有目的地进行,以防止和治疗相应的疾病,更要把锻炼当作一种乐趣,养成锻炼的习惯。
因为工作需要,我经常与客户打交道,并因处理突发事情四处奔忙,这在一定程度起到了锻炼身体的作用,同时,我还每周坚持游泳一到两次,以保证有足够的精力去做工作,去享受生活。
身体锻炼,就像努力争取成功一样,贵在坚持。
除上述两点以,注意饮食结构,合理膳食,以及注意养成好的卫生习惯等,都是养成健康习惯的组成部分。
总之,健康是"革命"的本钱,是成功的保证。健康成就自己。
(4)不断学习的好习惯"万般皆下品,唯有读书高"的年代已经过去了,但是养成读书的好习惯则永远不会过时。
哈利·杜鲁门是美国历史上著名的总统。他没有读过大学,曾经营农场,后来经营一间布店,经历过多次失败,当他最终担任政府职务时,已年过五旬。但他有一个好习惯,就是不断地阅读。多年的阅读,使杜鲁门的知识非常渊博。他一卷一卷地读了《大不列颠百科全书》以及所有查理斯·狄更斯和维克多·雨果的小说。此外,他还读过威廉·莎士比亚的所有戏剧和十四行诗等。
杜鲁门的广泛阅读和由此得到的丰富知识,使他能带领美国顺利度过第二次世界大战的结束时期,并使这个国家很快进入战后繁荣。他懂得读书是成为一流领导人的基础。读书还使他在面对各种有争议的、棘手的问题时,能迅速做出正确的决定。例如,在20世纪50年代他顶住压力把人们敬爱的战争英雄道格拉斯·麦克阿瑟将军解职。
他的信条是:"不是所有的读书人都是一名领袖,然而每一位领袖必须是读书人。"
美国前任总统克林顿说:在19世纪获得一小块土地,就是起家的本钱;而21世纪,人们最指望得到的赠品,再也不是土地,而联邦政府的奖学金。因为他们知道,掌握知识就是掌握了一把开启未来大门的钥匙。"
每一个成功者都是有着良好阅读习惯的人。世界500家大企业的CEO至少每个星期要翻阅大概30份杂志或图书资讯,一个月可以翻阅100多本杂志,一年要翻阅1000本以上。
世界500家大企业的CEO至少每个星期要翻阅大概30份杂志或图书资讯,一个月可以翻阅100多本杂志,一年要翻阅1000本以上。如果你每天读15分钟,你就有可能在一个月之内读完一本书。一年你就至少读过12本书了,10年之后,你会读过总共120本书!想想看,每天只需要抽出15分钟时间,你就可以轻易地读完120本书,它可以帮助你在生活的各方面变得更加富有。如果你每天花双倍的时间,也就是半个小时的话,一年就能读25本书--10年就是250本!
我觉得,每一个想在35岁以前成功的人,每个月至少要读一本书,两本杂志。
(5)谦虚的好习惯
一个人没有理由不谦虚。相对于人类的知识来讲,任何博学者都只能是不及格。
著名科学家法拉第晚年,国家准备授予他爵位,以表彰他在物理、化学方面的杰出贡献,但被他拒绝了。法拉第退休之后,仍然常去实验室做一些杂事。一天,一位年轻人来实验室做实验。他对正在扫地的法拉第说道:"干这活,他们给你的钱一定不少吧?"老人笑笑,说道:"再多一点,我也用得着呀。""那你叫什么名字?老头?""迈克尔·法拉第。"老人淡淡地回答道。年轻人惊呼起来:"哦,天哪!您就是伟大的法拉第先生!""不",法拉第纠正说,"我是平凡的法拉第。"
谦虚不仅是一种美德,更是是一种人生的智慧,是一种通过贬低自己来保护自己的计谋。
(6)自制的好习惯
任何一个成功者都有着非凡的自制力。
三国时期,蜀相诸葛亮亲自率领蜀国大军北伐曹魏,魏国大将司马懿采取了闭城休战、不予理睬的态度对付诸葛亮。他认为,蜀军远道来袭,后援补给必定不足,只要拖延时日,消耗蜀军的实力,一定能抓住良机,战胜敌人。
诸葛亮深知司马懿沉默战术的利害,几次派兵到城下骂阵,企图激怒魏兵,引诱司马懿出城决战,但司马懿一直按兵不动。诸葛亮于是用激将法,派人给司马懿送来一件女人衣裳,并修书一封说:"仲达不敢出战,跟妇女有什么两样。你若是个知耻的男儿,就出来和蜀军交战,若不然,你就穿上这件女人的衣服。""士可杀不可辱。"这封充满侮辱轻视的信,虽然激怒了司马懿,但并没使老谋深算的司马懿改变主意,他强压怒火稳住军心,耐心等待。
相持了数月,诸葛亮不幸病逝军中,蜀军群龙无首,悄悄退兵,司马懿不战而胜。
抑制不住情绪的人,往往伤人又伤己如果司马懿不能忍耐一时之气,出城应战,那么或许历史将会重写。
现代社会,人们面临的诱惑越来越多,如果人们缺乏自制力,那么就会被诱惑牵着鼻子走,偏离成功的轨道。
(7)幽默的好习惯
有人说,男人需要幽默,就像女人需要一个漂亮的脸蛋一样重要。
男人需要幽默,就像女人需要一个漂亮的脸蛋一样重要。美国第16任总统林肯长相丑陋,但他从不忌讳这一点,相反,他常常诙谐地拿自己的长相开玩笑。在竞选总统时,他的对手攻击他两面三刀,搞阴谋诡计。林肯听了指着自己的脸说:"让公众来评判吧。如果我还有另一张脸的话,我会用现在这一张吗?"还有一次,一个反对林肯的议员走到林肯跟前挖苦地问:"听说总统您是一位成功的自我设计者?""不错,先生。"林肯点点头说,"不过我不明白,一个成功的设计者,怎么会把自己设计成这副模样?"林肯就是这种幽默的方法,多次成功地化解了可能出现的尴尬和难堪场面。
没有幽默的男人不一定就差,但懂得幽默的男人一定是一个优秀的人,懂得幽默的女人更是珍稀动物。
(8)微笑的好习惯
微笑是大度、从容的表现,也是交往的通行证。
举世闻名的希尔顿大酒店,其创建人希尔顿在创业之初,经过多年探索,最终发现了一条简单、易行、不花本钱的经营秘诀--微笑。从此,他要求所有员工:无论饭店本身遭遇到什么困难,希尔顿饭店服务员脸上的微笑永远是属于顾客的阳光。这束"阳光"最终使希尔顿饭店赢得了全世界一致好评。
在欧美发达国家,人们见面都要点头微笑,使人们相互之间感到很温暖。而在中国,如果你在大街上向一个女士微笑,那么你可能被说成"有病"。向西方人学习,让我们致以相互的微笑吧。
从古至今,敬业是所有成功人士最重要的品质之一。
(9)敬业、乐业的好习惯
敬业是对渴望成功的人对待工作的基本要求,一个不敬业的人很难在他所从事的工作中做出成绩。
美国标准石油公司有一个叫阿基勃特的小职员,开始并没有引起人们的特别注意。他的敬业精神特别强,处处注意维护和宣传企业的声誉。在远行住旅馆时总不忘记在自己签名的下方写上"每桶四美元的标准石油"字样,在给亲友写信时,甚至在打收条时也不例外,签名后总不忘记写那几个字。为此,同事们都叫他"每桶四美元"。这事被公司的董事长洛克菲勒知道了,他邀请阿基勃特共进晚餐,并号召公司职员向他学习。后来,阿基勃特成为标准石油公司的第二任董事长。
3.35岁以前成功必须戒除的9大恶习
坏习惯使成功寸步难行。
与建立良好习惯相应的,是克服不良习惯。不破不立,不改掉不良习惯,好习惯是难以建立起来的。
古希腊的佛里几亚国王葛第士以非常奇妙的方法,在战车的轭打了一串结。他预言:谁能打开这个结,就可以征服亚洲。一直到公元前334年还没有一个人能将绳结打开。这时。亚历山大率军入侵小亚细亚,他来到葛第士绳结前,不加考虑便拔剑砍断了它。后来,他果然一举占领了比希腊大50倍的波斯帝国。
一个孩子在山里割草,不小心被毒蛇咬伤了脚。孩子疼痛难忍,而医院在远处的小镇上。孩子毫不犹豫地用镰刀割断受伤的脚趾,然后忍着巨痛艰难地走到医院。虽然缺少了一个脚趾,但这个孩子以短暂的疼痛保住了自己的生命。
改掉坏习惯,就应该有亚历山大的气概,就应有那个小孩的果断和勇敢,彻底改掉坏习惯,让好习惯引领自己走向成功。
以下这9大恶习是你必须戒除的:
1)经常性迟到。你上班或开会经常迟到吗?迟到是造成使老板和同事反感的种子,它传达出的信息:你是一个只考虑自己、缺乏合作精神的人。
2)拖延。虽然你最终完成了工作,但拖后腿使你显得不胜任。为什么会产生延误呢?如果是因为缺少兴趣,你就应该考虑一下你的择业;如果是因为过度追求尽善尽美,这毫无疑问会增多你在工作中的延误。社会心理学专家说:很多爱拖延的人都很害怕冒险和出错,对失败的恐惧使他们无从下手。
3)怨天尤人。这几乎是失败者共同的标签。一个想要成功的人在遇到挫折时,应该冷静地对待自己所面临的问题,分析失败的原因,进而找到解决问题的突破口。
4)一味取悦他人。一个真正称职的员工应该对本职工作内存在的问题向上级说明并提出相应的解决办法,而不应该只是附和上级的决定。对于管理者,应该有严明的奖惩方式,而不应该做"好好先生",这样做虽然暂时取悦了少数人,却会失去大多数人的支持。
5)传播流言。每个人都可能会被别人评论,也会去评论他人,但如果津津乐道的是关于某人的流言蜚语,这种议论最好停止。世上没有不透风的墙,你今天传播的流言,早晚会被当事人知道,又何必去搬石头砸自己的脚?所以,流言止于智者。
6)对他人求全责备、尖酸刻薄。每个人在工作中都可能有失误。当工作中出现问题时,应该协助去解决,而不应该一味求全责备。特别是在自己无法做到的情况下,让自己的下属或别人去达到这些要求,很容易使人产生反感。长此以往,这种人在公司没有任何威信而言。
7)出尔反尔。已经确定下来的事情,却经常做变更,就会让你的下属或协助员工无从下手。你做出的承诺,如果无法兑现,会在大家面前失去信用。这样的人,难以担当重任。
8)傲慢无礼。这样做并不能显得你高人一头,相反会引起别人的反感。因为,任何人都不会容忍别人瞧不起自己。傲慢无礼的人难以交到好的朋友。人脉就是财脉,年轻时养成这种习惯的人,相信你很难取得成功。
9)随大流。人们可以随大流,但不可以无主见。如果你习惯性地随大流,那你就有可能形成思维定势,没有自己的主见,或者既便有,也不敢表达自己的主见,而没有主见的人是不会成功的。( 世界经理人)
没能发财的十大原因
1、不明白财富的定义;不明白成功的镜像规律。
汗,还真不知道什么叫财富。再怯怯地问一声:啥叫镜像啊?
2、没有致富的心态与观念
承认自己没有,比如有致富心态的,看到一个铺面门庭若市,他会想到那里到底卖什么东西这么吸引人,回去自己也捣鼓点卖。而我只是想挤进去买点便宜货。
3、没有理财与致富的规划
又一条没有。从来没为自己做过规划,划了也坚持不了三个月。
4、理财方式与训练方式不恰当
来论坛之后才学着理财。方法正在学习中。
5、大多数人没有投资在高报酬的领域中
这个肯定啦,如果有谁知道投资可获得高报酬的,除了股票和传销,请你马上告诉我。
6、没有用商业手法控制支出
这个我也有。想花钱的时候就告诉自己先花了再说,不考虑控制。
7、不善于创造把握理财与致富的机会
这个我觉得我没有,总认为是机会还没来,来了我当然要抓住。
8、无创新意识不知如何决策
承认。确实不愿意创新。听了那么多关于货币基金的介绍,可我还是一门心思在储蓄,因为懒、嫌麻烦。
9、未以正确的思考方式解决问题
不承认,我一直都在我认为正确的道路上行走着。
10、没有找到一位理财与致富向导
确实没有。不过来理财生活以后又感觉向导太多了,有点晕。(阿里巴巴)
三种策略让你赚到100万
大多数年轻人的目标是100万元,而且是愈早实现愈好。但是根据网络调查显示,有七成人认为,30岁时至少应该先拥有10万元存款,但却只有一成七的人能够办到。这就表示有相当多的年轻人,连10万元的目标都还没能达成,百万财富更是一个遥远的梦想。
于是在社会上各种致富法纷纷出笼。譬如嫁入豪门、娶个富家女、每期买彩票,这些方法似乎是最快、但也是最不切实际的。到底有没有机会靠着自己的努力,提早赚到百万财富,答案当然是"有",这里有短、中、长期三套战略,供你参考。
2年战略:高杠杆工具才能小兵立大功
如果想两年就赚到百万财富,最可能实现梦想的途径就是利用高杠杆投资工具。虽然风险超高,但是报酬也高,想要以小搏大、倍数获利,就要正确运用这种工具。只要你对趋势敏感,行情不论走多或是走空,都有获利机会。
高杠杆投资凭借的不是运气,而是精准判断盘势,冷静面对大盘起落,情绪绝不随着输赢起舞。但所谓"高收益高风险",想要两年就得到暴利,等于是走着钢索赚钱,因为期货或是选择权杠杆高,当看错趋势时,几十万元很快就输光出场,是一条风险最高的求财途径。因此,先模拟练功并严格控制投资金额,是激进主义者最重要的自保之道。
5年战略:做老板、当top sales
如果自认为用期指或是选择权赚大钱,心脏不够强、武艺不够高的话,年限不妨放宽一点,定5年战略,也就是努力创业当老板、甚至是加盟总部的老板、或是努力成为业务高手。
什么样的创业能够5年就净赚百万元,当然是要能引领潮流或是抓住特殊机遇的创业。
程度更高段的赚钱方法则是当一群老板的老板,也就是成立加盟连锁总部,只要能够研发出独特口味、或是独特经营模式,而且能够复制标准化程序,稳定收取加盟店上缴的权利金。
当然创业的成本高,学问也很大。如果不愿意当老板,只想继续当伙计赚大钱,不妨选择产品单价高、抽佣也高、制度完善的业务体系,只要用对方法,就可以成为个中高手
10年战略:运用多种工具保守理财
如果自认投资手段不佳,也不适合创业当老板,或是不擅与人打交道,无法成为业务高手的话,那么便得回归正统的理财管道,将累积财富的时间拉长至10年,积极开源、努力储蓄守成,透过定期储蓄,或是投资定存概念股,每年赚取股利,或是把钱交给专家理财,透过定期定额基金投资,逐步累积资产。
更传统的方式是投资房地产,虽然国内房地产价格还有向下修正的空间,但只要选对地段,还是可以找到极具增值潜力的房子,不管是自住或投资,都是一种稳健的资产累积方式。
你是属于急功近利型的兔子?还是稳扎稳打型的乌龟?其实都有适合的致富计划,但要再提醒的是,不管选择哪一种计划,想要提前致富,一定要做足功课,懂得深入领受实践,百万财富将不是遥远梦想!(理财加油站)
posted @
2008-12-19 08:07 幽幽 阅读(247) |
评论 (0) |
编辑 收藏
彩色图像转换为黑白图像时需要计算图像中每像素有效的亮度值,通过匹配像素
亮度值可以轻松转换为黑白图像。
计算像素有效的亮度值可以使用下面的公式:
Y=0.3RED+0.59GREEN+0.11Blue
然后使用 Color.FromArgb(Y,Y,Y) 来把计算后的值转换
转换代码可以使用下面的方法来实现:
C#
1public Bitmap ConvertToGrayscale(Bitmap source)
2
3{
4
5 Bitmap bm = new Bitmap(source.Width,source.Height);
6
7 for(int y=0;y<bm.Height;y++)
8
9 {
10
11 for(int x=0;x<bm.Width;x++)
12
13 {
14
15 Color c=source.GetPixel(x,y);
16
17 int luma = (int)(c.R*0.3 + c.G*0.59+ c.B*0.11);
18
19 bm.SetPixel(x,y,Color.FromArgb(luma,luma,luma));
20
21 }
22
23 }
24
25 return bm;
26
27}
VB
1Public Function ConvertToGrayscale()Function ConvertToGrayscale()Function ConvertToGrayscale()Function ConvertToGrayscale(ByVal source As Bitmap) as Bitmap
2
3 Dim bm as new Bitmap(source.Width,source.Height)
4
5 Dim x
6
7 Dim y
8
9 For y=0 To bm.Height
10
11 For x=0 To bm.Width
12
13 Dim c as Color = source.GetPixel(x,y)
14
15 Dim luma as Integer = CInt(c.R*0.3 + c.G*0.59 + c.B*0.11)
16
17 bm.SetPixel(x,y,Color.FromArgb(luma,luma,luma)
18
19 Next
20
21 Next
22
23 Return bm
24
25End Function
posted @
2008-10-20 13:10 幽幽 阅读(985) |
评论 (0) |
编辑 收藏
从该DC中得到位图数据
在DC加载了RGB24位图,如何从该DC中得到位图数据,给些代码好吗?
回复人: windows_editor(等咱有钱了,每天早上喝两大碗豆浆)
LONG GetBitmapBits(
HBITMAP hbmp, // handle to bitmap
LONG cbBuffer, // number of bytes to copy
LPVOID lpvBits // buffer to receive bits
); //从位图中取RGB值
SetBitmapBits 将位图的数据加上头格式转化为位图
回复人: bluebohe(薄荷)
HBITMAP GetSrcBit(HDC hDC,DWORD BitWidth, DWORD BitHeight)
{
HDC hBufDC;
HBITMAP hBitmap, hBitTemp;
//创建设备上下文(HDC)
hBufDC = CreateCompatibleDC(hDC);
//创建HBITMAP
hBitmap = CreateCompatibleBitmap(hDC, BitWidth, BitHeight);
hBitTemp = (HBITMAP) SelectObject(hBufDC, hBitmap);
//得到位图缓冲区
StretchBlt(hBufDC, 0, 0, BitWidth, BitHeight,
hDC, 0, 0, BitWidth, BitHeight, SRCCOPY);
//得到最终的位图信息
hBitmap = (HBITMAP) SelectObject(hBufDC, hBitTemp);
//释放内存
DeleteObject(hBitTemp);
::DeleteDC(hBufDC);
return hBitmap;
}
BOOL SaveBmp(HBITMAP hBitmap, CString FileName)
{
//设备描述表
HDC hDC;
//当前分辨率下每象素所占字节数
int iBits;
//位图中每象素所占字节数
WORD wBitCount;
//定义调色板大小, 位图中像素字节大小 ,位图文件大小 , 写入文件字节数
DWORD dwPaletteSize=0, dwBmBitsSize=0, dwDIBSize=0, dwWritten=0;
//位图属性结构
BITMAP Bitmap;
//位图文件头结构
BITMAPFILEHEADER bmfHdr;
//位图信息头结构
BITMAPINFOHEADER bi;
//指向位图信息头结构
LPBITMAPINFOHEADER lpbi;
//定义文件,分配内存句柄,调色板句柄
HANDLE fh, hDib, hPal,hOldPal=NULL;
//计算位图文件每个像素所占字节数
hDC = CreateDC("DISPLAY", NULL, NULL, NULL);
iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES);
DeleteDC(hDC);
if (iBits <= 1) wBitCount = 1;
else if (iBits <= 4) wBitCount = 4;
else if (iBits <= 8) wBitCount = 8;
else wBitCount = 24;
GetObject(hBitmap, sizeof(Bitmap), (LPSTR)&Bitmap);
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = Bitmap.bmWidth;
bi.biHeight = Bitmap.bmHeight;
bi.biPlanes = 1;
bi.biBitCount = wBitCount;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrImportant = 0;
bi.biClrUsed = 0;
dwBmBitsSize = ((Bitmap.bmWidth * wBitCount + 31) / 32) * 4 * Bitmap.bmHeight;
//为位图内容分配内存
hDib = GlobalAlloc(GHND,dwBmBitsSize + dwPaletteSize + sizeof(BITMAPINFOHEADER));
lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib);
*lpbi = bi;
// 处理调色板
hPal = GetStockObject(DEFAULT_PALETTE);
if (hPal)
{
hDC = ::GetDC(NULL);
hOldPal = ::SelectPalette(hDC, (HPALETTE)hPal, FALSE);
RealizePalette(hDC);
}
// 获取该调色板下新的像素值
GetDIBits(hDC, hBitmap, 0, (UINT) Bitmap.bmHeight, (LPSTR)lpbi + sizeof(BITMAPINFOHEADER) +dwPaletteSize, (BITMAPINFO *)lpbi, DIB_RGB_COLORS);
//恢复调色板
if (hOldPal)
{
::SelectPalette(hDC, (HPALETTE)hOldPal, TRUE);
RealizePalette(hDC);
::ReleaseDC(NULL, hDC);
}
//创建位图文件
fh = CreateFile(FileName, GENERIC_WRITE,0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (fh == INVALID_HANDLE_VALUE) return FALSE;
// 设置位图文件头
bmfHdr.bfType = 0x4D42; // "BM"
dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize;
bmfHdr.bfSize = dwDIBSize;
bmfHdr.bfReserved1 = 0;
bmfHdr.bfReserved2 = 0;
bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwPaletteSize;
// 写入位图文件头
WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
// 写入位图文件其余内容
WriteFile(fh, (LPSTR)lpbi, dwDIBSize, &dwWritten, NULL);
//清除
GlobalUnlock(hDib);
GlobalFree(hDib);
CloseHandle(fh);
return TRUE;
}
posted @
2008-09-28 16:19 幽幽 阅读(2238) |
评论 (0) |
编辑 收藏
转自CSDN
在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。
其格式一般为: #Pragma Para
其中Para 为参数,下面来看一些常用的参数。
(1)message 参数。 Message 参数是我最喜欢的一个参数,它能够在编译信息输出窗
口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:
#Pragma message(“消息文本”)
当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。
当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法
#ifdef _X86
#Pragma message(“_X86 macro activated!”)
#endif
当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_
X86 macro activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了
。
(2)另一个使用得比较多的pragma参数是code_seg。格式如:
#pragma code_seg( ["section-name"[,"section-class"] ] )
它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。
(3)#pragma once (比较常用)
只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。
(4)#pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。BCB可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。
有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma startup指定编译优先级,如果使用了#pragma package(smart_init) ,BCB就会根据优先级的大小先后编译。
(5)#pragma resource "*.dfm"表示把*.dfm文件中的资源加入工程。*.dfm中包括窗体
外观的定义。
(6)#pragma warning( disable : 4507 34; once : 4385; error : 164 )
等价于:
#pragma warning(disable:4507 34) // 不显示4507和34号警告信息
#pragma warning(once:4385) // 4385号警告信息仅报告一次
#pragma warning(error:164) // 把164号警告信息作为一个错误。
同时这个pragma warning 也支持如下格式:
#pragma warning( push [ ,n ] )
#pragma warning( pop )
这里n代表一个警告等级(1---4)。
#pragma warning( push )保存所有警告信息的现有的警告状态。
#pragma warning( push, n)保存所有警告信息的现有的警告状态,并且把全局警告
等级设定为n。
#pragma warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的
一切改动取消。例如:
#pragma warning( push )
#pragma warning( disable : 4705 )
#pragma warning( disable : 4706 )
#pragma warning( disable : 4707 )
//.......
#pragma warning( pop )
在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)。
(7)pragma comment(...)
该指令将一个注释记录放入一个对象文件或可执行文件中。
常用的lib关键字,可以帮我们连入一个库文件。
posted @
2008-08-19 11:01 幽幽 阅读(279) |
评论 (0) |
编辑 收藏
一种给窗口添加阴影的方法 华南理工大学微软技术俱乐部 |
|
因为自己很喜欢那些界面做得很漂亮的软件或者使用各种美化界面的软件,如avedesk,samurize等等。其中美化界面的一个重要的方面就是给窗口添加上阴影。虽然OS X已经原生的支持窗口阴影,但是windows要到Longhorn才开始支持原生的窗口阴影。现在如果想实现窗口阴影,一般都会借助第三方的软件,例如windowFX或者YzShadow。其中YzShadow是一个免费软件,我自己也在使用。但是这个软件有个弱点,就是无法为Layered Window添加阴影。而我自己编写的一个简易便条程序Stickies(功能类似OneNote,但是功能简单,小巧。)就是运用了Layered Window来作为软件的界面,于是便自己尝试添加窗口阴影。以下便是添加阴影的方法,写下来与大家讨论一下。
我的程序是在Visual Studio.NET 2003下编写的MFC应用程序。我为了实现窗口阴影创建了一个Shadow的类。首先我们看看各类之间的关系:
1. Class ShadowCastingWindow 该类是一个应用程序的窗口,它会在桌面上投射下阴影。这个类是从CWnd继承而来。 ShadowCastingWindow成员变量: m_Alpha 保存该窗口的透明度值。
ShadowCastingWindow成员函数: BOOL ShadowCastingWindow::CreateWindow( CString wndName, CWnd * pParentWnd ) { BOOL tmp = CWnd::CreateEx( WS_EX_TOOLWINDOW|WS_EX_LAYERED , … , WS_POPUP|WS_VISIBLE , … ); m_Shadow.CreateShadow( this, m_Alpha ); }
该函数用于创建应用程序的窗口并创建阴影。请留意CreateEx中窗口属性WS_EX_...和WS_...的取值,这使得该应用程序的窗口是一个没有标题栏的Layered Windows。是否有标题栏对于下文Shadow类中求遮挡窗口的大小会有所不同,这必须通过一个判断逻辑或者根据程序的应用不同编写好代码。对于Layered Windows会有两种刷新模式,一种就是传统的消息机制,就是操作系统自动地在适当的时候发送WM_PAINT的消息给应用程序窗口,应用程序窗口则相应该消息,对窗口进行刷新;另一种方式则是在Windows2000以后才支持的UpdateLayeredWindow的机制,在这种机制下,应用程序不再处理WM_PAINT消息,所有的刷新均由用户在内存中的一个绘图上下文中绘制好图像之后再通过UpdateLayeredWindow绘制到屏幕上,只要经过一次绘制,窗口的图像便会保存在一块预订好的内存区域内,如果窗口的图像没有改变那么操作系统便会自动地处理刷新。
void ShadowCastingWindow::OnSizing(UINT fwSide, LPRECT pRect) { CWnd::OnSizing(fwSide, pRect); m_Shadow.OnShadowCastingWndNewSize(pRect->right - pRect->left, pRect->bottom - pRect->top); }
void ShadowCastingWindow::OnSize(UINT nType, int cx, int cy) { CWnd::OnSize(nType, cx, cy); m_Shadow.OnShadowCastingWndNewSize(cx,cy); }
void ShadowCastingWindow::OnMoving(UINT fwSide, LPRECT pRect) { CWnd::OnMoving(fwSide, pRect); m_Shadow.OnShadowCastingWndNewPos(pRect->left, pRect->top ); }
void ShadowCastingWindow::OnMove(int x, int y) { CWnd::OnMove(x, y); m_Shadow.OnShadowCastingWndNewPos(x, y ); }
这四个事件函数都是处理应用程序窗口大小或者位置变化的。只需要在其中调用Shadow类中相应的处理函数即可,Shadow便会自动地更改大小或者移动位置。可能有人会问为什么需要显式地调整Shadow的位置和大小?因为从下文可以看到Shadow其实也是一个Layered Window,没有父窗口,所以操作系统不可以自动地保持两者的相对位置。
void ShadowCastingWindow::SetOpacity( int alpha ) { m_Alpha = alpha; m_Shadow.SetAlpha( alpha ); SetLayeredWindowAttributes( 0, (BYTE)m_Alpha, LWA_ALPHA ); Invalidate(); }
处理应用程序窗口透明度变化的函数,其中调用了阴影Shadow对于透明度变化的处理函数。并刷新应用程序窗口。
2. Class Shadow 该类继承与CWnd类。 Shadow成员变量: CWnd * m_pShadowCastingWindow; 指向父窗口—需要投射阴影的窗口的指针。 int m_Alpha; 当前阴影的透明度。 int m_DeltaTop; int m_DeltaLeft; int m_DeltaRight; int m_DeltaButtom; 用于表示阴影的尺寸。计算方法如下:
Shadow成员函数: BOOL Shadow::CreateShadow(CWnd * pShadowCastingWnd, int alpha ) { //根据投射阴影的窗口的尺寸和各参数计算出阴影的尺寸。 CRect rect; pShadowCastingWnd->GetWindowRect(&rect); rect.top += m_DeltaTop; rect.left -= m_DeltaLeft; rect.right += m_DeltaRight; rect.bottom += m_DeltaButtom; m_Alpha = alpha; BOOL tmp = CWnd::CreateEx( WS_EX_TOOLWINDOW|WS_EX_LAYERED ,… , WS_POPUP|WS_VISIBLE , rect , …);
m_IsCreated = true; CustomizedPaint(); return tmp; }
创建阴影,由于阴影必须是没有标题栏的,而且因为要绘制半透明的像素所以必须使用Layered Window。
void Shadow::CustomizedPaint(void) { if ( !m_IsCreated ) return;
BLENDFUNCTION blendPixelFunction= {AC_SRC_OVER, 0, m_Alpha, AC_SRC_ALPHA}; POINT ptWindowScreenPosition = {rect.left, rect.top}; POINT ptSrc = {0, 0}; SIZE szWindow = {rect.Width(), rect.Height()};
CDC * dcScreen = GetDesktopWindow()->GetDC(); CDC dcMemory; dcMemory.CreateCompatibleDC( dcScreen );
//----------------------------------- //把要绘制的内容绘制在dcMemory里。对于Shadow需要把投射阴影窗口所覆盖的区 //域剪裁掉 //-----------------------------------
UpdateLayeredWindow( dcScreen, &ptWindowScreenPosition, &szWindow, &dcMemory, &ptSrc, 0, &blendPixelFunction, ULW_ALPHA);
GetDesktopWindow()->ReleaseDC(dcScreen); dcMemory.DeleteDC(); } 根据不同程序需要加上适当的绘制流程。比如可以通过画一个长方形来表示阴影,这个效果自然就比较差;也可以利用一些在Photoshop中处理好的阴影图片把它做适当的大小调整作为窗口的阴影这样更容易做出阴影边缘柔化的效果。这个CustomizedPaint只需要在窗口的内容被改变的时候才需要重新调用,其他时候系统会自动管理已经绘制的图像,用它来刷新窗口,而不需要重新绘制。
BOOL Shadow::PreCreateWindow(CREATESTRUCT& cs) { cs.style &= ~WS_BORDER; cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS, ::LoadCursor( NULL, IDC_CURSOR ), NULL, NULL); return CWnd::PreCreateWindow(cs); } 通过修改默认的cs.lpszClass使得窗口不再自动重画背景。
void Shadow::OnShadowCastingWndNewSize( int x, int y ) { if ( !m_IsCreated ) return; SetWindowPos( m_pShadowCastingWindow,0,0,x+m_DeltaLeft+m_DeltaRight,y-m_DeltaTop+m_DeltaButtom, SWP_NOMOVE ); CustomizedPaint(); }
提供该投射阴影的窗口的接口函数,当投射阴影的窗口大小改变的时候便调用这个函数把新的窗口位置传给Shadow,Shadow便会改变自己的大小,并重绘窗口。
void Shadow::OnShadowCastingWndNewPos( int x, int y ) { if ( !m_IsCreated ) return; SetWindowPos( m_pShadowCastingWindow, x-m_DeltaLeft, y+m_DeltaTop, 0, 0, SWP_NOSIZE ); }
提供该投射阴影的窗口的接口函数,当投射阴影的窗口位置改变的时候便调用这个函数把新的窗口位置传给Shadow,Shadow便会改变自己的位置。注意了,这里并不需要重绘窗口,因为窗口的内容并没有改变。
void Shadow::SetAlpha( int alpha ) { m_Alpha = alpha; CustomizedPaint(); }
提供该投射阴影的窗口的接口函数,当投射阴影的窗口的透明度改变的时候便调用这个函数把新透明度传给Shadow,Shadow便会改变自己的透明度,并重绘窗口。
下面给出一个我自己写的程序的效果图:
3. 结论:
上面就简单地介绍了一个绘制窗口阴影的方法。这种方法基本上可以适用于各种类型的窗口,其中需要注意一下几点:
1. 在于Shadow::CreateShadow中如何正确取得投射阴影窗口m_pShadowCastingWindow的大小然后计算出阴影窗口的大小。
2. Shadow::CustomizedPaint中如何更高效的绘制阴影,例如剪裁掉投射阴影窗口遮挡住的窗口内容,避免绘制时出现闪烁。同时如何正确使用好UpdateLayeredWindow这个系统调用会是实现绘制阴影的关键。当然在当前的设计下,我们可以在CustomizePaint中绘制任何的东西,而不一定是阴影。? 大家可以在这里发挥想象力,让窗口更加绚丽多彩。
其实这个程序只要让他通过钩子函数与特定的Win32API挂钩,完全可以写出一个可以给系统中所有窗口加上阴影效果的小软件。大家不妨试试。如果做出来了,记得给我一份。
注:文章中的代码都是示意性的,都是通过我自己写的程序删减后得到,未必能通过测试。旨在说明一些关键步骤需要注意的地方。如果问题欢迎email讨论。
|
posted @
2008-08-17 12:18 幽幽 阅读(5281) |
评论 (3) |
编辑 收藏
这篇文章翻译至MSDN2005,给自己学习,也给所有觉得它有用的人,文中难免有翻译不到位或者错误的地方,望高手指正。译者:欧昊川(转载麻烦注明出处及译者)
2008年5月4日
这个概述讨论了窗口的一些特性,如窗口类型、状态、大小及位置。
1、窗口类型(WindowStyles)
这一节描述层叠窗口、弹出窗口、子窗口、分层窗口、仅处理消息的窗口这五种类型。
1.1层叠窗口(OverlappedWindows)
层叠窗口是一个具有标题栏、边框和客户区的顶层窗口;也就是说它适合做为应用程序主窗口。它也可以具有一个系统菜单,最小和最大化按钮,以及滚动条。一个层叠窗口被典型地用于包含所有上述组件的应用程序主窗口。
通过在CreateWindowEx中指定WS_OVERLAPPED或者WS_OVERLAPPEDWINDOW样式,一个应用程序就能创建一个层叠窗口。假如你使用第一个样式,那么创建的窗口就具有一个标题栏和边框;假如你使用第二个,那么窗口就具有一个标题栏,可以调整大小的边框,系统菜单,以及最大最小化按钮。
1.2弹出窗口(Pop-upWindows)
弹出窗口是一个非凡的层叠窗口,它被用于显示在应用程序主窗口之外的对话框,消息框以及其他临时窗口。标题栏对弹出窗口来说是可选的;除此之外,弹出窗口跟具有WS_OVERLAPPED样式的层叠窗口一样。
你可以通过在CreateWindowEx中指定WS_POPUP样式来创建一个弹出窗口。假如要使用标题栏,就加入WS_CAPTION样式。使用WS_POPUPWINDOW样式来创建一个含有边框和系统菜单的弹出窗口。WS_CAPTION样式必须与WS_POPUPWINDOW样式一起使用才能使系统菜单可见。
1.3子窗口(ChildWindows)
子窗口具有WS_CHILD样式并且它被限制在其父窗口的客户区中。应用程序典型地使用子窗口来把其父窗口的客户区划分成几个功能区域。你可以通过在CreateWindowEx中指定WS_CHILD样式来创建子窗口。
子窗口必须具有一个父窗口。父窗口可以是一个层叠窗口,弹出窗口,或者另外一个子窗口。你可以在CreateWindowEx中指定父窗口。假如你在CreateWindowEx中指定了WS_CHILD样式但是没有指定父窗口,那么系统不会创建这个子窗口。
子窗口只具有一个客户区而没有其他特性,除非这些特性被明确的请求。应用程序可以为子窗口添加标题栏,系统菜单,最小化最大化按钮,边框,以及滚动条。但是子窗口不能具有自定义菜单。假如应用程序指定了一个自定义菜单句柄,那么无论是在它注册这个子窗口类还是创建这个子窗口时,这个菜单句柄都被忽略。假如没有指定边框样式,系统将创建一个无边框窗口。应用程序可以使用无边框的子窗口来划分父窗口的客户区假如想保持这种划分对用户是不可见的话。
下面一节讨论窗口的布置、裁剪、与父窗口的关系、消息四个主题。
1.4窗口布置(Positioning)
系统总是相对于父窗口客户区的左上角来放置子窗口。子窗口的任何部分都不会出现在其父窗口的边框之外。假如应用程序创建一个比父窗口大的子窗口,或者移动子窗口使得一个或者所有子窗口超出了父窗口的边框,那么系统会裁剪子窗口,即在父窗口边框之外的部分不被显示。对父窗口产生影响的行为同样会影响子窗口,这些行为如下:
posted @
2008-08-16 13:38 幽幽 阅读(1148) |
评论 (0) |
编辑 收藏
OLE拖放实现
MFC本身的CView类是支持拖放操作的,通过研究CView类的源码,大体知道它的实现原理是这样的:CView类中有一个COleDropTarget类的对象,在视图窗口初始化时,调用COleDropTarget类成员函数Register(),以此在系统中注册该视图窗口为拖放接收窗口。当进行拖放操作的鼠标指针处于视图窗口范围内时,COleDropTarge类会做出反应,它的OnDragEnter、OnDragOver、OnDropEx、OnDrop等成员函数被依次调用,这些函数默认均是调用与其相对应的CView类成员函数OnDragEnter、OnDragOver、OnDropEx、OnDrop等,程序员只需重载这些CView类成员函数,即可对拖动的过程及结果进行控制。
因为COleDropTarget默认只对CView提供支持,所以如果要让其他的窗口支持拖放,我们必须同时对要支持拖放的窗口类和COleDropTarget类进行派生。把对拖放操作具体进行处理的代码封装成派生窗口类的成员函数,然后重载COleDropTarget中对应的五个虚函数,当它接收到拖放动作时,调用窗口派生类的处理函数即可。但这里有一个问题,就是我们怎么知道何时调用派生类的处理函数呢?答案是运用RTTI技术。如果COleDropTarget派生类收到的窗口指针类型,就是我们派生的窗口类,那么就调用它的处理函数,否则调用基类进行处理。
首先生成一个对话框工程,添加二个新类。
第一个类名为CListCtrlEx,父类为CListCtrl。添加完毕后,在CListCtrlEx的定义头文件中加入DECLARE_DYNAMIC(CListCtrlEx),在其实现文件中加入IMPLEMENT_DYNAMIC(CListCtrlEx,CListCtrl),这样就对CListCtrlEx类添加了RTTI运行期类型识别(Run Time Type Information)支持。
第二个类名为COleDropTargetEx,父类为COleDataTarget。
在CListCtrlEx中添加COleDropTargetEx类的对象,并添加下列公有虚函数的声明:
virtual BOOL Initialize();
virtual DROPEFFECT OnDragOver(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);
virtual DROPEFFECT OnDropEx(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, DROPEFFECT dropList, CPoint point);
virtual BOOL OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point);
virtual void OnDragLeave(CWnd* pWnd);
Initialize函数用于注册CListCtrlEx成为拖放接收窗口;
OnDragOver在拖放鼠标进入窗口时被调用。此函数的返回值决定了后续的动作的类型:如果返回DROPEFFECT_MOVE,则产生一个剪切动作;如果返回DROPEFFECT_COPY,则产生一个复制动作,如果返回DROPEFFECT_NONE,则不会产生拖放动作,因为OnDropEx、OnDrop函数将不会被调用(OnDragLeave函数仍会被调用)。
OnDropEx函数会在OnDrop函数之前调用,如果OnDropEx函数没有对拖放动作进行处理,则应用程序框架会接着调用OnDrop函数进行处理。所以必须要在派生类中重载OnDropEx函数——即使什么动作都都没有做——否则我们的OnDrop函数将不会被执行到,因为没有重载的话,将会调用基类的OnDropEx函数,而基类的OnDropEx函数对拖放是进行了处理的——尽管不是我们所想要的动作。当然你也可以把对拖放进行处理的动作放在OnDropEx中——那样就不需要重载OnDrop了。
OnDragLeave函数会在鼠标离开窗口时被调用,在此可以进行一些简单的清理工作。譬如在OnDragEnter或者OnDragOver函数中,我们改变了光标的形态,那么此时我们就应该把光标恢复过来。
这些函数中最重要的是OnDrop函数,拖放动作将在此进行处理,它的全部源码如下:
BOOL CListCtrlEx::OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point)
{
UINT nFileCount = 0;
HDROP hDropFiles = NULL;
HGLOBAL hMemData = NULL;
AfxMessageBox("OnDrop");
if(pDataObject->IsDataAvailable(CF_HDROP))
{
hMemData = pDataObject->GetGlobalData(CF_HDROP);
hDropFiles = (HDROP)GlobalLock((HGLOBAL)hMemData); //锁定内存块
if(hDropFiles != NULL)
{
char chTemp[_MAX_PATH+1] = {0};
nFileCount = DragQueryFile(hDropFiles, 0xFFFFFFFF, NULL, 0);
for(UINT nCur=0; nCur<nFileCount; ++nCur) //遍历取得每个文件名
{
ZeroMemory(chTemp, _MAX_PATH+1);
DragQueryFile(hDropFiles, nCur, (LPTSTR)chTemp, _MAX_PATH+1);
AddAllFiles(chTemp);
}
}
GlobalUnlock(hMemData);
return TRUE;
}
else
{
return FALSE;
}
}
在第二个类COleDropTarget中添加如下对应的函数:
virtual DROPEFFECT OnDragEnter(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);
virtual DROPEFFECT OnDragOver(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);
virtual DROPEFFECT OnDropEx(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, DROPEFFECT dropList, CPoint point);
virtual BOOL OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point);
virtual void OnDragLeave(CWnd* pWnd);
它们的动作都差不多:先用RTTI判断窗口指针pWnd的类型,如果是CListCtrlEx,则调用CListCtrlEx中对应的处理函数,否则调用基类的处理函数。以OnDrop为例:
BOOL COleDropTargetEx::OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point)
{
CListCtrlEx* pListCtrlEx = NULL;
ASSERT_VALID(this);
ASSERT(IsWindow(pWnd->m_hWnd));
if(pWnd->IsKindOf(RUNTIME_CLASS(CListCtrlEx)))
{
pListCtrlEx = (CListCtrlEx*)pWnd;
return pListCtrlEx->OnDrop(pWnd, pDataObject, dropEffect, point);
}
else
{
return COleDropTarget::OnDrop(pWnd, pDataObject, dropEffect, point);
}
}
//倒霉的64K限制,只能再截断了:(
至此,我们成功地为CListCtrlEx添加了文件拖入操作的支持。一个完整的拖放操作,还包括拖出动作,所以必须要为该类再添加拖出操作,即,将列表中的某一项或者多项拖出成为一个文件。这就需要用到另一个类:COleDataSource。具体步骤如下:
在CListCtrlEx中加入一个COleDataSource的实例,并映射列表框的LVN_BEGINDRAG消息处理函数,在此我们添加拖出操作的代码。
实现拖出非常简单,只需要依次调用COleDataSource的三个函数即可:Empty用于清空原先对象中缓存的数据,CacheGlobalData用来缓存数据以进行拖放操作,最后调用DoDragDrop启动本次拖放操作。
但在调用之前,必须要做一些准备工作。主要的任务就是创建一个DROPFILES结构体,并拷贝要拖放的文件名到结构体后的内存中。DROPFILES结构体定义了CF_HDROP剪贴板格式,紧跟它后面的是一系列被拖放文件的路径名。它的定义如下:
typedef struct _DROPFILES
{
DWORD pFiles; //文件名起始地址
POINT pt; //鼠标放下的位置,坐标由fNC成员指定
BOOL fNC; //为TRUE表示适用屏幕坐标系,否则使用客户坐标系
BOOL fWide; //文件名字符串是否使用宽字符
} DROPFILES, FAR* LPDROPFILES;
拖放之前的准备动作的代码如下:
uBufferSize = sizeof(DROPFILES) + uBufferSize + 1;
hMemData = GlobalAlloc(GPTR,uBufferSize);
ASSERT(hMemData != NULL);
lpDropFiles = (LPDROPFILES)GlobalLock(hMemData); //锁定之,并设置相关成员
ASSERT(lpDropFiles != NULL);
lpDropFiles->pFiles = sizeof(DROPFILES);
#ifdef _UNICODE
lpDropFiles->fWide = TRUE;
#else
lpDropFiles->fWide = FALSE;
#endif
//把选中的所有文件名依次复制到DROPFILES结构体后面(全局内存中)
pItemPos = strSelectedList.GetHeadPosition();
pszStart = (char*)((LPBYTE)lpDropFiles + sizeof(DROPFILES));
while(pItemPos != NULL)
{
lstrcpy(pszStart, (LPCTSTR)strSelectedList.GetNext(pItemPos));
pszStart = strchr(pszStart,'\0') + 1; //下次的起始位置是上一次结尾+1
}
准备完毕之后就可以进行拖放了,拖放动作有DoDragDrop函数触发,其原型如下:
DROPEFFECT DoDragDrop(
DWORD dwEffects = DROPEFFECT_COPY|DROPEFFECT_MOVE|DROPEFFECT_LINK, LPCRECT lpRectStartDrag = NULL,
COleDropSource* pDropSource = NULL
);
这里,dwEffects指定了允许施加于本COleDataSource实例之上的动作集:剪切、复制或无动作。
lpRectStartDrag指示拖放操作真正开始的矩形,如果鼠标没有移出该矩形,则拖放操作视作放弃处理。如果本成员设为NULL,则该起始矩形将为一个像素大小。
pDropSource表明拖放所使用的COleDataSource对象。
而该函数的返回值,则表明本次拖放操作所实际产生的效果,至于具体产生何种效果,则由系统决定。譬如在拖放时按住Shift键,将产生剪切效果;按住Ctrl键,将产生复制效果,等等。
拖放的代码如下:
m_oleDataSource.Empty();
m_oleDataSource.CacheGlobalData(CF_HDROP, hMemData);
DropResult = m_oleDataSource.DoDragDrop(DROPEFFECT_MOVE|DROPEFFECT_COPY);
最后一点要注意的是,在Windows NT 4.0以上的系统中,即使实际产生的是DROPEFFECT_MOVE动作,DoDragDrop函数也只返回DROPEFFECT_NONE。产生这个问题的原因在于,Windows NT 4.0的Shell会直接移动文件本身来对移动操作进行优化。返回值DROPEFFECT_MOVE最初的含义,就是通知执行拖放操作的应用程序去删除原位置上的文件。但是因为Shell已经替应用程序完成了这个(删除)动作,所以,函数返回DROPEFFECT_NONE。要想知道文件是否真的被移动了也很简单,只要在函数返回之后检查一下原位置上的文件是否存在就可以了。
Windows 9x系列的操作系统也会对移动进行同样的优化动作,但是它不会返回DROPEFFECT_NONE来代替DROPEFFECT_MOVE。详细的解释参见MS知识库Q182219。
posted @
2008-08-14 19:19 幽幽 阅读(3119) |
评论 (0) |
编辑 收藏