小问题有大智慧-代理服务器的监测 是几个月前的文章,最近碰到别人问如何设置代理的问题,又回顾了部分代码,虽然时间不长,还是有不少记不清了。,于是就整理了那个设置代理的函数,代码是实践的科学,每写一次,都会有点心得。
先把代码贴出来,重点的部分用粗体。这个函数的大概流程是,先查询当前的浏览器设置,然后根据用户的设定,再决定 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.dwValue
- Option[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的可能性。