程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#使用WinAPI 修改電源設置,臨時禁止筆記本合上蓋子時睡眠

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

編輯:C#入門知識

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

 

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

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

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


 

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

Windows下電源管理方案是這樣的。最大的維度是電源配置方案,每套方案包含著一組電源設置。可以更改當前激活的方案,也可以修改每個電源設置的值。

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

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

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


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

祭出要用到的API。

PowerGetActiveScheme

PowerSetActiveScheme

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

PowerWriteACValueIndex

大致流程很簡單,首先獲取當前的設置,保存下來。然後對系統進行設置,使其合上蓋子時不采取任何操作。最後在需要的時候將原來的設置寫回。需要注意的一點是,在對當前激活的方案的設置進行修改時,需要調用 PowerSetActiveScheme 一次才能生效。

 

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

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

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

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

 

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

在C#裡聲明的時候長這樣了:

//返回值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
);

 

世界還是很簡單的,直到碰上了一個二級指針

DWORD WINAPI PowerGetActiveScheme(
  _In_opt_  HKEY UserRootPowerKey,
  _Out_     GUID **ActivePolicyGuid
);

這東西目的是把一個指向GUID* 的變量p_GUID,的地址傳進去,然後他會new一個GUID作為結果,再然後會把p_GUID的值設為這個結果的地址。使用完畢之後,需調用LocalFree釋放這段內存。 這下不能用ref 來省事了,所以就老老實實傳個IntPtr進去吧:

uint PowerGetActiveScheme(IntPtr UserRootPowerKey, ref IntPtr p_ActivePolicyGuid);

調用之後,p_ActivePolicyGuid就是一個指向GUID變量的指針了。由於使用了ref修飾,所以他本身是個一級指針。要怎麼樣對他指向的內容進行解釋呢?C#裡有個Marshal

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

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


直到……

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

這個時候就比較坑爹了,因為剛開始寫這個軟件的時候,主線程裡只跑了一個NotifyIcon控件,這東西的handle是私有的,而且就算通過下面的hack拿到句柄,並注冊成功後,這個線程也收不到消息。hack代碼如下(抄這的: 來個更BT的NotifyIcon支持BalloonTip,還沒搞懂):

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?

注冊之後怎麼用呢?

一是重載窗體的消息處理函數:

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 方法添加消息過濾了。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved