diff options
author | John Ankarström <john@ankarstrom.se> | 2022-08-17 02:33:26 +0200 |
---|---|---|
committer | John Ankarström <john@ankarstrom.se> | 2022-08-17 02:33:26 +0200 |
commit | bd857d24443b8c8f5d2f58047c3f8ac5f058acea (patch) | |
tree | 0ef5f3c9abf5ccd6ee6a9e376ceac985de2409fd | |
parent | 8351d4c42c76584415e02a40d41f497a8f042c72 (diff) | |
download | EpisodeBrowser-bd857d24443b8c8f5d2f58047c3f8ac5f058acea.tar.gz |
Make FileView more type-safe.
-rw-r--r-- | c/data.cpp | 64 | ||||
-rw-r--r-- | c/data.h | 100 | ||||
-rw-r--r-- | c/debug.cpp | 4 | ||||
-rw-r--r-- | c/episodelistview.cpp | 16 | ||||
-rw-r--r-- | c/episodelistview.h | 2 | ||||
-rw-r--r-- | c/test.cpp | 59 | ||||
-rw-r--r-- | c/wcharptr.cpp | 2 | ||||
-rw-r--r-- | c/win.cpp | 2 | ||||
-rw-r--r-- | c/win.h | 4 |
9 files changed, 132 insertions, 121 deletions
@@ -1,65 +1 @@ -#include <memory> -#include <vector> - #include "data.h" -#include "win.h" - -template <typename T> -inline unsigned char* ValToBuf(const T& val, unsigned char* const buf) -{ - memcpy(buf, &val, sizeof(val)); - return buf+sizeof(val); -} - -template <typename T> -inline unsigned char* BufToVal(unsigned char* const buf, T& val) -{ - memcpy(&val, buf, sizeof(val)); - return buf+sizeof(val); -} - -FileView::FileView(const wchar_t* const filename, const size_t cb) -{ - hf = CreateFile(filename, GENERIC_READ|GENERIC_WRITE, - 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); - if (hf == INVALID_HANDLE_VALUE) { - if (GetLastError() == ERROR_FILE_NOT_FOUND) { - hf = CreateFile(filename, GENERIC_READ|GENERIC_WRITE, - 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); - if (hf == INVALID_HANDLE_VALUE) - throw Win32Error{}; - } else - throw Win32Error{}; - } - - LARGE_INTEGER cbMap; - cbMap.QuadPart = cb; - hm = CreateFileMapping(hf, nullptr, PAGE_READWRITE, - cbMap.HighPart, cbMap.LowPart, nullptr); - if (!hm) - throw Win32Error{}; - - view = reinterpret_cast<unsigned char*>(MapViewOfFile(hm, FILE_MAP_ALL_ACCESS, 0, 0, 0)); - if (!view) - throw Win32Error{}; -} - -FileView::~FileView() -{ - FlushViewOfFile(view, 0); - UnmapViewOfFile(view); - CloseHandle(hm); - CloseHandle(hf); -} - -void Write(unsigned char* buf, const ElvDataA& e) -{ - memcpy(buf, reinterpret_cast<const unsigned char*>(&e), sizeof(e)); -} - -ElvDataA* Read(unsigned char* const buf) -{ - if (buf[0] != 'a') - return nullptr; - return reinterpret_cast<ElvDataA*>(buf); -} @@ -1,15 +1,21 @@ #ifndef DATA_H #define DATA_H +#include <stdexcept> + #include "pl.h" #include "util.h" #include "wcharptr.h" +#include "win.h" + +/* The structs ending with A are written as-is to disk. As such, they + * should be regarded as immutable. If the format needs to be changed + * in the future, then new structs ending with B should be added. */ -/* ElvDataA and DlvDataA are written as-is to disk; note the careful - * alignment. As such, they should be regarded as immutable. If the - * format needs to be changed in the future, then new structs called - * ElvDataB and DlvDataB should be added. */ +/* Note that unsigned chars are 1-byte-aligned, wchar_t are + * 2-byte-aligned and ints are 4-byte-aligned (on x86 Windows). */ +/* Basic episode data presented in episode list view. */ struct ElvDataA { unsigned char version = 'a'; @@ -21,6 +27,7 @@ struct ElvDataA wchar_t title[128] = {0}; }; +/* Extra episode data presented in data list view. */ struct DlvDataA { wchar_t date[32] = {0}; @@ -30,21 +37,86 @@ struct DlvDataA wchar_t wiki[192] = {0}; }; +/* Variable template for obtaining the version of a given struct. */ +template <typename T> +constexpr inline unsigned char Version = T{}.version; + +template <typename T, typename = void> +constexpr inline bool HasVersion = false; + +template <typename T> +constexpr inline bool HasVersion<T, std::void_t<typename T::version>> = true; + +/* FileView objects manage a memory-mapped file. The view buffer may + * be treated as an array of a given type T. Note that reading and + * writing a view can throw structured exceptions. We ignore these. */ +template <typename T> struct FileView { - FileView(const wchar_t* filename, size_t cb); - ~FileView(); - inline operator unsigned char*() { return view; } - inline operator ElvDataA*() { return reinterpret_cast<ElvDataA*>(view); } + FileView(const wchar_t* filename, size_t c_) : c(c_) + { + hf = CreateFile(filename, GENERIC_READ|GENERIC_WRITE, + 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hf == INVALID_HANDLE_VALUE) { + if (GetLastError() == ERROR_FILE_NOT_FOUND) { + hf = CreateFile(filename, GENERIC_READ|GENERIC_WRITE, + 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hf == INVALID_HANDLE_VALUE) + throw Win32Error{}; + } else + throw Win32Error{}; + } + + LARGE_INTEGER cbMap; + cbMap.QuadPart = c*sizeof(T); + hm = CreateFileMapping(hf, nullptr, PAGE_READWRITE, + cbMap.HighPart, cbMap.LowPart, nullptr); + if (!hm) + throw Win32Error{}; + + view = reinterpret_cast<T*>(MapViewOfFile(hm, FILE_MAP_ALL_ACCESS, 0, 0, 0)); + if (!view) + throw Win32Error{}; + } + + ~FileView() + { + FlushViewOfFile(view, 0); + UnmapViewOfFile(view); + CloseHandle(hm); + CloseHandle(hf); + } + + /* Access element by index, performing bounds check and, if + * applicable for T, version validation. */ + T& At(size_t i) + { + if (i >= c) + throw std::out_of_range("index larger than buffer"); + + T& t = view[i]; + + if constexpr (HasVersion<T>) + if (t.version != Version<T>) + throw std::runtime_error("invalid struct version"); + + /* TODO: Use a custom exception type that informs the + * user of possible data corruption. */ + + return t; + } + + inline operator T*() noexcept + { + return view; + } + HANDLE hf; HANDLE hm; - unsigned char* view; - /* TODO: Handle exceptions on read and write... */ + T* view; + size_t c; }; - -void Write(unsigned char* buf, const ElvDataA& e); -ElvDataA* Read(unsigned char* buf); - + inline int FromWeb(const int iEp, ElvDataA& e, DlvDataA& d) noexcept { WcharPtr title, wiki, date, source, hint; diff --git a/c/debug.cpp b/c/debug.cpp index 6b5baff..b23df37 100644 --- a/c/debug.cpp +++ b/c/debug.cpp @@ -19,13 +19,13 @@ Benchmark::Benchmark(const char* const name, const int id, const int avgmax) if (!freq) { static LARGE_INTEGER liFreq; if (!QueryPerformanceFrequency(&liFreq)) - throw Win32Error(); + throw Win32Error{}; freq = liFreq.QuadPart; } LARGE_INTEGER liTicks; if (!QueryPerformanceCounter(&liTicks)) - throw Win32Error(); + throw Win32Error{}; ticks = liTicks.QuadPart; } diff --git a/c/episodelistview.cpp b/c/episodelistview.cpp index 0451132..be753a6 100644 --- a/c/episodelistview.cpp +++ b/c/episodelistview.cpp @@ -12,9 +12,11 @@ #include "util.h" #include "win.h" +constexpr size_t MAX_EPISODES = 8192; + EpisodeListView::EpisodeListView(const HWND hWndParent) : ListView(hWndParent, reinterpret_cast<HMENU>(IDC_EPISODELISTVIEW), 0), - m_fv(L"elvdata.dat", sizeof(ElvDataA)*8192) + m_fv(L"elvdata.dat", MAX_EPISODES) { LVCOLUMN lvc; @@ -55,7 +57,7 @@ void EpisodeListView::HandleContextMenu(const WORD command) LVITEM lvi = {LVIF_PARAM, -1}; while (FindNextItem(&lvi, LVNI_SELECTED)) { - ElvDataA& e = static_cast<ElvDataA*>(m_fv)[lvi.lParam-1]; + ElvDataA& e = m_fv.At(lvi.lParam-1); if (ID_SUBGROUP(command) == IDG_CTX_RATE) { /* Process rate commands. */ @@ -117,7 +119,7 @@ LRESULT EpisodeListView::HandleNotify(const LPARAM lParam) case LVN_GETDISPINFO: /* Display item. */ { NMLVDISPINFO* const nm = reinterpret_cast<NMLVDISPINFO*>(lParam); - ElvDataA& e = reinterpret_cast<ElvDataA*>(m_fv.view)[nm->item.lParam-1]; + ElvDataA& e = m_fv.At(nm->item.lParam-1); wchar_t* vs[] = {e.siEp, e.title, e.sRating}; /* ELVSIEPISODE, ELVSITITLE, ELVSIRATING */ nm->item.pszText = vs[nm->item.iSubItem]; return 0; @@ -166,7 +168,7 @@ LRESULT EpisodeListView::HandleNotify(const LPARAM lParam) break; case CDDS_ITEMPREPAINT: { - const ElvDataA& e = static_cast<ElvDataA*>(m_fv)[nm->nmcd.lItemlParam-1]; + const ElvDataA& e = m_fv.At(nm->nmcd.lItemlParam-1); if (!e.bWatched) { extern HFONT g_hfBold; Require(SelectObject(nm->nmcd.hdc, g_hfBold)); @@ -329,8 +331,8 @@ int CALLBACK EpisodeListView::SortProc(const LPARAM iItem1, const LPARAM iItem2, * If m_iSortCol is negative, the order is descending. */ const int order = Cmp(elv->m_iSortCol, 0); - const ElvDataA& e1 = static_cast<ElvDataA*>(elv->m_fv)[lvi1.lParam-1]; - const ElvDataA& e2 = static_cast<ElvDataA*>(elv->m_fv)[lvi2.lParam-1]; + const ElvDataA& e1 = elv->m_fv[lvi1.lParam-1]; + const ElvDataA& e2 = elv->m_fv.At(lvi2.lParam-1); switch (abs(elv->m_iSortCol)-1) { case ELVSIEPISODE: @@ -398,7 +400,7 @@ void EpisodeListView::Update() SendMessage(hWnd, WM_SETREDRAW, FALSE, 0); ListView_DeleteAllItems(hWnd); for (int iEp = 1; iEp <= cEp; iEp++) { - ElvDataA e = static_cast<ElvDataA*>(m_fv)[iEp-1]; + ElvDataA e = m_fv.At(iEp-1); if (!e.siEp[0]) continue; diff --git a/c/episodelistview.h b/c/episodelistview.h index 0604d15..f58f3ca 100644 --- a/c/episodelistview.h +++ b/c/episodelistview.h @@ -33,7 +33,7 @@ struct EpisodeListView : public ListView private: int m_iSortCol; static int CALLBACK SortProc(LPARAM lParam1, LPARAM lParam2, LPARAM extra); - FileView m_fv; + FileView<ElvDataA> m_fv; }; #endif @@ -54,40 +54,42 @@ TESTS FromProlog(6, e1_0); FromProlog(10, e2_0); { - FileView fv{L"tmp.dat", sizeof(ElvDataA)*2}; - Write(fv, e1_0); - Write(reinterpret_cast<unsigned char*>(static_cast<ElvDataA*>(fv)+1), e2_0); + FileView<ElvDataA> fv{L"tmp.dat", 2}; + memcpy(fv+0, &e1_0, sizeof(e1_0)); + memcpy(fv+1, &e2_0, sizeof(e2_0)); } { - FileView fv{L"tmp.dat", sizeof(ElvDataA)}; - ElvDataA* e1 = Read(fv); - if (e1_0.rating != e1->rating) - FAIL("rating is different (%d/%d)", e1_0.rating, e1->rating); - if (e1_0.bWatched != e1->bWatched) + FileView<ElvDataA> fv{L"tmp.dat", 2}; + const ElvDataA& e1 = *fv; + if (e1.version != 'a') + FAIL("version is different"); + if (e1_0.rating != e1.rating) + FAIL("rating is different (%d/%d)", e1_0.rating, e1.rating); + if (e1_0.bWatched != e1.bWatched) FAIL("bWatched is different"); - if (e1_0.bTVOriginal != e1->bTVOriginal) + if (e1_0.bTVOriginal != e1.bTVOriginal) FAIL("bTVOriginal is different"); - if (wcscmp(e1_0.sRating, e1->sRating) != 0) + if (wcscmp(e1_0.sRating, e1.sRating) != 0) FAIL("sRating is different"); - if (wcscmp(e1_0.siEp, e1->siEp) != 0) + if (wcscmp(e1_0.siEp, e1.siEp) != 0) FAIL("siEp is different"); - if (wcscmp(e1_0.title, e1->title) != 0) + if (wcscmp(e1_0.title, e1.title) != 0) FAIL("title is different"); } { - FileView fv{L"tmp.dat", sizeof(ElvDataA)*2}; - ElvDataA* e2 = Read(reinterpret_cast<unsigned char*>(static_cast<ElvDataA*>(fv)+1)); - if (e2_0.rating != e2->rating) - FAIL("rating is different (%d/%d)", e2_0.rating, e2->rating); - if (e2_0.bWatched != e2->bWatched) + FileView<unsigned char> fv{L"tmp.dat", 2*sizeof(ElvDataA)}; + ElvDataA& e2 = *reinterpret_cast<ElvDataA*>(&fv.At(sizeof(ElvDataA))); + if (e2_0.rating != e2.rating) + FAIL("rating is different (%d/%d)", e2_0.rating, e2.rating); + if (e2_0.bWatched != e2.bWatched) FAIL("bWatched is different"); - if (e2_0.bTVOriginal != e2->bTVOriginal) + if (e2_0.bTVOriginal != e2.bTVOriginal) FAIL("bTVOriginal is different"); - if (wcscmp(e2_0.sRating, e2->sRating) != 0) + if (wcscmp(e2_0.sRating, e2.sRating) != 0) FAIL("sRating is different"); - if (wcscmp(e2_0.siEp, e2->siEp) != 0) + if (wcscmp(e2_0.siEp, e2.siEp) != 0) FAIL("siEp is different"); - if (wcscmp(e2_0.title, e2->title) != 0) + if (wcscmp(e2_0.title, e2.title) != 0) FAIL("title is different"); } //DeleteFile(L"tmp.dat"); @@ -100,20 +102,19 @@ TESTS return; { - FileView fv{L"tmp.dat", sizeof(ElvDataA)*8192}; - unsigned char* p = fv; + FileView<ElvDataA> fv{L"tmp.dat", 8192}; + ElvDataA* p = fv; for (int iEp = 1; iEp <= cEp; iEp++) { ElvDataA e; FromProlog(iEp, e); - Write(p, e); - p += sizeof(e); + memcpy(p, &e, sizeof(e)); + p++; } } { - FileView fv{L"tmp.dat", sizeof(ElvDataA)*8192}; - ElvDataA* ve = reinterpret_cast<ElvDataA*>(fv.view); - ElvDataA& e = ve[9]; + FileView<ElvDataA> fv{L"tmp.dat", 8192}; + ElvDataA& e = fv.At(9); if (wcscmp(e.title, L"Pro Soccer Player Blackmail Case") != 0) FAIL("title is not correct"); } @@ -128,7 +129,7 @@ int RunTests() //EpisodeDataFromWeb{}, EpisodeDataFromProlog{}, IO{}, - MigrateElvDataFromPrologToDisk{}, + //MigrateElvDataFromPrologToDisk{}, }; printf("Results (%llu tests):\n", sizeof(tests)/sizeof(*tests)); diff --git a/c/wcharptr.cpp b/c/wcharptr.cpp index f659767..f7cb948 100644 --- a/c/wcharptr.cpp +++ b/c/wcharptr.cpp @@ -11,7 +11,7 @@ WcharPtr WcharPtr::FromNarrow(const char* const src, const int cp) wchar_t* dst = new wchar_t[cchWide]; if (!MultiByteToWideChar(cp, 0, src, cchNarrow, dst, cchWide)) { delete dst; - throw Win32Error(); + throw Win32Error{}; } return dst; } @@ -145,7 +145,7 @@ Library::Library(const wchar_t* const lib) { m_hModule = LoadLibrary(lib); if (!m_hModule) - throw Win32Error(); + throw Win32Error{}; } Library::~Library() @@ -53,7 +53,7 @@ T* Library::GetProcAddress(const char* const szProc) noexcept template <typename T> inline T Require(const T x) { - if (!x) throw Win32Error(); + if (!x) throw Win32Error{}; return x; } @@ -62,7 +62,7 @@ template <typename T> inline T Prefer(const T x) { if (!x) { - EBMessageBox(Win32Error().What(), L"System Error", MB_ICONWARNING); + EBMessageBox(Win32Error{}.What(), L"System Error", MB_ICONWARNING); return (T)0; } return x; |