lo tolmencre
lo tolmencre

Reputation: 3934

extract variable from macro block

I am trying to write a function that behaves differently depending on the OS.

I got this code somewhere in my function:

#ifdef OS_WINDOWS
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    int cols;
    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
    cols = csbi.srWindow.Right - csbi.srWindow.Left + 1;
    #else
        int cols;
        #ifdef TIOCGSIZE
        struct ttysize ts;
        ioctl(STDIN_FILENO, TIOCGSIZE, &ts);
        cols = ts.ts_cols;
        #elif defined(TIOCGWINSZ)
        struct winsize ts;
        ioctl(STDIN_FILENO, TIOCGWINSZ, &ts);
        cols = ts.ws_col;
        #endif // TIOCGSIZE
    #endif // OS_WINDOWS

So, I want to get the number of columns, but that needs to be done in two different ways for Windows and Linux... I then want to continue working with the cols variable. But I get variable 'cols' is uninitialized when used here

How can I "extract" the cols variable from the macro block?

Upvotes: 0

Views: 82

Answers (3)

lo tolmencre
lo tolmencre

Reputation: 3934

Okay, thanks everyone. I changed a few more things and now it's working for me:

        unsigned get_terminal_cols()
        {
            unsigned c = -1;
            #ifdef _WIN32
                CONSOLE_SCREEN_BUFFER_INFO csbi;
                GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
                c = csbi.srWindow.Right - csbi.srWindow.Left - 3;
            #else
                struct winsize w;
                ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
                c = w.ws_col -3;
            #endif // _WIN32
            return c;
        }

Upvotes: 0

JVene
JVene

Reputation: 1759

When dealing with OS specific behavior, you'll notice that code is complicated and difficult to understand or maintain when such conditional code is included "inline" with the other, non conditional code.

One way to make this cleaner and clearer is to put conditional code outside the function, and choose among alternatives in a clearly separated function.

For example, let's say the concept is to get columns. Know that is different for each OS case, start by declaring a function to get columns:

int GetColumns();

Now, DEFINE the function depending on the operating system:

#ifdef OS_WINDOWS

int GetColumns()
{
 int cols(0);
 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
 cols = csbi.srWindow.Right - csbi.srWindow.Left + 1;

 return cols;
}

#else
#ifdef TIOCGSIZE
int GetColumns()
{
  int cols;
  struct ttysize ts;
  ioctl(STDIN_FILENO, TIOCGSIZE, &ts);
  cols = ts.ts_cols;
  return cols;
}
#elif defined( TIOCGWINSZ )
int GetColumns()
{
  int cols;
  struct winsize ts;
  ioctl(STDIN_FILENO, TIOCGWINSZ, &ts);
  cols = ts.ws_col;
  return cols;
}
#endif // elif

#endif // else to OS_WINDOWS

I have not reviewed all of your code within these blocks, but the point here is to separate these concepts to make the code clearer, cleaner and easier to use.

In your main function you now only need:

int cols = GetColumns();

What this does is acknowledge that you need a way to get columns, and that's it. It clearly separates the complications of getting the columns from the code that calls for it.

Now, the side effects of the various conditional code no longer corrupts the function you're writing. If there are complications, they are now isolated with the body of the various versions of GetColumns. The function "wraps" the issues of local variables, differences in function calls, inside the function.

This also makes it usable from elsewhere in the program without repeating conditional code.

Upvotes: 1

crashmstr
crashmstr

Reputation: 28573

What the compiler sees on Windows

CONSOLE_SCREEN_BUFFER_INFO csbi;
int cols; //This is the only place where `cols` is delcared!
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
cols = csbi.srWindow.Right - csbi.srWindow.Left + 1;

What the compiler sees not on Windows

    struct ttysize ts;
    ioctl(STDIN_FILENO, TIOCGSIZE, &ts);
    cols = ts.ts_cols; //cols is not declared

or

    struct winsize ts;
    ioctl(STDIN_FILENO, TIOCGWINSZ, &ts);
    cols = ts.ws_col; //cols is not declared

Note that there is no declaration of cols in the non-Windows code.

A simple solution would be to move the decalration of cols before the macro block.

int cols = 0; //or -1 or some other error value
#ifdef OS_WINDOWS
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
//...

As a note on preprocessor macros: The preprocessor runs before the code is compiled, dealing with #include, #define (replaces text), #ifdef (conditionally includes blocks of code), and so on. Editors and IDEs may show non-use blocks grayed out, but you can also get the preprocessor output seperately to look at (compiler flags depend on your compiler), and this may help you find why this is having errors.

Upvotes: 2

Related Questions