Rust bitfields and enumerations C++ style
Asked Answered
C

1

6

I'm a Rust beginner which comes from C/C++. To start off I tried to create a simple "Hello-World" like program for Microsoft Windows using user32.MessageBox where I stumbled upon a problem related to bitfields. Disclaimer: All code snippets were written in the SO editor and might contain errors.

MessageBox "Hello-World" in C

The consolidated C declarations needed to call the UTF-16LE version of the function are:

enum MessageBoxResult {
    IDFAILED,
    IDOK,
    IDCANCEL,
    IDABORT,
    IDRETRY,
    IDIGNORE,
    IDYES,
    IDNO,
    IDTRYAGAIN = 10,
    IDCONTINUE
};

enum MessageBoxType {
    // Normal enumeration values.
    MB_OK,
    MB_OKCANCEL,
    MB_ABORTRETRYIGNORE,
    MB_YESNOCANCEL,
    MB_YESNO,
    MB_RETRYCANCEL,
    MB_CANCELTRYCONTINUE,

    MB_ICONERROR            = 0x10UL,
    MB_ICONQUESTION         = 0x20UL,
    MB_ICONEXCLAMATION      = 0x30UL,
    MB_ICONINFORMATION      = 0x40UL,

    MB_DEFBUTTON1           = 0x000UL,
    MB_DEFBUTTON2           = 0x100UL,
    MB_DEFBUTTON3           = 0x200UL,
    MB_DEFBUTTON4           = 0x300UL,

    MB_APPLMODAL            = 0x0000UL,
    MB_SYSTEMMODAL          = 0x1000UL,
    MB_TASKMODAL            = 0x2000UL,

    // Flag values.
    MB_HELP                 = 1UL << 14,

    MB_SETFOREGROUND        = 1UL << 16,
    MB_DEFAULT_DESKTOP_ONLY = 1UL << 17,
    MB_TOPMOST              = 1UL << 18,
    MB_RIGHT                = 1UL << 19,
    MB_RTLREADING           = 1UL << 20,
    MB_SERVICE_NOTIFICATION = 1UL << 21
};

MessageBoxResult __stdcall MessageBoxW(
    HWND            hWnd,
    const wchar_t * lpText,
    const wchar_t * lpCaption,
    MessageBoxType  uType
);

Usage:

MessageBoxType mbType = MB_YESNO | MB_ICONEXCLAMATION | MB_DEFBUTTON3 | MB_TOPMOST;
if ((mbType & 0x0F /* All bits for buttons */ == MB_YESNO) && (mbType & 0xF0 /* All bits for icons */ == MB_ICONEXCLAMATION) && (mbType & 0xF00 /* All bits for default buttons */ == MB_DEFBUTTON3) && (mbType & MB_TOPMOST != 0)) {
    MessageBoxW(NULL, L"Text", L"Title", mbType);
}

The MessageBoxType enumeration contains enumeration values and flag values. The problem with that is that MB_DEFBUTTON2 and MB_DEFBUTTON3 can be used together and "unexpectedly" result in MB_DEFBUTTON4. Also the access is quite error prone and ugly, I have to |, & and shift everything manually when checking for flags in the value.

MessageBox "Hello-World" in C++

In C++ the same enumeration can be cleverly put into a structure, which has the same size as the enumeration and makes the access way easier, safer and prettier. It makes use of bitfields - the layout of bitfields not defined by the C standard, but since I only want to use it for x86-Windows it is always the same, so I can rely on it.

enum class MessageBoxResult : std::uint32_t {
    Failed,
    Ok,
    Cancel,
    Abort,
    Retry,
    Ignore,
    Yes,
    No,
    TryAgain = 10,
    Continue
};

enum class MessageBoxButton : std::uint32_t {
    Ok,
    OkCancel,
    AbortRetryIgnore,
    YesNoCancel,
    YesNo,
    RetryCancel,
    CancelTryContinue
};

enum class MessageBoxDefaultButton : std::uint32_t {
    One,
    Two,
    Three,
    Four
};

// Union so one can access all flags as a value and all boolean values separately.
union MessageBoxFlags {
    enum class Flags : std::uint32_t {
        None,
        Help                = 1UL << 0,
        SetForeground       = 1UL << 2,
        DefaultDesktopOnly  = 1UL << 3,
        TopMost             = 1UL << 4,
        Right               = 1UL << 5,
        RtlReading          = 1UL << 6,
        ServiceNotification = 1UL << 7
    };

    // Flags::operator|, Flags::operator&, etc. omitted here.

    Flags flags;
    struct {
        bool help                   : 1;
        char _padding0              : 1;
        bool setForeground          : 1;
        bool defaultDesktopOnly     : 1;
        bool topMost                : 1;
        bool right                  : 1;
        bool rtlReading             : 1;
        bool serviceNotification    : 1;
        char _padding1              : 8;
        char _padding2              : 8;
        char _padding3              : 8;
    };

    constexpr MessageBoxFlags(const Flags flags = Flags::None)
        : flags(flags) {}
};

enum class MessageBoxIcon : std::uint32_t {
    None,
    Stop,
    Question,
    Exclamation,
    Information
};

enum class MessageBoxModality : std::uint32_t {
    Application,
    System,
    Task
};

union MessageBoxType {
    std::uint32_t value;
    struct {                                          // Used bits                                   Minimum (Base 2)                          Maximum (Base 2)                          Min (Base 16) Max (Base 16)
        MessageBoxButton button                 :  4; // 0000.0000.0000.0000|0000.0000.0000.XXXX     0000.0000.0000.0000|0000.0000.0000.0000 - 0000.0000.0000.0000|0000.0000.0000.0110 : 0x0000.0000 - 0x0000.0006
        MessageBoxIcon icon                     :  4; // 0000.0000.0000.0000|0000.0000.XXXX.0000     0000.0000.0000.0000|0000.0000.0001.0000 - 0000.0000.0000.0000|0000.0000.0100.0000 : 0x0000.0010 - 0x0000.0040
        MessageBoxDefaultButton defaultButton   :  4; // 0000.0000.0000.0000|0000.XXXX.0000.0000     0000.0000.0000.0000|0000.0001.0000.0000 - 0000.0000.0000.0000|0000.0011.0000.0000 : 0x0000.0100 - 0x0000.0300
        MessageBoxModality modality             :  2; // 0000.0000.0000.0000|00XX.0000.0000.0000     0000.0000.0000.0000|0001.0000.0000.0000 - 0000.0000.0000.0000|0010.0000.0000.0000 : 0x0000.1000 - 0x0000.2000
        MessageBoxFlags::Flags flags            :  8; // 0000.0000.00XX.XXXX|XX00.0000.0000.0000     0000.0000.0000.0000|0100.0000.0000.0000 - 0000.0000.0010.0000|0000.0000.0000.0000 : 0x0000.4000 - 0x0020.0000
        std::uint32_t _padding0                 : 10; // XXXX.XXXX.XX00.0000|0000.0000.0000.0000     
    };

    MessageBoxType(
        const MessageBoxButton button,
        const MessageBoxIcon icon = MessageBoxIcon::None,
        const MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton::One,
        const MessageBoxModality modality = MessageBoxModality::Application,
        const MessageBoxFlags::Flags flags = MessageBoxFlags::Flags::None
    ) : button(button), icon(icon), defaultButton(defaultButton), modality(modality), flags(flags), _padding0(0) {}

    MessageBoxType() : value(0) {}
};

MessageBoxResult __stdcall MessageBoxW(
    HWND            parentWindow,
    const wchar_t * text,
    const wchar_t * caption,
    MessageBoxType  type
);

Usage:

auto mbType = MessageBoxType(MessageBoxButton::YesNo, MessageBoxIcon::Exclamation, MessageBoxDefaultButton::Three, MessageBoxModality::Application, MessageBoxFlags::Flags::TopMost);
if (mbType.button == MessageBoxButton::YesNo && mbType.icon == MessageBoxIcon::Exclamation && mbType.defaultButton == MessageBoxDefaultButton::Three && mbType.flags.topMost) {
    MessageBoxW(nullptr, L"Text", L"Title", mbType);
}

With this C++ version I can access flags as boolean values and have enumeration classes for the other types, all while it still being a simple std::uint32_t in memory. Now I struggled to implement this in Rust.

MessageBox "Hello-World" in Rust

#[repr(u32)]
enum MessageBoxResult {
    Failed,
    Ok,
    Cancel,
    Abort,
    Retry,
    Ignore,
    Yes,
    No,
    TryAgain = 10,
    Continue
}

#[repr(u32)]
enum MessageBoxButton {
    Ok,
    OkCancel,
    AbortRetryIgnore,
    YesNoCancel,
    YesNo,
    RetryCancel,
    CancelTryContinue
}

#[repr(u32)]
enum MessageBoxDefaultButton {
    One,
    Two,
    Three,
    Four
}

#[repr(u32)]
enum MessageBoxIcon {
    None,
    Stop,
    Question,
    Exclamation,
    Information
}

#[repr(u32)]
enum MessageBoxModality {
    Application,
    System,
    Task
}

// MessageBoxFlags and MessageBoxType ?

I know about the WinApi crate which to my understanding is generated automatically from VC++-header files which doesn't help, because I will have the same problems as in C. I also saw the bitflags macro but it seems to me it doesn't handle this kind of "complexity".

How would I implement MessageBoxFlags and MessageBoxType in Rust, so I can access it in a nice (not necessarily the same) way as in my C++ implementation?

Citrange answered 10/4, 2018 at 15:22 Comment(3)
Just a small note: winapi is not generated automatically. It's transcribed by hand. Every attempt made to bind win32 to Rust automatically has ended in failure, the headers standing triumphant over the bloodied corpses of the conversion programs, their authors broken in mind and spirit. There's a reason the head of the winapi project is a rabbit.Exaltation
To be slightly more on-topic: I wouldn't bother trying to do this via unions and bitflags. I don't think the bitflags crate was designed for this level of contortion. I'd just write getters/setters on a #[repr(C)] struct MessageBoxFlags(u8);.Exaltation
Did you see the crate bitfield?Amylaceous
C
7

The bitfield crate @Boiethios mentioned is kind of what I wanted. I created my own first macro crate bitfield which allows me to write the following:

#[bitfield::bitfield(32)]
struct Styles {
    #[field(size = 4)] button: Button,
    #[field(size = 4)] icon: Icon,
    #[field(size = 4)] default_button: DefaultButton,
    #[field(size = 2)] modality: Modality,
    style: Style
}

#[derive(Copy, Clone, bitfield::Flags)]
#[repr(u8)]
enum Style {
    Help = 14,
    Foreground = 16,
    DefaultDesktopOnly,
    TopMost,
    Right,
    RightToLeftReading,
    ServiceNotification
}

#[derive(Clone, Copy, bitfield::Field)]
#[repr(u8)]
enum Button {
    Ok,
    OkCancel,
    AbortRetryIgnore,
    YesNoCancel,
    YesNo,
    RetryCancel,
    CancelTryContinue
}

#[derive(Clone, Copy, bitfield::Field)]
#[repr(u8)]
enum DefaultButton {
    One,
    Two,
    Three,
    Four
}

#[derive(Clone, Copy, bitfield::Field)]
#[repr(u8)]
enum Icon {
    None,
    Stop,
    Question,
    Exclamation,
    Information
}

#[derive(Clone, Copy, bitfield::Field)]
#[repr(u8)]
enum Modality {
    Application,
    System,
    Task
}

I can then use the code like this:

// Verbose:
let styles = Styles::new()
    .set_button(Button::CancelTryContinue)
    .set_icon(Icon::Exclamation)
    .set_style(Style::Foreground, true)
    .set_style(Style::TopMost, true);

// Alternatively:
let styles = Styles::new() +
    Button::CancelTryContinue +
    Icon::Exclamation +
    Style::Foreground +
    Style::TopMost;

let result = user32::MessageBoxW(/* ... */, styles);
Citrange answered 26/4, 2018 at 17:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.