I found a solution: you can use DialogBoxParam method/macro instead of DialogBox method/macro. The DialogBox wraps DialogBoxParam but does not expose the dwInitParam, which you can use for passing data in the LPARAM for a WM_INITDIALOG message.
Here, I just pass this as actual parameter for the formal parameter dwInitParam of DialogBoxParam:
const INT_PTR result_dialog = DialogBoxParam(nullptr, MAKEINTRESOURCE(IDD_GRAPHICS_SETTINGS),
nullptr, SettingsDialogProcDelegate, reinterpret_cast< LPARAM >(this));
Next, the class member Window callback is defined as:
INT_PTR CALLBACK DeviceEnumeration::SettingsDialogProcDelegate(
HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
DeviceEnumeration *device_enumeration = nullptr;
if (WM_INITDIALOG == uMsg) {
device_enumeration = reinterpret_cast< DeviceEnumeration * >(lParam);
const LONG_PTR replacement_value = reinterpret_cast< LONG_PTR >(device_enumeration);
// Changes an attribute of the specified window.
// 1. A handle to the window and, indirectly, the class to which the window belongs.
// 2. Sets the user data associated with the window.
// 3. The replacement value.
SetWindowLongPtr(hwndDlg, GWLP_USERDATA, replacement_value);
}
else {
// Retrieves information about the specified window.
// 1. A handle to the window and, indirectly, the class to which the window belongs.
// 2. Retrieves the user data associated with the window.
device_enumeration = reinterpret_cast< DeviceEnumeration * >(GetWindowLongPtr(hwndDlg, GWLP_USERDATA));
}
return device_enumeration->SettingsDialogProc(hwndDlg, uMsg, wParam, lParam);
}