Skip to content

Commit 095704e

Browse files
committed
Add CSV export
1 parent 76a321d commit 095704e

15 files changed

+187
-2
lines changed

docs/WindowsHookEx-ExportCSV.png

22.2 KB
Loading

docs/assets/css/style.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@
88
width: 120px !important;
99
}
1010

11+
sup
12+
{
13+
font-size: 75%;
14+
vertical-align: super;
15+
}

docs/index.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@ Additionally, when working on [ReactOS](https://reactos.org) it is a good tool t
4242
* Hook messages received, with decoded arguments
4343
* All received events include the process name and Thread ID on which they are received!
4444
* Show processes where the hook dll is loaded
45+
* Allows exporting the recorded hooks to CSV [^1]
4546
* An Atom viewer, showing [Global and User Atoms](https://docs.microsoft.com/en-us/windows/win32/dataxchg/about-atom-tables).
4647
* Highlight Added Atoms (green)
4748
* Highlight Removed Atoms (red)
4849
* Works on Windows XP / Server 2003 and newer
4950

51+
[^1]: New in version 1.8.0.
5052

5153

5254
## Screenshots
@@ -56,7 +58,11 @@ Available options:
5658

5759

5860
Hook types:
59-
![Hook types](WindowsHookEx-HookTypes.png)
61+
![Hook types](WindowsHookEx-HookTypes.png)
62+
63+
64+
CSV Export:
65+
![CSV Export](WindowsHookEx-ExportCSV.png)
6066

6167

6268
Atom viewer:

src/CAboutWindow.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//
1+
//
22
// WindowsHookEx - Test the behavior of the api SetWindowsHookEx
33
// Copyright (c) 2020 Mark Jansen
44
// UI Framework: Wizard-2020 Example from https://building.enlyze.com/posts/writing-win32-apps-like-its-2020-part-1

src/CHookOutputPage.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88

99
#include "Wizard-2020.h"
1010
#include <cassert>
11+
#include <algorithm>
1112
#include "HookDll/Shared.h"
1213

1314
#define IDC_MENU_CLEAR_LIST 600
15+
#define IDC_MENU_EXPORT_CSV 601
1416

1517

1618
const static int g_ColumnWidths[] = {
@@ -193,8 +195,10 @@ void
193195
CHookOutputPage::UpdateMenu(HMENU hMenu)
194196
{
195197
std::wstring clearText = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_MENU_CLEAR_LIST);
198+
std::wstring exportText = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_MENU_EXPORT_CSV);
196199

197200
AppendMenuW(hMenu, MF_STRING, IDC_MENU_CLEAR_LIST, clearText.c_str());
201+
AppendMenuW(hMenu, MF_STRING, IDC_MENU_EXPORT_CSV, exportText.c_str());
198202
AppendMenuW(hMenu, MF_SEPARATOR, 0, nullptr);
199203
}
200204

@@ -206,6 +210,9 @@ CHookOutputPage::_OnCommand(WPARAM wParam)
206210
case IDC_MENU_CLEAR_LIST:
207211
ListView_DeleteAllItems(m_hList);
208212
break;
213+
case IDC_MENU_EXPORT_CSV:
214+
_OnExportCsv();
215+
break;
209216
}
210217

211218
return 0;
@@ -295,3 +302,48 @@ CHookOutputPage::OnTimer(WPARAM wParam)
295302

296303
return 0;
297304
}
305+
306+
void
307+
CHookOutputPage::_OnExportCsv()
308+
{
309+
std::wstring wstrSaveFilter = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_SAVE_FILTER);
310+
std::replace(wstrSaveFilter.begin(), wstrSaveFilter.end(), L'|', L'\0');
311+
312+
std::wstring wstrSaveTitle = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_SAVE_TITLE);
313+
314+
315+
std::wstring wstrFileToSave = std::wstring(MAX_PATH, L'\0');
316+
317+
OPENFILENAMEW ofn = {};
318+
ofn.lStructSize = sizeof(ofn);
319+
ofn.hwndOwner = m_hWnd;
320+
ofn.lpstrFilter = wstrSaveFilter.c_str();
321+
ofn.lpstrFile = wstrFileToSave.data();
322+
ofn.nMaxFile = MAX_PATH;
323+
ofn.lpstrTitle = wstrSaveTitle.c_str();
324+
ofn.lpstrDefExt = L".csv";
325+
326+
if (GetSaveFileNameW(&ofn))
327+
{
328+
auto pos = wstrFileToSave.find(L'\0');
329+
if (pos != std::wstring::npos)
330+
wstrFileToSave.resize(pos);
331+
332+
auto hFile = make_unique_handle(CreateFileW(wstrFileToSave.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
333+
if (hFile.get() == INVALID_HANDLE_VALUE)
334+
{
335+
ErrorBox(L"Unable to open file.\r\nError: " + std::to_wstring(GetLastError()));
336+
return;
337+
}
338+
try
339+
{
340+
export_csv(hFile.get(), m_hList);
341+
}
342+
catch (const export_error& err)
343+
{
344+
std::string tmpA = err.what();
345+
std::wstring tmpW(tmpA.begin(), tmpA.end());
346+
ErrorBox(tmpW.c_str());
347+
}
348+
}
349+
}

src/CHookOutputPage.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,6 @@ class CHookOutputPage : public CPage
4242
LRESULT _OnDestroy();
4343
LRESULT _OnCommand(WPARAM wParam);
4444
LRESULT _OnSize();
45+
void _OnExportCsv();
4546
static LRESULT CALLBACK _WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
4647
};

src/WindowsHookEx.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@
229229
<ClCompile Include="CMainWindow.cpp" />
230230
<ClCompile Include="CHookOutputPage.cpp" />
231231
<ClCompile Include="AlternateCallbacks.cpp" />
232+
<ClCompile Include="csv_export.cpp" />
232233
<ClCompile Include="Wizard-2020.cpp" />
233234
<ClCompile Include="utils.cpp" />
234235
</ItemGroup>
@@ -239,6 +240,7 @@
239240
<ClInclude Include="CMainWindow.h" />
240241
<ClInclude Include="CPage.h" />
241242
<ClInclude Include="CHookOutputPage.h" />
243+
<ClInclude Include="csv_export.h" />
242244
<ClInclude Include="resource.h" />
243245
<ClInclude Include="version.h" />
244246
<ClInclude Include="Wizard-2020.h" />

src/WindowsHookEx.vcxproj.filters

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
<ClCompile Include="AlternateCallbacks.cpp">
3939
<Filter>Source Files</Filter>
4040
</ClCompile>
41+
<ClCompile Include="csv_export.cpp">
42+
<Filter>Source Files</Filter>
43+
</ClCompile>
4144
</ItemGroup>
4245
<ItemGroup>
4346
<ClInclude Include="targetver.h">
@@ -76,6 +79,9 @@
7679
<ClInclude Include="version.h">
7780
<Filter>Header Files</Filter>
7881
</ClInclude>
82+
<ClInclude Include="csv_export.h">
83+
<Filter>Header Files</Filter>
84+
</ClInclude>
7985
</ItemGroup>
8086
<ItemGroup>
8187
<ResourceCompile Include="lang\en-US.rc">

src/Wizard-2020.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
#include "unique_resource.h"
3232
#include "win32_wrappers.h"
33+
#include "csv_export.h"
3334

3435
#include "resource.h"
3536
#include "utils.h"

src/csv_export.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//
2+
// WindowsHookEx - Test the behavior of the api SetWindowsHookEx
3+
// Copyright (c) 2023 Mark Jansen
4+
// UI Framework: Wizard-2020 Example from https://building.enlyze.com/posts/writing-win32-apps-like-its-2020-part-1
5+
// Copyright (c) 2020 Colin Finck, ENLYZE GmbH
6+
// SPDX-License-Identifier: MIT
7+
//
8+
9+
#include "Wizard-2020.h"
10+
11+
static void
12+
write(HANDLE hFile, const char* text)
13+
{
14+
DWORD cbWritten;
15+
DWORD cbLen = (DWORD)strlen(text);
16+
if (!WriteFile(hFile, text, cbLen, &cbWritten, nullptr))
17+
throw export_error("Unable to write data.\r\nError: " + std::to_string(GetLastError()));
18+
if (cbWritten != cbLen)
19+
throw export_error("Could not write all data.");
20+
}
21+
22+
inline static BOOL
23+
ListView_GetColumnA(HWND hwnd, int columnCount, LVCOLUMNA& column)
24+
{
25+
return (BOOL)SendMessageA(hwnd, LVM_GETCOLUMNA, (WPARAM)columnCount, (LPARAM)&column);
26+
}
27+
28+
inline static BOOL
29+
ListView_GetItemA(HWND hwnd, LV_ITEMA& item)
30+
{
31+
return (BOOL)SendMessageA(hwnd, LVM_GETITEMA, 0, (LPARAM)&item);
32+
}
33+
34+
void
35+
export_csv(HANDLE hFile, HWND hList)
36+
{
37+
LVCOLUMNA column = { LVCF_TEXT };
38+
std::string Buffer(MAX_PATH, '\0');
39+
column.pszText = Buffer.data();
40+
column.cchTextMax = (int)Buffer.size();
41+
int columnCount = 0;
42+
43+
write(hFile, "sep=,\r\n"); // Tell excel that we use a 'comma' as delimiter
44+
for (; ListView_GetColumnA(hList, columnCount, column); columnCount++)
45+
{
46+
// Each cell in the header is quoted, so we don't have to check for comma's in the data
47+
if (!columnCount)
48+
write(hFile, "\"");
49+
else
50+
write(hFile, "\",\"");
51+
write(hFile, Buffer.c_str());
52+
}
53+
write(hFile, "\"\r\n");
54+
55+
const int itemCount = ListView_GetItemCount(hList);
56+
Buffer.resize(512);
57+
for (int index = 0; index < itemCount; index++)
58+
{
59+
LV_ITEMA item = {};
60+
for (int col = 0; col < columnCount; ++col)
61+
{
62+
item.iItem = index;
63+
item.iSubItem = col;
64+
item.mask = LVIF_TEXT;
65+
item.pszText = Buffer.data();
66+
item.cchTextMax = (int)Buffer.size();
67+
if (ListView_GetItemA(hList, item))
68+
{
69+
// Each cell in the data is quoted, so we don't have to check for comma's in the data
70+
if (!col)
71+
write(hFile, "\"");
72+
else
73+
write(hFile, "\",\"");
74+
75+
write(hFile, Buffer.c_str());
76+
}
77+
}
78+
write(hFile, "\"\r\n");
79+
}
80+
}

0 commit comments

Comments
 (0)