1

Say I have the code below using the WINDOW struct from NCurses.

#include "ncurses.h"

class Window : public WINDOW {
    Window(int winHeight, int winWidth, int startX, int startY) {
        newwin(winHeight, winWidth, startX, startY);
    }
    void refresh() {
        wrefresh(this);
    }
};

int main() {
    initscr();

    Window win = Window(5, 5, 5, 5);
    win.refresh();

    endwin();
    return 0;
}

Is there a proper way to have C++ classes inherit C structs so that they can be passed as them, similar to how C++ classes that have inherited each other can?

René Höhle
  • 26,716
  • 22
  • 73
  • 82
WilliamB
  • 65
  • 5
  • in C++ a "C struct" is just a class. https://stackoverflow.com/questions/54585/when-should-you-use-a-class-vs-a-struct-in-c – 463035818_is_not_an_ai Feb 17 '21 at 17:33
  • 3
    Before even considering the possible answers: doing this goes against basic principles of good desgin. Do **NOT** inherit WINDOW, but make it a data member. Your Window class is not a ncurses handle. – spectras Feb 17 '21 at 17:35
  • 1
    ncurses doesn't seem to want to let you touch `WINDOW`s. In particular, `newwin` allocates the `WINDOW`s by itself, so there's not much point in trying to "replace" them with `Window`, since you're not in control of the object creation. If you want a "more C++" interface to the ncurses functions you want to *wrap* the *pointer*, not inherit from the structure. – HTNW Feb 17 '21 at 17:37
  • I bet you can't inherit from a C base class, as in C, structs don't have vtables. – Joseph Larson Feb 17 '21 at 17:37
  • 2
    Well, simple inheritance is the way MFC (C++) classes like `CRect` embrace the old WinAPI (C) structures like `RECT`: `class CRect : public tagRECT {...` But those classes also provide an operator that returns a *pointer* to the inherited structure, such as `operator LPRECT();`. – Adrian Mole Feb 17 '21 at 17:40
  • @JosephLarson vtables aren't necessarily participating in inheritance, that's only for structs or classes with `virtual` functions, and C structs don't have functions, so there's no problem at all. – πάντα ῥεῖ Feb 17 '21 at 17:40
  • @spectras what's the correct way of allowing a class to be passed as a C struct? – WilliamB Feb 17 '21 at 17:48
  • @AdrianMole> MFC are not exactly an example of good object oriented-design though… – spectras Feb 17 '21 at 17:53
  • @WilliamB the correct way is to not allow that, and have a clean API instead. – spectras Feb 17 '21 at 17:53
  • 1
    @spectras Well, that's a matter of opinion. The underlying driving force behind MFC was to provide C++ *wrappers* for the existing, C-style WinAPI, etc. In that sense, it is both good and (reasonably) robust. – Adrian Mole Feb 17 '21 at 17:55
  • @AdrianMole , yes, this is exactly what I'm trying to do, create a wrapper, basically I want it so that when a Window object is passed as a parameter to something that takes a WINDOW struct it will give it the correct value rather then having to set a getter method for the window. – WilliamB Feb 17 '21 at 18:01
  • @WilliamB _"what's the correct way of allowing a class to be passed as a C struct?"_ Don't let other's confuse you here. There's nothing special to do with c structs in c++. They'll have the same names and memory layout as they'd have in c. If you declare them in a `extern "C" {}` block, there won't be any difference but the name mangling, and linking mangled or unmangled symbols always works with the c++ linker. – πάντα ῥεῖ Feb 17 '21 at 18:03
  • @AdrianMole> when you provide object-oriented wrappers, you might want them to follow the paradigm correctly. For instnace, having to call `foo->create()` instead of creating resources in the constructor, and `foo->destroy()` is plain bad design. In retrospect, of course; back then we did not have all the experience and feedback and it seemed reasonable. Does not mean it is a good example today. – spectras Feb 17 '21 at 20:07

1 Answers1

1

One way to create C++ 'wrappers' for structures defined in a C library, so that you can easily pass pointers to API calls in such a library is to emulate the way the MFC (Microsoft Foundation Class) library wraps GDI objects, such as the RECT structure, into classes like CRect.

MFC uses straightforward inheritance of the 'base' structure, and provides operators in each class that return pointers to the base structure (which will actually be a class instance's this pointer).

In the code below, I show the definition of the RECT structure and some excerpts of the CRect class, from the relevant Windows headers and MFC headers/source.

// The "C" base structure, from "windef.h"
typedef struct tagRECT
{
    LONG    left;
    LONG    top;
    LONG    right;
    LONG    bottom;
} RECT, *LPRECT;

typedef const RECT *LPCRECT;
class CRect : public tagRECT
{
public:
    CRect(int l, int t, int r, int b) {
        // Assign given parameters to base structure members...
        left = l; top = t; right = r; bottom = b;
    }
//...
    // Utility routines...
    int Width() const {
        return right - left;
    }
//...
    // convert between CRect and LPRECT/LPCRECT (no need for &)
    operator LPRECT() {
        return this;
    }
    operator LPCRECT() const {
        return this;
    }
//...
};

With wrapper classes defined like this, you can pass a CRect object (as in the comment above, no need for &) to a call from your C Library that expects a pointer to a RECT. For example, the GetWindowRect() function takes an LPRECT parameter and is called, from C, like this:

HWND hWnd;
RECT rect;
BOOL answer = GetWindowRect(hWnd, &rect); // Pass pointer to C structure

Using the CRect class, you can just pass the object, and the LPRECT operator will take care of the rest (but you can add the & if you really want to):

HWND hWnd;
CRect rc;
BOOL answer = GetWindowRect(hWnd, rc);

There are limitations and caveats involved in this approach (e.g. horrid things may happen if the C library expects a RECT to be passed by value), but it may be an approach you find helpful.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • Good answer, however CRect is not exactly from MFC. That class is defined in `` and it’s one of the many classes shared between MFC and ATL. It’s usable in both MFC projects, ATL projects, or stand-alone apps which don’t use either of them. https://learn.microsoft.com/en-us/cpp/atl-mfc-shared/reference/classes-shared-by-mfc-and-atl?view=msvc-160 – Soonts Feb 17 '21 at 18:57
  • If I understand the NCurses documentation correctly it takes WINDOW as a pointer, how would I have it recognize that? – WilliamB Feb 17 '21 at 19:03
  • @Soonts Well, I'm very old and, back in the old days, there was just MFC. ATL came along later (IIRC). – Adrian Mole Feb 17 '21 at 19:31
  • @WilliamB You can have your class inherit from `WINDOW` and then add an `operator WINDOW* ()` member that returns `this`. In the example I've shown (as it's Microsoft code), the `LPRECT` type is used, which is defined as a `RECT*` in the first code block. And, if all the library calls take a WINDOW *pointer*, then you should be reasonably safe using this approach. – Adrian Mole Feb 17 '21 at 19:33
  • @AdrianMole Not sure I’m much younger. That transition happened in Visual Studio 2002. Here’s what offline MSDN library says about them: “Beginning with Visual C++ .NET 2002, several existing MFC utility classes were rewritten or revised to reduce their dependencies on other MFC classes. These utility classes can now be used in any native C++ project.” – Soonts Feb 17 '21 at 20:28
  • @AdrianMole But Window does not have the values requried by WINDOW *, when assigning a window in NCurses you do something like this, WINDOW* win = newwin(5,5,5,5); this just returning this gives an error. – WilliamB Feb 18 '21 at 01:18
  • @WilliamB Then perhaps, as suggested in the comments to your question, your should have a `WINDOW` as a data member of your class, rather than use inheritance. That way, you can readily assign it in the class constructor and **still** use the pointer to `this` as a pointer to a `WINDOW` ... ***so long as the WINDOW is the first data member of your class***. – Adrian Mole Feb 18 '21 at 09:52