最初發表在QQ空間,見:C++代碼的維護,從執行個體說起
小問題有大智慧-Proxy 伺服器的監測 是幾個月前的文章,最近碰到別人問如何設定代理的問題,又回顧了部分代碼,雖然時間不長,還是有不少記不清了。,於是就整理了那個設定代理的函數,代碼是實踐的科學,每寫一次,都會有點心得。
先把代碼貼出來,這個函數的大概流程是,先查詢當前的瀏覽器設定,然後根據使用者的設定,再決定
1. 無代理
2. 使用自動設定指令碼
3. 使用某個代理
這三個選項中的一個,根據不同的選項,設定具體的值,然後調用API設定代理選項。
void CWRSBar::ModifySetting( const Option::ProxyEntryInfo &pei ){ // refer to following value in WinInet.h // so as to use these values as index in array. // /* #define INTERNET_PER_CONN_FLAGS 1 #define INTERNET_PER_CONN_PROXY_SERVER 2 #define INTERNET_PER_CONN_PROXY_BYPASS 3 #define INTERNET_PER_CONN_AUTOCONFIG_URL 4 #define INTERNET_PER_CONN_AUTODISCOVERY_FLAGS 5 */// 初始化資料結構,主要是Option數組 INTERNET_PER_CONN_OPTION_LIST List; INTERNET_PER_CONN_OPTION Option[6]; unsigned long nSize = sizeof(List); Option[0].dwOption = 0; Option[0].Value.dwValue = 0; // connection flags Option[INTERNET_PER_CONN_FLAGS].dwOption = INTERNET_PER_CONN_FLAGS; Option[INTERNET_PER_CONN_FLAGS].Value.dwValue = PROXY_TYPE_DIRECT; //|PROXY_TYPE_AUTO_DETECT; // proxy server Option[INTERNET_PER_CONN_PROXY_SERVER].dwOption = INTERNET_PER_CONN_PROXY_SERVER; Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue = NULL; // proxy bypass Option[INTERNET_PER_CONN_PROXY_BYPASS].dwOption = INTERNET_PER_CONN_PROXY_BYPASS; Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue = NULL; // auto config URL Option[INTERNET_PER_CONN_AUTOCONFIG_URL].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL; Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue = NULL; // others ... Option[INTERNET_PER_CONN_AUTODISCOVERY_FLAGS].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS; Option[INTERNET_PER_CONN_AUTODISCOVERY_FLAGS].Value.dwValue = AUTO_PROXY_FLAG_USER_SET | AUTO_PROXY_FLAG_DETECTION_RUN; List.dwSize = nSize; //sizeof(INTERNET_PER_CONN_OPTION_LIST); List.pszConnection = NULL; List.dwOptionCount = 5; List.dwOptionError = 0; List.pOptions = &Option[1]; // Use it like C macro class JustForOutputOption { public: void operator()( INTERNET_PER_CONN_OPTION *p ) { ATLASSERT( p ); INTERNET_PER_CONN_OPTION *Option = p; WRST( LOG_TREND_PROXY )( TEXT("Option[INTERNET_PER_CONN_FLAGS](0x%x): (0x%x), ") TEXT("Option[INTERNET_PER_CONN_PROXY_SERVER](0x%x): (%s), ") TEXT("Option[INTERNET_PER_CONN_PROXY_BYPASS](0x%x): (%s), ") TEXT("Option[INTERNET_PER_CONN_AUTOCONFIG_URL](0x%x): (%s), ") TEXT("Option[INTERNET_PER_CONN_AUTODISCOVERY_FLAGS](0x%x): (0x%x)."), Option[INTERNET_PER_CONN_FLAGS].dwOption, Option[INTERNET_PER_CONN_FLAGS].Value.dwValue, Option[INTERNET_PER_CONN_PROXY_SERVER].dwOption, Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue ? Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue : TEXT("NULL"), Option[INTERNET_PER_CONN_PROXY_BYPASS].dwOption, Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue ? Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue : TEXT("NULL"), Option[INTERNET_PER_CONN_AUTOCONFIG_URL].dwOption, Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue ? Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue : TEXT("NULL"), Option[INTERNET_PER_CONN_AUTODISCOVERY_FLAGS].dwOption, Option[INTERNET_PER_CONN_AUTODISCOVERY_FLAGS].Value.dwValue ); } }DebugOutputOption; // 查詢當前的設定. if( InternetQueryOption( NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize ) ) { DebugOutputOption( Option ); int proxyType = Option::MANUAL; std::tstring proxyAuto, proxyDirect; g_wrsCfg.GetOpt( WRS_TRENDPROXY_AUTO_NAME, proxyAuto ); g_wrsCfg.GetOpt( WRS_TRENDPROXY_DIRECT_NAME, proxyDirect ); if( pei.name == proxyAuto ) proxyType = Option::AUTO; if( pei.name == proxyDirect ) proxyType = Option::DIRECT; std::Bit32 flag; enum { USE_OLD_PAC = 1, USE_OLD_BYPASS, USE_OLD_PROXY, }; flag[USE_OLD_PAC] = flag[USE_OLD_PROXY] = flag[USE_OLD_BYPASS] = true; TCHAR proxy[BufSize], bypass[BufSize]; proxy[0] = bypass[0] = 0;// 根據使用者的配置,做具體的設定 switch( proxyType ) {// 使用自動設定指令碼,如果原來有值,使用原來的值。 case Option::AUTO: { Option[INTERNET_PER_CONN_FLAGS].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_AUTO_PROXY_URL; if( !Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue || !lstrlen( Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue ) ) { // use AUTO value now //Option[0].Value.pszValue = const_cast<LPTSTR>( pei.server.c_str() ); Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue = const_cast<LPTSTR>( g_wrsTrendProxyCfg.autoURL.c_str() ); flag[USE_OLD_PAC] = false; } } break;// 直連 case Option::DIRECT: { Option[INTERNET_PER_CONN_FLAGS].Value.dwValue = PROXY_TYPE_DIRECT; } break;// 使用使用者配置的代理 case Option::MANUAL: { Option[INTERNET_PER_CONN_FLAGS].Value.dwValue = PROXY_TYPE_DIRECT|PROXY_TYPE_PROXY; if( Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue ) GlobalFree( Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue ); _sntprintf( proxy, BufSize-1, TEXT("%s:%d"), pei.server.c_str(), pei.port ); ATLASSERT( lstrlen( proxy ) ); Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue = proxy; flag[USE_OLD_PROXY] = false; if( Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue ) GlobalFree( Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue ); lstrcat( bypass, g_wrsTrendProxyCfg.bypass.c_str() ); if( lstrlen( bypass ) ) lstrcat( bypass, TEXT(";") ); lstrcat( bypass, TEXT("<local>") ); Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue = bypass; flag[USE_OLD_BYPASS] = false; } break; } DebugOutputOption( Option );// 設定代理選項 InternetSetOption( NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, nSize ); // free memeory if( Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue && flag[USE_OLD_BYPASS] ) GlobalFree( Option[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue ); if( Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue && flag[USE_OLD_PROXY] ) GlobalFree( Option[INTERNET_PER_CONN_PROXY_SERVER].Value.pszValue ); if( Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue && flag[USE_OLD_PAC] ) GlobalFree( Option[INTERNET_PER_CONN_AUTOCONFIG_URL].Value.pszValue ); // system // Notifies the system that the registry settings have been changed so that it verifies the settings on the next call to InternetConnect. // This is used by InternetSetOption. InternetSetOption( NULL, INTERNET_OPTION_SETTINGS_CHANGED, NULL, 0 ); // ie // Causes the proxy data to be reread from the registry for a handle. No buffer is required. // This option can be used on the HINTERNET handle returned by InternetOpen. It is used by InternetSetOption. // so if we don't call this API ie might show previous value even after last API call. // //InternetSetOption( NULL, INTERNET_OPTION_REFRESH , NULL, 0 ); }}
這段代碼中想特別說明的有2個地方,
1. 關於Option數組的使用,一開始的時候,是直接使用數字做為索引的,即Option[0],Option[1], Option[2], ..., 這樣做當然可以工作。不過稍微隔幾天,你就會發現記不清各個索引的函義了,哪個是放代理的,哪個是放URL的,那個是放bypass的等,特別不利於後面代碼的維護。經過思考之後,還是覺得使用有意義的枚舉名稱更合適,看了WinInet.h定義的幾個值,可以直接用做索引,於是將代碼稍微修改,變成了上面的樣子。Option[0]是佔位用的,真正的有意義的Option是從Option[1]開始。這樣一來,不管是在函數開頭的初始化的部分,還是函數中間給個別選項的賦值,都顯得特別清楚。Magic Number不見了。將這種方式和原來的方式做個簡單的對比:
a. 設計都在編碼之前,在寫代碼之前,大都會有一個思路,Option[0]放什麼,Option[1]放什麼,可是由於使用數字做索引,代碼類似這樣
Option[1].dwOption = INTERNET_PER_CONN_FLAGS;Option[1].Value.dwValue = PROXY_TYPE_DIRECT;
或許開始時心裡知道1代表著什麼,但真正看代碼,從代碼的角度分析,卻是反過來,是從右邊的值來推導左邊變數的含義的,這違反了編碼的基本原則,有時使人困惑。即使Option[1]你在設計時並不打算放INTERNET_PER_CONN_FLAGS,從代碼是看不出來的,相反,如果語句象下面這樣,
Option[INTERNET_PER_CONN_FLAGS].dwOption = INTERNET_PER_CONN_PROXY_SERVER;
你很快會發現其中的錯誤,因是左右不匹配。
b. a中開始良好的編碼在後面也能體現出來優勢,DebugOutputOption( Option ); 輸出了Option數組的值,如果使用數字做索引,你還能清楚Option[0], Option[1]代表什麼,它們的類型會是什麼嘛?你需要驚人的記憶力。使用有意義的枚舉名做為索引,則可以輕鬆協助你實現這個功能,事半功倍。你很容易就能確定哪個是DWORD,哪個是字串。
Option[INTERNET_PER_CONN_FLAGS].Value.dwValueOption[INTERNET_PER_CONN_PROXY_BYPASS].Value.pszValue
2. JustForOutputOption是一個local class,真正的起作用的代碼就一條語句,local class的定義見http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/topic/com.ibm.xlcpp8a.doc/language/ref/cplr062.htm,這裡為什麼要用Local Class呢?最初,在查詢瀏覽器的代理設定之後和開始進行新的代理設定之前,就是直接列印調試資訊的語句,當時就想雖然就是一句調試輸出的代碼,但這句代碼比較複雜,而且重複了2次,有必要封裝一下。不過,當時因為偷懶,並沒有做。後來因為1的原因,Option數組中的順序變了,不得不修改這2句調試語句,挺麻煩的,於是乎又有了封裝的念頭。首先想到的肯定是用一個獨立的函數封裝,想了一下放棄了,這個地方使用函數封裝並不合適。
a. 如果使用一個單獨的函數,函數的範圍至少得是類的成員函數,但是卻只在這個函數內部使用,
b. 假設後面有其它函數會使用這個函數,函數的參數應該如何設計?傳遞Option類型的指標和數組大小嘛,那麼Option數組的順序呢?無法確保其它的函數的Option數群組成員的順序。
所以,從範圍來看,範圍僅限於這個函數內部,沒有必要影響整個類,甚至全域。從功能設計來看,也不適合設計函數,因為不通用。對熟悉C的開發人員來說,在這個地方,宏是個選擇,就用來作簡單的文字替換即可。對於C++的開發人員來說,Local class是個更好的選擇,畢竟宏有許多缺點。因此,最終使用local class來封裝這個調試輸出語句,並且重載了operator(),使用起來象函數調用一樣方便。
DebugOutputOption( Option );
Local class還有其它的用途,比如RAII等等。順便提下,C++是一個備受爭議的語言,很多其它語言的擁躉譏諷C++含有大量無用的特徵,Local Class正是其中之一。下這些人。
上面2點說得差不多了,最後補充一下,代碼是寫得玩的,未經QA測試,不排除有bug的可能性。