博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C#使用WinAPI 修改电源设置,临时禁止笔记本合上盖子时睡眠
阅读量:5946 次
发布时间:2019-06-19

本文共 4382 字,大约阅读时间需要 14 分钟。

原文

,弄了一个防止系统睡眠的工具。然后马上发现,新的需求来了:为了保护环境(省钱),在系统设置中,合上盖子时会自动睡眠。那因下载之类的原因,需要临时禁止睡眠的话,又懒得去改设置,而且下次还得改回来。所以没事也是折腾,就研究了怎么用软件实现了。

 

最开始的思路就是进行Hook,以截断睡眠消息。但是木有找到方法。

然后发现当系统进行睡眠时,会广播一个消息,然后每个软件会有两秒钟(xp和03可以长达20秒)的时间进行善后()。虽然可以唤醒睡眠的电脑(),但是还没找到方法取消这次睡眠。

最后,我的解决方法时,临时修改电源设置,即将合上盖子的动作设置为啥事不干,然后在需要的时候恢复原来的设置。


 

Windows下电源管理,及配置工具powercfg

Windows下电源管理方案是这样的。最大的维度是电源配置方案,每套方案包含着一组电源设置。可以更改当前激活的方案,也可以修改每个电源设置的值。

使用系统自带工具powercfg进行电源配置的查看及更改:其中GUID值会在后面用到。

image

image

注意到这里:

  

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。

    (还有一个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得往参数里传二级指针的时候根本就不知道该怎么办。

 

基本数据类型参考这个表格就好了(网上抄的,而且需要注意的是,真的是仅供参考):

对于指针,参考这个博文(他也是转的,没去找原始出处了): 

 

 

下面来两个用到的具体例子。

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 在C#里有这个Guid类型与之对应。至于一级指针,得看这个指针是干啥用的。如果这个指针只是指向一个变量的话,就用ref修饰,实际传递的就是指针 了。如果这个指针指向的是一个数组的首地址,那就先得在C#里分配一段内存,然后把这个内存的地址传进去。参考前面转的博文。
    
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));

世界稍微有点复杂,但还是能接受的。


直到……

一个一个手工转这也太不是个事了。

无意间看到这个网站,相见恨晚:   别的码农们干完上面的活后,把成果分享在这上面,造福后人。呃,这东西在VS上还弄了个插件……

只要轻按Insert……不过对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代码如下(抄这的: ,还没搞懂):

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);
}

二是使用消息过滤:  

实现了这个接口后,就可以使用 方法添加消息过滤了。

转载地址:http://vmfxx.baihongyu.com/

你可能感兴趣的文章
nginc+memcache
查看>>
php正则匹配utf-8编码的中文汉字
查看>>
MemCache在Windows环境下的搭建及启动
查看>>
linux下crontab实现定时服务详解
查看>>
Numpy中的random模块中的seed方法的作用
查看>>
用java数组模拟登录和注册功能
查看>>
javaScript实现归并排序
查看>>
关于jsb中js与c++的相互调用
查看>>
UVA 122 Trees on the level 二叉树 广搜
查看>>
POJ-2251 Dungeon Master
查看>>
tortoisesvn的安装
查看>>
我是怎么使用最短路径算法解决动态联动问题的
查看>>
URAL 1353 Milliard Vasya's Function DP
查看>>
速读《构建之法:现代软件工程》提问
查看>>
Android onclicklistener中使用外部类变量时为什么需要final修饰【转】
查看>>
django中聚合aggregate和annotate GROUP BY的使用方法
查看>>
TFS简介
查看>>
docker管理平台 shipyard安装
查看>>
安装django
查看>>
Bootstrap3 栅格系统-简介
查看>>