C#使用WinAPI 修改电源设置,临时禁止笔记本合上盖子时睡眠
原文 http://www.cnblogs.com/h46incon/archive/2013/09/03/3299138.html
在 阻止系统自动睡眠的小软件,附C#制作过程 ,弄了一个防止系统睡眠的工具。然后马上发现,新的需求来了:为了保护环境(省钱),在系统设置中,合上盖子时会自动睡眠。那因下载之类的原因,需要临时禁止睡眠的话,又懒得去改设置,而且下次还得改回来。所以没事也是折腾,就研究了怎么用软件实现了。
最开始的思路就是进行Hook,以截断睡眠消息。但是木有找到方法。
然后发现当系统进行睡眠时,会广播一个消息,然后每个软件会有两秒钟(xp和03可以长达20秒)的时间进行善后(PBT_APMSUSPEND event)。虽然可以唤醒睡眠的电脑(System Wake-up Events),但是还没找到方法取消这次睡眠。
最后,我的解决方法时,临时修改电源设置,即将合上盖子的动作设置为啥事不干,然后在需要的时候恢复原来的设置。
Windows下电源管理,及配置工具powercfg
Windows下电源管理方案是这样的。最大的维度是电源配置方案,每套方案包含着一组电源设置。可以更改当前激活的方案,也可以修改每个电源设置的值。
使用系统自带工具powercfg进行电源配置的查看及更改:其中GUID值会在后面用到。


注意到这里:
|
1
2
3
|
电源方案 GUID: a1841308-3541-4fab-bc81-f71556f20b4a (节能) 子组 GUID: 4f971e89-eebd-4455-a8de-9e59040e7347 (电源按钮和盖子) 电源设置 GUID: 5ca83367-6e45-459f-a27b-476b1d01c936 (合上盖子操作) |
电源方案GUID可能会因激活的方案不同而不同,而子组GUID和电源设置GUID在每个方案下都是一样的。后面用这两个ID进行设置就好。对了,每个设置都有直流和交流两项,分别表示使用笔记本电源和外置电源的设置。
至此,省事的话差不多可以收工了:使用powercfg这个工具对电源方案进行设置就好了。
但是,为了折腾,我还是选择了使用API对电源方案进行配置。
祭出要用到的API。
PowerReadACValueIndex (还有一个DC相关的API未列出,下同)
大致流程很简单,首先获取当前的设置,保存下来。然后对系统进行设置,使其合上盖子时不采取任何操作。最后在需要的时候将原来的设置写回。需要注意的一点是,在对当前激活的方案的设置进行修改时,需要调用 PowerSetActiveScheme 一次才能生效。
下面的问题,就变成了如何在C#里使用API了。
WinAPI基本只提供了C的接口,很多在C#中都没有封装,所以需要自己对相应的函数进行声明。一个简单的例子是下面这样。
|
1
2
3
|
using System.Runtime.InteropServices;[DllImport("kernel32.dll")]public static extern uint SetThreadExecutionState(uint esFlags); |
其中,最蛋疼的一点就是得自己进行参数的类型转换。最最蛋疼的一点是,使用有些API得往参数里传二级指针的时候根本就不知道该怎么办。
基本数据类型参考这个表格就好了(网上抄的,而且需要注意的是,真的是仅供参考):

对于指针,参考这个博文(他也是转的,没去找原始出处了): C#调用Win32 API如何处理指针类型的参数
下面来两个用到的具体例子。
|
1
2
3
4
5
6
7
|
DWORD WINAPI PowerReadACValueIndex( _In_opt_ HKEY RootPowerKey, _In_opt_ const GUID *SchemeGuid, _In_opt_ const GUID *SubGroupOfPowerSettingsGuid, _In_opt_ const GUID *PowerSettingGuid, _Out_ LPDWORD AcValueIndex); |
在C#里声明的时候长这样了:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//返回值DWORD转为uint。uint PowerReadACValueIndex( //第一个参数类型HKEY,不知道他是一个干啥用的指针,而且这个API里只能是NULL值,就简单声明为IntPtr类型,使用时传IntPtr.Zero就好了。 IntPtr RootPowerKey, //GUID ref Guid SchemeGuid, ref Guid SubGroupOfPowerSettingsGuid, ref Guid PowerSettingGuid, //最后一个参数类型LPDWORD。LP指的是long pointer,好像现在的系统不分长短指针了,就简单把他理解为一个指针吧。那LPDWORD就是一个指向DWORD的指针。对应到C#里就是ref uint了。 ref uint AcValueIndex); |
世界还是很简单的,直到碰上了一个二级指针:
|
1
2
3
4
|
DWORD WINAPI PowerGetActiveScheme( _In_opt_ HKEY UserRootPowerKey, _Out_ GUID **ActivePolicyGuid); |
这东西目的是把一个指向GUID*
的变量p_GUID,的地址传进去,然后他会new一个GUID作为结果,再然后会把p_GUID的值设为这个结果的地址。使用完毕之后,需调用
LocalFree释放这段内存。 这下不能用ref 来省事了,所以就老老实实传个IntPtr进去吧:
|
1
|
uint PowerGetActiveScheme(IntPtr UserRootPowerKey, ref IntPtr p_ActivePolicyGuid); |
调用之后,p_ActivePolicyGuid就是一个指向GUID变量的指针了。由于使用了ref修饰,所以他本身是个一级指针。要怎么样对他指向的内容进行解释呢?C#里有个Marshal:
|
1
|
Guid guid = (Guid)Marshal.PtrToStructure(p_ActivePolicyGuid, typeof(Guid)); |
世界稍微有点复杂,但还是能接受的。
直到……

一个一个手工转这也太不是个事了。
无意间看到这个网站,相见恨晚: http://www.pinvoke.net/ 别的码农们干完上面的活后,把成果分享在这上面,造福后人。呃,这东西在VS上还弄了个插件……

只要轻按Insert……不过对API的实际用法不一样,也会导致声明的类型有所不同,自己了解一下转换方法总是有好处的。
当运行软件后,用户又去系统里对电源设置进行更改,比如又把合上盖子的动作改成睡眠的话,那就不好了。更可能发生的情况是,系统更改了当前激活的电源方案,比如从“节能”改成“高性能”,那合上盖子的动作就很有可能改变了。所以我们需要对这个动作进行监控。
这有个API(就是上面截图里的那个)可以在修改制定选项时进行通知:
|
1
2
3
4
5
6
7
8
9
10
|
[DllImport(@"User32", SetLastError=true, EntryPoint = "RegisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]public static extern IntPtr RegisterPowerSettingNotification( IntPtr hRecipient, ref Guid PowerSettingGuid, uint Flags );public const uint DEVICE_NOTIFY_WINDOW_HANDLE = 0;public const uint DEVICE_NOTIFY_SERVICE_HANDLE = 1; |
需要往第一个参数里传入一个句柄。这个句柄可以有两种类型,一是窗口句柄,另一种比较复杂,涉及到服务,觉得很麻烦,还不知道有没比较简便的方法。
这个时候就比较坑爹了,因为刚开始写这个软件的时候,主线程里只跑了一个NotifyIcon控件,这东西的handle是私有的,而且就算通过下面的hack拿到句柄,并注册成功后,这个线程也收不到消息。hack代码如下(抄这的: 来个更BT的NotifyIcon支持BalloonTip,还没搞懂):
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
private IntPtr GetWindowHandle(NotifyIcon notifyIcon){ if ( notifyIcon == null ) { return IntPtr.Zero; } Type type = notifyIcon.GetType(); BindingFlags bf = BindingFlags.Instance | BindingFlags.NonPublic; FieldInfo fiWindow = type.GetField("window", bf); object objWindow = fiWindow.GetValue(this.m_NotifyIcon); type = objWindow.GetType().BaseType; FieldInfo fiHandle = type.GetField("handle", bf); IntPtr handle = (IntPtr)fiHandle.GetValue(objWindow); return handle;} |
所以最后还是乖乖地弄了一个Form控件。这有一个问题:一个线程已经有消息队列了,我能不能在需要注册窗体handle的地方,注册线程的handle?
注册之后怎么用呢?
一是重载窗体的消息处理函数:
|
1
2
3
4
5
6
7
8
9
10
11
|
protected override void WndProc(ref Message m){ if (m.Msg == Win32API.WM_POWERBROADCAST) { MessageBox.Show("Power mode Changed! wndproc"); return; } base.WndProc(ref m);} |
二是使用消息过滤: IMessageFilter
实现了这个接口后,就可以使用 Application.AddMessageFilter 方法添加消息过滤了。
C#使用WinAPI 修改电源设置,临时禁止笔记本合上盖子时睡眠的更多相关文章
- 怎么修改与设置.jsp文件属性?
1.首先 打开 >> Myeclipse或Eclipse.(我用的是Myeclipse) 2.打开 >> Window >> Preferences ...
- 怎么修改与设置.java文件属性?
1.首先 打开 >> Myeclipse或Eclipse.(我用的是Myeclipse) 2.打开 >> Window >> Preferences ...
- 【前端】webkit内核浏览器DIV滚动条样式修改和设置
webkit内核浏览器DIV滚动条样式修改和设置 引言: 最近在做自己的小项目,为了设计出好看的页面费劲了心思,大到页面的整体布局,小到DIV的滚动条都不放过,以下是我通过查阅资料总结的webkit内 ...
- Nginx 设置临时维护页面
Nginx 设置临时维护页面 http://www.myexception.cn/open-source/1753957.html http://blog.justwd.net/snippets/ng ...
- LoadRunner如何在脚本运行时修改log设置选项
LoadRunner如何在脚本运行时修改log设置选项?答案是使用lr_set_debug_message函数: lr_set_debug_message Sets the message level ...
- springboot 修改和设置 banner
springboot 修改和设置 banner 先上图 修改步骤 1.在src/main/resources下新建一个banner.txt文档 2.通过http://patorjk.com/softw ...
- QStandardItemModel的data线程安全(在插入数据时,临时禁止sizeHint去读model中的data)
版权声明:本文为博主原创文章,欢迎转载,转载请注明出处 https://blog.csdn.net/MatchYang/article/details/52988257 在直接使用QStandardI ...
- [20190507]sga_target=0注意修改_kghdsidx_count设置.txt
[20190507]sga_target=0注意修改_kghdsidx_count设置.txt --//昨天遇到一例视图定义太复杂导致长时间分析sql语句出现library cache lock等待事 ...
- 永久修改Putty设置
在使用远程登录Putty时,会发现修改一些设置并且退出后发现自己之前改的设置不见了,可以通过保存设置解决 假设我要修改远程终端的背景颜色,选择系统颜色 勾选后,如果不保存下次登入时又要进行设置 点击D ...
随机推荐
- css3动画工具
去年,我刚刚开始学习css3时候,看到了腾讯的这个工具,引起了我对css3的兴趣. 配合着书本上的知识写了一些效果,感觉不错. http://www.f2e.name/case/css3/tools. ...
- google base库之simplethread
// This is the base SimpleThread. You can derive from it and implement the // virtual Run method, or ...
- R包——ggplot2(二)
关于ggplot包(二) 关于ggplot包(二) 标尺(Scale) 从前面可以看到,画图其实就是在做映射,不管是映射到不同的几何对象上,还是映射各种图形属性.在对图形属性进行映射之后,使用标尺可以 ...
- PHP开发APP接口
第1章 APP接口简介 - 课程简介 (:) - APP接口介绍 (:) - 客户端APP通信 (:) 最近学习 - 客户端APP通信格式区别 (:) - APP接口做的哪些事儿 (:) 第2章 封装 ...
- sublime模式下开启vim并修改esc
首先我用的是sublime text2 sublime下开启vim模式: 在Preference -> Setting-User里面加上 "ignored_packages" ...
- ora-01653: unable to extend table sys.aud$ by 8192 in tablespac system[转载]
在用sqlplus user/password@truth登录数据库时报如下错误:ORA-00604: error occurred at recursive SQL level 1ORA-01653 ...
- SD卡协议规范学习
首先,本博文遵照SD卡协议3.01版本,最旧协议版本为1.10,但是协议是向下兼容的.SD卡Physical Layer Simplified Specification Version 3.01英文 ...
- 侯捷C++ Type traits(类型萃取
泛型編程編出來的代碼,適用於任何「吻合某種條件限制」的資料型別.這已成為撰寫可復用代碼時的一個重要選擇.然而,總有一些時候,泛型不夠好 — 有時候是因為不同的型別差距過大,難以產生一致的泛化實作版本. ...
- Hibernate 配置详解(12) 其实我也不想用这么土的名字
hibernate.hbm2ddl.import_files 这个配置用于在hibernate根据映射文件执行DDL之前,如果我们自己设置了要事先运行的SQL文件,hibernate就会先执行这些SQ ...
- node.weiChat
微信的朋友圈分享是现在流行的推广模式,最近两天尝试了一下使用微信进行商品的分享,分享结束后我可以在自己的数据库中查询到用户是否分享成功,包括用户使用微信进行支付时的成功验证.个人觉得微信上的教程有些绕 ...