16 октября 2007

Hash calculation

Как правильно заметил Not a kernel guy, лишний раз бросаться исключениями не стоит. Если знаешь как обработать ошибку - обработай ее сразу. Не знаешь - брось исключение, "на верху" разберутся.

В качестве примера - код получения хэша. В фокусе - вызов CryptGetHashParam:
#include <windows.h>
#include <Loki/ScopeGuard.h>
#include <boost/range/size.hpp>
#include <boost/static_assert.hpp>
#include "TestWinFn.h"

template <class TOutputContainer, typename TInputContainer>
void Hash(ALG_ID Algorithm, const TInputContainer& Input, TOutputContainer& Output)
{
BOOST_STATIC_ASSERT(sizeof(typename TOutputContainer::value_type) == sizeof(BYTE));

HCRYPTPROV hProv = 0;
TestWinFn(CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT));
LOKI_ON_BLOCK_EXIT(CryptReleaseContext, hProv, 0);

HCRYPTHASH hHash = 0;
TestWinFn(CryptCreateHash(hProv, Algorithm, 0, 0, &hHash));
LOKI_ON_BLOCK_EXIT(CryptDestroyHash, hHash);

TestWinFn(CryptHashData(hHash, reinterpret_cast<const BYTE*>(&Input[0]),
static_cast<DWORD>(boost::size(Input) * sizeof(Input[0])), 0));

DWORD HashSize = 0;
DWORD Error = CryptGetHashParam(hHash, HP_HASHVAL, NULL, &HashSize, 0) ? 0 : ::GetLastError();
if ((Error == ERROR_MORE_DATA) || (!Error && HashSize))
{
Output.resize(HashSize);
TestWinFn(CryptGetHashParam(hHash, HP_HASHVAL, &Output[0], &HashSize, 0));
}
else
throw WindowsError(Error);
}

15 октября 2007

Windows exceptions

Функции WinAPI сообщают об ошибках в C-стиле - через коды ошибок. Классика C++ - сообщать об ошибках через исключения. Достаточно обернуть вызов функции в специальный "адаптер", и брюки превращаются в элегантные шорты:
#include <comdef.h>

inline void TESTHR(HRESULT hr)
{
if (FAILED(hr))
_com_issue_error(hr);
};

...
TESTHR(::CoCreateGuid(&UniqueID));
Типичный адаптер для обработки ошибок COM. Не помешает иметь такой же адаптер для не-COM функций:
#include <windows.h>

inline void TestWinFn(BOOL WindowsFunctionResult)
{
if (!WindowsFunctionResult)
throw WindowsError(::GetLastError());
}

...
TestWinFn(::ConvertSidToStringSid(SID, &StringSID));
Единственное - не хватает того самого класса WindowsError.
#include <windows.h>
#include <exception>
#include <Loki/ScopeGuard.h>

class WindowsError : public std::exception
{
public:
WindowsError(DWORD ErrorCode = ::GetLastError())
std::exception(Message(ErrorCode).c_str()),
_ErrorCode(ErrorCode) {}

DWORD ErrorCode() const { return _ErrorCode; }

private:
static std::wstring Message(DWORD ErrorCode)
{
std::wstring Result;
LPVOID Buffer = NULL;

if (::FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
ErrorCode,
0, // Default language
(LPTSTR) &Buffer,
0,
NULL))
{
LOKI_ON_BLOCK_EXIT(LocalFree, Buffer);
Result.assign((LPCWSTR)Buffer);
}

return Result;
}

private:
DWORD _ErrorCode;
};

PS. Кто не использует Loki - сюда.

01 октября 2007

WTL's cracked handlers

До чего же мне нравится идея "cracked handlers" в WTL, но вот реализация...

Идея cracked handlers такова. Обработчики оконных событий в WTL выглядят так:
class SomeWindowImpl
{
BEGIN_MSG_MAP(SomeWindowImpl)
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
MESSAGE_HANDLER(WM_SIZE, OnSize)
END_MSG_MAP()

LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
};
Потом приходится копаться в MSDN и выковыривать из wParam и lParam нужную информацию. Идея cracked handlers проста - сделать все по-человечески:
class SomeWindowImpl
{
BEGIN_MSG_MAP(SomeWindowImpl)
MSG_WM_LBUTTONDOWN(OnLButtonDown)
MSG_WM_SIZE(OnSize)
END_MSG_MAP()

LRESULT OnLButtonDown(UINT nFlags, CPoint Point);
LRESULT OnSize(UINT nType, CSize Size);
};
Все бы было нормально, если не видеть как реализованы все эти MSG_WM_xxx:
#define MSG_WM_LBUTTONDOWN(func) \
if (uMsg == WM_LBUTTONDOWN) \
{ \
SetMsgHandled(TRUE); \
func((UINT)wParam, CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); \
lResult = 0; \
if(IsMsgHandled()) \
return TRUE; \
}

#define MSG_WM_SIZE(func) \
if (uMsg == WM_SIZE) \
{ \
SetMsgHandled(TRUE); \
func((UINT)wParam, CSize(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); \
lResult = 0; \
if(IsMsgHandled()) \
return TRUE; \
}
Посмотришь на них - и начинаешь задумываться: что произойдет быстрее - появятся мониторы с 33 тыс.точек по горизонтали (или вертикали), или люди перестанут пользоваться программой, которую ты пишешь. Я, конечно, про GET_x_LPARAM - они работают до поры до времени, позже все же придется воспользоваться GetClientRect() и GetMessagePos(). Или не придется?... ;) Попробуй угадай.

А иногда там встречаются менее заметные вещи, типа использования unsigned вместо signed. Сразу и не догадаешься. Вобщем, в результате:
// #include <atlcrack.h>