Открыть список
Как стать автором
Обновить

Использование traits для обустройства Win32 API

C++

Вступление


Всякому известно, что Win32 API не отличается чрезмерным удобством. Например, для вызова функции частенько приходится заполнять большую структуру с полями вроде cbSize, dwFlags и т. д. Или для получения строки сначала узнавать размер ее, готовить буфер и затем лишь получать саму строку. Далее пойдет речь про функцию ::HttpQueryInfo() и применение идиомы «traits» для упрощения работы с ней.

Прямолинейный способ


Код взят из MSDN:
// Retrieving Headers Using a Constant
BOOL SampleCodeOne(HINTERNET hHttp)
{
   LPVOID lpOutBuffer=NULL;
   DWORD dwSize = 0;
retry:
   // This call will fail on the first pass, because 
   // no buffer is allocated.
   if(!HttpQueryInfo(hHttp,HTTP_QUERY_RAW_HEADERS_CRLF,
      (LPVOID)lpOutBuffer,&dwSize,NULL))
   { 
      if (GetLastError()==ERROR_HTTP_HEADER_NOT_FOUND)
      {
         // Code to handle the case where the header isn't available.
         return TRUE;
      }		
      else
      {
        // Check for an insufficient buffer.
        if (GetLastError()==ERROR_INSUFFICIENT_BUFFER)
        {
            // Allocate the necessary buffer.
            lpOutBuffer = new char[dwSize];
            // Retry the call.
            goto retry;				
        }		
        else
        {
            // Error handling code.
            delete [] lpOutBuffer;
            return FALSE;
        }		
      }		
   }
   delete [] lpOutBuffer;
   return TRUE;
}

На голове начинают шевелиться волосы. (Кстати, delete [] можно вызывать и для нулевого указателя).

Хитрый способ


Различных заголовков у запроса довольно много, но почти все они представляют из себя либо строку, либо число. В идеале хочется получать значение заголовка в одной строке кода:
const int code = get_header<HTTP_QUERY_STATUS_CODE>(hRequest) ;
const int responseBodySize = get_header<HTTP_QUERY_CONTENT_LENGTH>(hRequest) ;
const CString allHeaders = get_header<HTTP_QUERY_RAW_HEADERS_CRLF>(hRequest) ;

Для этого тип возвращаемого значения у get_header должен зависеть от указанного заголовка.
template <DWORD I>
TypeDependentOfI get_header(HINTERNET aRequest)
{
	return Magic(aRequest, I) ;
}

И тут на помощь приходит идиома «traits». Обычно она реализуется в виде шаблона со специализациями, в котором перечислены typedef'ы и константы, зависящие от параметров шаблона.
using ATL::CString ;

template <DWORD I>
struct header_traits ;

template <>
struct header_traits<HTTP_QUERY_STATUS_CODE>
{
	typedef DWORD result_type ;
} ;

template <>
struct header_traits<HTTP_QUERY_CONTENT_LENGTH>
{
	typedef DWORD result_type ;
} ;

template <>
struct header_traits<HTTP_QUERY_RAW_HEADERS_CRLF>
{
	typedef CString result_type ;
} ;

Конечно, писать «в лоб» N специализаций для всех заголовков не хочется. Кроме того, отличаться будет не только тип возвращаемого значения, но и сам метод добычи этого значения. Приходится делать хитрее. Сначала выносим методы добычи в отдельные структуры (чтобы можно было на эти методы ссылаться из traits).
struct string_getter
{
	typedef CString result_type ;
	static type get(HINTERNET aRequest, DWORD aValueCode)
	{
		DWORD len = 0 ;
		const BOOL r1 = ::HttpQueryInfo(aRequest, aValueCode, 0, IN OUT &len, 0) ;
		if (len > 0)
		{
			CString res ;
			const DWORD r2 = ::HttpQueryInfo(aRequest, aValueCode, OUT res.GetBufferSetLength(len + 1), IN OUT &len, 0) ;
			if (r2 != 0)
				return res ;
		}
		return CString() ;
	}
} ;

struct num_getter
{
	typedef DWORD result_type ;
	static type get(HINTERNET aRequest, DWORD aValueCode)
	{
		static const int KMaxLen = 16 ;
		TCHAR buf[KMaxLen + 1] ;
		ZeroSizeOf(buf) ;
		DWORD len = KMaxLen ;
		if (::HttpQueryInfo(aRequest, aValueCode, OUT buf, IN OUT &len, 0))
		{
			return (DWORD)_tcstoul(buf) ;
		}
		return 0 ;
	}
} ;

А в самих traits уже упоминаем эти структуры, в которых есть информация и о типе возвращаемого значения, и метод для добычи его.
template <>
struct header_traits<HTTP_QUERY_STATUS_CODE>
{
	typedef num_getter getter ;
} ;

template <>
struct header_traits<HTTP_QUERY_CONTENT_LENGTH>
{
	typedef num_getter getter ;
} ;

template <>
struct header_traits<HTTP_QUERY_RAW_HEADERS_CRLF>
{
	typedef string_getter getter ;
} ;

Затем пишем удобные макросы для перечисления объявления заголовков:
#define DECLARE_STRING_HEADER_GETTER(code) \
	template <> struct header_traits<code> \
	{ typedef string_getter getter ; } ;

#define DECLARE_NUM_HEADER_GETTER(code) \
	template <> struct header_traits<code> \
	{ typedef num_getter getter ; } ;

	DECLARE_NUM_HEADER_GETTER(HTTP_QUERY_STATUS_CODE)
	DECLARE_NUM_HEADER_GETTER(HTTP_QUERY_CONTENT_LENGTH)
	DECLARE_STRING_HEADER_GETTER(HTTP_QUERY_RAW_HEADERS_CRLF)
	DECLARE_STRING_HEADER_GETTER(HTTP_QUERY_CONTENT_DISPOSITION)
	// ...

#undef DECLARE_NUM_HEADER_GETTER
#undef DECLARE_STRING_HEADER_GETTER

В таком виде гораздо проще и нагляднее перечислять специализации для нужных заголовков, особенно если их не 3-4, а несколько десятков.
В итоге «волшебный» метод get_header() приобретает вид:
template <DWORD I>
typename header_traits<I>::getter::type get_header(HINTERNET aRequest)
{
	return typename header_traits<I>::getter::get(aRequest, I) ;
}


Заключение


Вышеописанное решение претендует на звание велосипеда, но до сих пор ничего подобного мне в интернетах не попадалось. Вообще Win32 API — очень благодатная почва для таких небольших усовершенствований.
Лично меня до глубины души радует написание таких мелочей, «синтаксического сахара», которые облегчают жизнь и повышают читаемость кода.
Теги:C++Win32traitsWinInet
Хабы: C++
Всего голосов 24: ↑20 и ↓4 +16
Просмотры2.5K

Похожие публикации

Разработчик C++
от 290 000 до 300 000 ₽ВГТМосква
C++
от 100 000 до 300 000 ₽OvisionСанкт-Петербург
Senior C++ / JS Developer
от 200 000 до 300 000 ₽ZennoLabМожно удаленно
Gameplay UE4 C++ Developer
от 200 000 ₽Omniverse GamesСанкт-Петербург
Python / C++ разработчик
от 150 000 до 230 000 ₽Wunder FundМожно удаленно

Лучшие публикации за сутки