2007年11月29日 星期四

C++ Builder 通過 WMI 獲取系統信息

C++ Builder 通過 WMI 獲取系統信息

Victor Chen, (C++ 愛好者)


--------------------------------------------------------------------------------
WMI: Windows Management Instrumentation (Windows 管理工具)
通過 WMI 可以獲取主板、BIOS、磁盤、顯卡、網絡等幾乎所有的系統信息。
利用這個工具可以管理本地或客戶端系統中幾乎所有的信息。
很多網絡管理工具都是基於WMI開發的。在 Windows NT/2000/XP/2003 都有這個工具, 在 Windows 98 裡面可以選擇安裝這個工具。

--------------------------------------------------------------------------------
BCB 的 WMI 庫文件 wbemuuid.lib 由本站提供, 包含在本頁下面的程序源碼下載裡面

--------------------------------------------------------------------------------
1 初始化 COM 接口:
訪問 WMI, 必須先初始化 COM 接口, 在程序的一開始調用 CoInitialize(NULL); 初始化, 在結束時調用 CoUninitialize(); 釋放資源。
這兩個函數在 #include 裡面定義。

2 獲取訪問 WMI 權限:
CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, 0);
如果這個函數返回 S_OK 獲取權限成功, 否則為失敗。

3 通過 IWbemLocator 和 IWbemServices 這兩個 COM 接口訪問 WMI, 獲取系統信息:
這個函數的參數: lpList 返回信息, wsClass 為要查找的系統信息類, 這些 COM 接口在



#include < wbemidl.h > 裡定義。

void GetWmiInfo(TStrings *lpList, WideString wsClass)
{
IWbemLocator *pWbemLocator = NULL;
if(CoCreateInstance(CLSID_WbemAdministrativeLocator, NULL, CLSCTX_INPROC_SERVER|CLSCTX_LOCAL_SERVER, IID_IUnknown, (void**)&pWbemLocator) == S_OK)
{
IWbemServices *pWbemServices = NULL;
WideString wsNamespace = (L"root\\cimv2");
if(pWbemLocator->ConnectServer(wsNamespace, NULL, NULL, NULL, 0, NULL, NULL, &pWbemServices) == S_OK)
{
IEnumWbemClassObject *pEnumClassObject = NULL;
WideString wsWQL=L"WQL", wsQuery=WideString(L"Select * from ")+wsClass;
if(pWbemServices->ExecQuery(wsWQL, wsQuery, WBEM_FLAG_RETURN_IMMEDIATELY,NULL, &pEnumClassObject) == S_OK)
{
IWbemClassObject *pClassObject = NULL;
ULONG uCount = 1, uReturned;
if(pEnumClassObject->Reset() == S_OK)
{
int iEnumIdx = 0;
while(pEnumClassObject->Next(WBEM_INFINITE, uCount, &pClassObject, &uReturned) == S_OK)
{
lpList->Add("---------------- ["+IntToStr(iEnumIdx)+"] -----------------");

SAFEARRAY *pvNames = NULL;
if(pClassObject->GetNames(NULL, WBEM_FLAG_ALWAYS | WBEM_MASK_CONDITION_ORIGIN, NULL, &pvNames) == S_OK)
{
long vbl, vbu;
SafeArrayGetLBound(pvNames, 1, &vbl);
SafeArrayGetUBound(pvNames, 1, &vbu);
for(long idx=vbl; idx<=vbu; idx++)
{
long aidx = idx;
wchar_t *wsName = 0;
VARIANT vValue;
VariantInit(&vValue);
SafeArrayGetElement(pvNames, &aidx, &wsName);

BSTR bs = SysAllocString(wsName);
HRESULT hRes = pClassObject->Get(bs, 0, &vValue, NULL, 0);
SysFreeString(bs);

if(hRes == S_OK)
{
AnsiString s;
Variant v = *(Variant*)&vValue;
if(v.IsArray())
{
for(int i=v.ArrayLowBound(); i <=v.ArrayHighBound(); i++)
{
Variant a = v.GetElement(i);
if(!s.IsEmpty())
s+=", ";
s+=VarToStr(a);
}
}
else
{
s = VarToStr(v);
}
lpList->Add(AnsiString(wsName)+"="+s);
}

VariantClear(&vValue);
SysFreeString(wsName);
}
}
if(pvNames)SafeArrayDestroy(pvNames);
iEnumIdx++;
}
}
if(pClassObject)pClassObject->Release();
}
if(pEnumClassObject)pEnumClassObject->Release();
}
if(pWbemServices)pWbemServices->Release();
}
if(pWbemLocator)pWbemLocator->Release();
}
//---------------------------------------------------------------------------

// 通過 WIN32_bios 獲取 BIOS 信息:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Memo1->Lines->Add("================== [WIN32_bios] =================");
GetWmiInfo(Memo1->Lines, "WIN32_bios");
Memo1->Lines->Add("");
}

--------------------------------------------------------------------------------

WMI 可以訪問的信息類型有:
Win32_1394Controller
Win32_BaseBoard
Win32_Battery
Win32_BIOS
Win32_Bus
Win32_CacheMemory
Win32_CDROMDrive
Win32_CurrentProbe
Win32_DesktopMonitor
Win32_DeviceMemoryAddress
Win32_DiskDrive
Win32_DisplayConfiguration
Win32_DisplayControllerConfiguration
Win32_DMAChannel
Win32_Fan
Win32_FloppyController
Win32_FloppyDrive
Win32_HeatPipe
Win32_IDEController
Win32_InfraredDevice
Win32_IRQResource
Win32_Keyboard
Win32_MemoryArray
Win32_MemoryDevice
Win32_MotherboardDevice
Win32_NetworkAdapter
Win32_NetworkAdapterConfiguration
Win32_OnBoardDevice
Win32_ParallelPort
Win32_PCMCIAController
Win32_PhysicalMemory
Win32_PhysicalMemoryArray
Win32_PnPEntity
Win32_PointingDevice
Win32_PortableBattery
Win32_PortConnector
Win32_PortResource
Win32_POTSModem
Win32_PowerManagementEvent
Win32_Printer
Win32_PrinterConfiguration
Win32_PrintJob
Win32_Processor
Win32_Refrigeration
Win32_SerialPort
Win32_SerialPortConfiguration
Win32_SMBIOSMemory
Win32_SoundDevice
Win32_SystemEnclosure
Win32_SystemMemoryResource
Win32_SystemSlot
Win32_TapeDrive
Win32_TemperatureProbe
Win32_UninterruptiblePowerSupply
Win32_USBController
Win32_VideoConfiguration
Win32_VideoController
Win32_VoltageProbe

以上類型(字符串值)通過前面寫的函數 GetWmiInfo 可以得到相應的信息, 例如 GetWmiInfo(Memo1->Lines, "WIN32_bios");

2007年7月4日 星期三

Gnu Make

http://blog.chinaunix.net/u/21948/showart_292182.html

學習任務:Gnu Make
學習資料:GNU Make 3.80(中文版,徐海兵譯)、網路
學習目標:
1、能夠熟練編寫中小規模project的Makefile
2、能夠讀懂大規模project,比如Linux kernel的Makefile
3、學習automake以實現自動化工程管理

2007-05-05

1、make是什麼?Makefile又是什麼?

Gnu make是Linux環境下用來構建和管理工程的命令工具,然而單獨的make命令是無法工作的,它需要一個Makefile檔。這個檔描述了
整個工程的編譯、連接規則,Makefile有自己的書寫格式、命令、關鍵字。make讀取Makefile,然後對這些規則解釋執行,以完成工程管理。
可以進行類比理解:shell是一個命令解釋器,它可以讀取shell腳本檔,在解釋的同時執行(注意:這是解釋器和編譯器的不同之處)。
同樣,make類似一個命令解釋器(但是不是命令解釋器,只是類比而已),它可以讀取Makefile檔,進行解釋執行。
因為shell腳本和Makefile都有自己獨立的書寫格式、命令等,所以需要分別理解,在變數引用等方面需要進行區分,不要混淆。
從另一方面來看,Linux存在很多相通之處,就像一個生態環境。shell和Makefile可以結合使用,以更加通用。二者在正則運算式上很多地方
相同。在學習中,採用對比研究的方法比較合適。

2、make的基本工作原理是什麼?

make是通過比較對應檔(規則的目標和依賴)的最後修改時間,來決定哪些檔需要更新、那些檔不需要更新。對需要更新的檔,make就執行
資料庫中所記錄的相應命令(在make讀取Makefile後會建立一個編譯過程的描述資料庫。此資料庫中記錄了所有各個檔之間的相互關係,以及
它們的關係描述)來重建它,對於不需要重建的檔make什麼也不做。

3、Makefile編寫可讀性的注意事項

(1)在書寫時,一個較長行可以使用反斜線(\)分解為多行,這樣做可以使Makefile清晰、容易閱讀。注意:反斜線之後不能有空格!

(2)在實際工作中大家比較認同的方法是,使用一個變數"objects"、"OBJECTS"、"objs"、"OBJS"來作為所有的.o文件的列表的替代。
在使用到這些檔列表的地方,使用此變數來代替。比如說定義了變數"OBJS",那麼就可以在需要的地方使用"$(OBJS)"來表示它。

(3)書寫規則建議的方式是:單目標,多依賴。就是說儘量做到一個規則中只存在一個目標檔,可有多個依賴檔。儘量避免多目標,單依賴的方式。
這樣後期維護也會非常方便,而且Makefile會更加清晰明瞭。

(4)Makefile可以包含子Makefile,這樣方便工程的模組化管理。這可以利用關鍵字include實現,和c語言對頭檔的包含方式類似。
include指示符告訴make暫停讀取當前的Makefile,而轉去讀取include指定的一個或多個檔,完成以後再繼續當前Makefile的讀取。
Makefile指示符include書寫在獨立的一行,其形式如下:

include FILENAMES

FILENAMES是shell所支援的檔案名(可以使用通配符)。指示符include所在的行可以以一個或者多個空格(make在處理時將忽略這些空格)
開始,但是切記不能以【TAB】字元開始。

4、Makefile中如何創建多個可執行程式?

Makefile中,如果在一個目錄下如果需要創建多個可執行程式,我們可以將所有程式的重建規則在一個Makefile中描述。
因為Makefile中第一個目標是"終極目標",約定的做法是使用一個稱為"all"的偽目標來作為終極目標,它的依賴檔就是那些需要創建的程式。


#sample multi-targetall: prog1 prog2 prog3.PHONY: allprog1: prog1.o utils.o $(CC) $(CFLAGS) $^ -o $@prog2: prog2.o $(CC) $(CFLAGS) $^ -o $@prog3: prog3.o sort.o utils.o $(CC) $(CFLAGS) $^ -o $@
註:$^為前提清單,$@為目標,如果以紅色部分來展開,即為如下所示,也可以寫$(^),$(@)
Prog1: prog1.o utils.o
$(CC) $(CFLAGS) prog1.o utils.o –o prog1
當需要單獨更新某一個程式時,我們可以通過make的命令行選項來明確指定需要重建的程式。
5、Makefile中make clean的標準格式

make存在一個內嵌隱含變數“RM”,它被定義為:“RM=rm -f”,因此在書寫clean規則時的命令行可以使用$(RM)來代替rm,這樣可以避免
出現一個不必要的麻煩。這是推薦使用的做法。

注意:目標clean僅僅代表一個動作的標識,這個標識可以作為make命令的參數。它沒有任何依賴,Makefile中把這種沒有任何依賴,只有執行
動作的目標稱為“偽目標”(phony targets)。你可以用make clean來執行清理工作。

.PHONY:cleanclean: $(RM) *.o $(TARGET)


6、小技巧

(1)如果要執行的命令以字元"@"開始,則make在執行時這個命令就不會被回顯。典型的用法是我們在使用echo命令時輸出一些資訊時,如:

@echo "starting"

所以可以在書寫Makefile時,使用@來控制命令的回顯。

(2)規則中,當目標需要被重建時,此規則定義的命令將會被執行,如果是多行命令,那麼make就為每一行命令使用一個獨立的子shell去執行。
因此,多行命令之間的執行是相互獨立的,相互之間不存在依賴。而在Makefile中書寫在同一行的多個命令屬於一個完整的shell命令行,所以需要
注意:在一個規則的命令中,命令行cd改變目錄並不會對其後的命令的執行產生影響。就是說其後的命令執行的工作目錄不會是之前使用cd進入的那個目錄。如果要實現這個目的,就不能把cd和其後的命令放在兩行來書寫。而應該把這兩行命令寫在一行上,用分號隔開。這樣它們才是一個完整的shell命令行。如果希望把一個完整的shell命令行書寫在多行上,需要使用反斜杠來對處於多行的命令進行連接,表示它們是一個完整的shell命令行。

(3)make的遞迴調用
在Makefile中使用"make"作為一個命令來執行本身或者其他makefile檔。遞迴調用在一個存在多級子目錄的項目中非常有用。

subsystem:
cd subdir && $(MAKE)
等價於
subsystem:
$(MAKE) -C subdir

意思是進入子目錄,然後在子目錄下面執行make。

(4)make可以定制命令包

在書寫Makefile時,可能有多個規則會使用相同的一組命令,就像C語言程式中需要經常使用的函數printf。可以將一組命令進行類似c語言函數的封裝,以後在需要的地方可以通過它的名字來對這一組命令中進行引用。這樣就可以減少重複工作,提高了效率。在GNU make中,可以使用指示符define來完成這個功能,通常把define定義的一組命令成為一個命令包。定義一個命令包的語法以define開始,以endef結束。

define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef

這樣,run-yacc就是這個命令包的名字。在define和endef之間的命令就是命令包的主體。需要說明的是,使用define定義的命令包中,命令體中變數和函數的引用不會展開。命令體中所有的內容包括$等都是變數run-yacc的定義,它和C語言中巨集的使用方式一樣。

使用:
foo.c:foo.y
$(run-yacc)

(5)區分Makefile和shell中對變數應用的不同

Makefile單字元的引用可以為$x $(x) ${x}三種形式。多字元的引用$(xx) ${xx}.
而shell中變數的引用只有兩種形式${xx} $xx.

一般在書寫Makefile時,各部分變數引用的格式建議如下:

(i)make變數(Makefile中定義的變數或者是make的環境變數)的引用使用$(VAR)的格式,無論VAR是單字元變數名還是多字元變數名。
(ii)出現在規則命令行中shell變數(一般為執行命令過程中的臨時變數,它不屬於Makefile變數,而是一個shell變數)引用使用shell的$tmp格式。
(iii)對出現在命令行中的make變數同樣使用$(CMDVAR)格式來引用。

(6)儘量採用直接展開式變數,而不要用遞迴展開式變數。

(7)如果實現對一個之前沒有定義過的變數進行賦值,可以使用?=。如果想對一個通用變數的值進行追加,使用+=。

7、思想

作為一名優秀的程式師,在面對一個複雜問題時,應該是尋求一種盡可能簡單、直接並且高效的處理方式來解決,而不是將一個簡單問題在實現上複雜化。如果想在簡單問題上突出自己使用某種語言的熟練程度,是一種非常愚蠢且不成熟的行為