Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why the irrelevant code made a difference?

I am thinking to make a progress bar with python in terminal. First, I have to get the width(columns) of terminal window. In python 2.7, there is no standard library can do this on Windows. I know maybe I have to call Windows Console API manually.

According to MSDN and Python Documentation, I wrote the following code:

import ctypes
import ctypes.wintypes

class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
    _fields_ = [
        ('dwSize', ctypes.wintypes._COORD),
        ('dwCursorPosition', ctypes.wintypes._COORD),
        ('wAttributes', ctypes.c_ushort),
        ('srWindow', ctypes.wintypes._SMALL_RECT),
        ('dwMaximumWindowSize', ctypes.wintypes._COORD)
    ]
hstd = ctypes.windll.kernel32.GetStdHandle(ctypes.c_ulong(-11)) # STD_OUTPUT_HANDLE = -11
print hstd
csbi = CONSOLE_SCREEN_BUFFER_INFO()
print ctypes.sizeof(csbi) # <---------------
ret = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(ctypes.c_ulong(hstd), csbi)
print ret
print csbi.dwSize.X

It works fine. I set about deleting some print in code. But after that, it doesn't work! GetLastError return 6 (Invalid Handle). After times of trying, I found that there must be SOMETHING at the pointed position of the code such as print 'hello', import sys or sys.stdout.flush(). At first, I guess that maybe it need time to do something. So I tried to put time.sleep(2) at that position, but it still doesn't work.

But, if I do use struct instead of ctypes.Structure, there's no such problem.

import ctypes
import struct

hstd = ctypes.windll.kernel32.GetStdHandle(-11) # STD_OUTPUT_HANDLE = -11
csbi = ctypes.create_string_buffer(22)
res = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(hstd, csbi)
width, height, curx, cury, wattr, left, top, right, bottom, maxx, maxy = struct.unpack("hhhhHhhhhhh", csbi.raw)
print bufx

Is there any one can tell me why the irrelevant code made such a difference?

like image 689
SEIAROTg Avatar asked Aug 01 '13 12:08

SEIAROTg


1 Answers

You need to pass the struct by reference:

ret = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(
    ctypes.c_ulong(hstd), 
    ctypes.byref(csbi)
)

I would also recommend that you declare the restype for GetStdHandle. That will mean that your code is ready to run under a 64 bit process. I'd write it like this:

ctypes.windll.kernel32.GetStdHandle.restype = ctypes.wintypes.HANDLE
hstd = ctypes.windll.kernel32.GetStdHandle(-11) # STD_OUTPUT_HANDLE = -11
csbi = CONSOLE_SCREEN_BUFFER_INFO()
ret = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(
    hstd, 
    ctypes.byref(csbi)
)

Actually, in my version of Python, your code reports a much more useful error. I see this:

Traceback (most recent call last):
  File "test.py", line 16, in 
    ret = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(ctypes.c_ulong(hstd), csbi)
ValueError: Procedure probably called with too many arguments (20 bytes in 
excess)

This is enough to make it clear that there is an binary mismatch at the interface between the Python code and the native code.

I suspect that if you get a more recent version of Python, you'd also benefit from this stack imbalance checking.

like image 186
David Heffernan Avatar answered Nov 13 '22 00:11

David Heffernan