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

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

Время на прочтение4 мин
Количество просмотров3K

Вступление


Всякому известно, что 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 — очень благодатная почва для таких небольших усовершенствований.
Лично меня до глубины души радует написание таких мелочей, «синтаксического сахара», которые облегчают жизнь и повышают читаемость кода.
Теги:
Хабы:
+16
Комментарии21

Публикации

Изменить настройки темы

Истории

Работа

QT разработчик
6 вакансий
Программист C++
123 вакансии

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн