小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

C#使用WinAPI 修改電源設置,臨時禁止筆記本合上蓋子時睡眠

 NaturalWill 2018-01-10

阻止系統(tǒng)自動睡眠的小軟件,附C#制作過程 ,弄了一個防止系統(tǒng)睡眠的工具。然后馬上發(fā)現(xiàn),新的需求來了:為了保護環(huán)境(省錢),在系統(tǒng)設置中,合上蓋子時會自動睡眠。那因下載之類的原因,需要臨時禁止睡眠的話,又懶得去改設置,而且下次還得改回來。所以沒事也是折騰,就研究了怎么用軟件實現(xiàn)了。

 

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

然后發(fā)現(xiàn)當系統(tǒng)進行睡眠時,會廣播一個消息,然后每個軟件會有兩秒鐘(xp和03可以長達20秒)的時間進行善后(PBT_APMSUSPEND event)。雖然可以喚醒睡眠的電腦(System Wake-up Events),但是還沒找到方法取消這次睡眠。

最后,我的解決方法時,臨時修改電源設置,即將合上蓋子的動作設置為啥事不干,然后在需要的時候恢復原來的設置。


 

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

Windows下電源管理方案是這樣的。最大的維度是電源配置方案,每套方案包含著一組電源設置??梢愿漠斍凹せ畹姆桨福部梢孕薷拿總€電源設置的值。

使用系統(tǒng)自帶工具powercfg進行電源配置的查看及更改:其中GUID值會在后面用到。

image

image

注意到這里:
  

1
2
3
電源方案 GUID: a1841308-3541-4fab-bc81-f71556f20b4a  (節(jié)能)
  子組 GUID: 4f971e89-eebd-4455-a8de-9e59040e7347  (電源按鈕和蓋子)
    電源設置 GUID: 5ca83367-6e45-459f-a27b-476b1d01c936  (合上蓋子操作)

電源方案GUID可能會因激活的方案不同而不同,而子組GUID和電源設置GUID在每個方案下都是一樣的。后面用這兩個ID進行設置就好。對了,每個設置都有直流和交流兩項,分別表示使用筆記本電源和外置電源的設置。

至此,省事的話差不多可以收工了:使用powercfg這個工具對電源方案進行設置就好了。


但是,為了折騰,我還是選擇了使用API對電源方案進行配置。

祭出要用到的API。

PowerGetActiveScheme

PowerSetActiveScheme

PowerReadACValueIndex    (還有一個DC相關(guān)的API未列出,下同)

PowerWriteACValueIndex

大致流程很簡單,首先獲取當前的設置,保存下來。然后對系統(tǒng)進行設置,使其合上蓋子時不采取任何操作。最后在需要的時候?qū)⒃瓉淼脑O置寫回。需要注意的一點是,在對當前激活的方案的設置進行修改時,需要調(diào)用 PowerSetActiveScheme 一次才能生效。

 

下面的問題,就變成了如何在C#里使用API了。

WinAPI基本只提供了C的接口,很多在C#中都沒有封裝,所以需要自己對相應的函數(shù)進行聲明。一個簡單的例子是下面這樣。

1
2
3
using System.Runtime.InteropServices;
[DllImport("kernel32.dll")]
public static extern uint SetThreadExecutionState(uint esFlags);

其中,最蛋疼的一點就是得自己進行參數(shù)的類型轉(zhuǎn)換。最最蛋疼的一點是,使用有些API得往參數(shù)里傳二級指針的時候根本就不知道該怎么辦。

 

基本數(shù)據(jù)類型參考這個表格就好了(網(wǎng)上抄的,而且需要注意的是,真的是僅供參考):

image

對于指針,參考這個博文(他也是轉(zhuǎn)的,沒去找原始出處了):  C#調(diào)用Win32 API如何處理指針類型的參數(shù)

 

 

下面來兩個用到的具體例子。

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轉(zhuǎn)為uint。
uint PowerReadACValueIndex(
    //第一個參數(shù)類型HKEY,不知道他是一個干啥用的指針,而且這個API里只能是NULL值,就簡單聲明為IntPtr類型,使用時傳IntPtr.Zero就好了。
    IntPtr RootPowerKey,
    //GUID在C#里有這個Guid類型與之對應。至于一級指針,得看這個指針是干啥用的。如果這個指針只是指向一個變量的話,就用ref修飾,實際傳遞的就是指針了。如果這個指針指向的是一個數(shù)組的首地址,那就先得在C#里分配一段內(nèi)存,然后把這個內(nèi)存的地址傳進去。參考前面轉(zhuǎn)的博文。
    ref Guid SchemeGuid,
    ref Guid SubGroupOfPowerSettingsGuid,
    ref Guid PowerSettingGuid,
    //最后一個參數(shù)類型LPDWORD。LP指的是long pointer,好像現(xiàn)在的系統(tǒng)不分長短指針了,就簡單把他理解為一個指針吧。那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作為結(jié)果,再然后會把p_GUID的值設為這個結(jié)果的地址。使用完畢之后,需調(diào)用LocalFree釋放這段內(nèi)存。 這下不能用ref 來省事了,所以就老老實實傳個IntPtr進去吧:

1
uint PowerGetActiveScheme(IntPtr UserRootPowerKey, ref IntPtr p_ActivePolicyGuid);

調(diào)用之后,p_ActivePolicyGuid就是一個指向GUID變量的指針了。由于使用了ref修飾,所以他本身是個一級指針。要怎么樣對他指向的內(nèi)容進行解釋呢?C#里有個Marshal

1
Guid guid = (Guid)Marshal.PtrToStructure(p_ActivePolicyGuid, typeof(Guid));

世界稍微有點復雜,但還是能接受的。


直到……

image

一個一個手工轉(zhuǎn)這也太不是個事了。

無意間看到這個網(wǎng)站,相見恨晚:   http://www./ 別的碼農(nóng)們干完上面的活后,把成果分享在這上面,造福后人。呃,這東西在VS上還弄了個插件……

image

只要輕按Insert……不過對API的實際用法不一樣,也會導致聲明的類型有所不同,自己了解一下轉(zhuǎn)換方法總是有好處的。

 


當運行軟件后,用戶又去系統(tǒng)里對電源設置進行更改,比如又把合上蓋子的動作改成睡眠的話,那就不好了。更可能發(fā)生的情況是,系統(tǒng)更改了當前激活的電源方案,比如從“節(jié)能”改成“高性能”,那合上蓋子的動作就很有可能改變了。所以我們需要對這個動作進行監(jiān)控。

這有個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;

需要往第一個參數(shù)里傳入一個句柄。這個句柄可以有兩種類型,一是窗口句柄,另一種比較復雜,涉及到服務,覺得很麻煩,還不知道有沒比較簡便的方法。

這個時候就比較坑爹了,因為剛開始寫這個軟件的時候,主線程里只跑了一個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控件。這有一個問題:一個線程已經(jīng)有消息隊列了,我能不能在需要注冊窗體handle的地方,注冊線程的handle?

注冊之后怎么用呢?

一是重載窗體的消息處理函數(shù):

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 

實現(xiàn)了這個接口后,就可以使用 Application.AddMessageFilter 方法添加消息過濾了。

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多