Related
I am writing the deepcopy function using python2.7 for my class. I encountered a weird problem
My code is the following
import copy
from ctypes import *
class Graph (Structure):
_fields_ = [("numVertices", c_ulong),
("numEdges", c_ulong)]
def __init__(self):
self.numVertices = c_ulong(0)
self.numEdges = c_ulong(0)
def __deepcopy__(self,memo={}):
newInstance = Graph()
newInstance.numVertices = c_ulong(self.numVertices.value)
newInstance.numEdges = c_ulong(self.numEdges.value)
return newInstance
graph = Graph()
anotherGraph = copy.deepcopy(graph)
I get the following error:
<ipython-input-46-a0cdaa4ef3f7> in __deepcopy__(self, memo)
9 def __deepcopy__(self,memo={}):
10 newInstance = Graph()
---> 11 newInstance.numVertices = c_ulong(self.numVertices.value)
12 newInstance.numEdges = c_ulong(self.numEdges.value)
13 return newInstance
AttributeError: 'long' object has no attribute 'value'
If you try:
type(graph.numVertices)
The result is long
I declared the numVertices as c_ulong(). Why does it become long?
The type of the fields in the structure is still maintained, but ctypes has some "helpful" conversions when reading the values:
from ctypes import *
class Test(Structure):
_fields_ = [('a',c_ulong),
('b',c_char_p)]
t = Test(1,b'hello')
print(type(t.a),type(t.b))
print(t._fields_)
Output:
<class 'int'> <class 'bytes'>
[('a', <class 'ctypes.c_ulong'>), ('b', <class 'ctypes.c_char_p'>)]
So you can write your code as the following and it will work correctly:
import copy
from ctypes import *
class Graph (Structure):
_fields_ = [("numVertices", c_ulong),
("numEdges", c_ulong)]
def __init__(self):
self.numVertices = 0
self.numEdges = 0
def __deepcopy__(self,memo={}):
newInstance = Graph()
newInstance.numVertices = self.numVertices
newInstance.numEdges = self.numEdges
return newInstance
graph = Graph()
anotherGraph = copy.deepcopy(graph)
You can suppress the conversions by deriving from the classes, but it is usually unnecessary. One use case is when using ctypes to call a function that returns an allocated string. You need to suppress the c_char_p to Python byte string conversion so you can later free the c_char_p.
from ctypes import *
class ulong(c_ulong): pass
class char_p(c_char_p): pass
class Test(Structure):
_fields_ = [('a',ulong),
('b',char_p)]
t = Test(1,b'hello')
print(type(t.a),type(t.b))
print(t.a,t.b)
Output:
<class '__main__.ulong'> <class '__main__.char_p'>
<ulong object at 0x0000000006263748> char_p(b'hello')
The win32 function NetShareDel takes three arguments, LPCWSTR LPCWSTR and DWORD.
So I use the following list for argtypes:
import ctypes as C
C.windll.Netapi32.NetShareDel.argtypes = [LPCWSTR, LPCWSTR, c_int]
C.windll.Netapi32.NetShareDel.restype = c_int
C.windll.Netapi32.NetShareDel(server, shareName, 0)
That works fine, but I can't figure out what to use for NetShareAdd, especialle the byte array for NET_SHARE_INFO struct and the last byref(c_int) argument.
Here's the code:
def Share(server, shareName, dir):
info = SHARE_INFO_2()
STYPE_DISKTREE = 0
info.shi2_netname = shareName
info.shi2_path = dir
info.shi2_type = STYPE_DISKTREE
info.shi2_remark = "Shared: " + time.strftime("%Y%m%d-%H:%M")
info.shi2_max_uses = -1
info.shi2_passwd = ""
info.shi2_current_uses = 0
info.shi2_permissions = 0xFFFFFFFF
i = c_int()
bytearray = buffer(info)[:]
windll.Netapi32.NetShareAdd.argtypes = [LPCWSTR, c_int, ????, ????]
windll.Netapi32.NetShareAdd(server, 2, bytearray, C.byref(i))
What would be the correct argtypes list for NetShareAdd?
Got it working finally
First the line
bytearray = buffer(info)[:]
was changed into byte pointer type
byteptr = C.POINTER(C.wintypes.BYTE)(info)
and then the argtypes and call will become POINTER(BYTE) too of course:
C.windll.Netapi32.NetShareAdd.argtypes = [LPCWSTR, c_int, C.POINTER(C.wintypes.BYTE), C.POINTER(c_int)]
C.windll.Netapi32.NetShareAdd.restype = c_int
windll.Netapi32.NetShareAdd(server, 2, byteptr, C.byref(i))
I am trying to capture the screen using only the ctypes modules. Unfortunately I cannot retrieve raw pixel from CGDataProviderCopyData. I need to get an access to raw data:
#!/usr/bin/env python
# coding: utf-8
from sys import maxsize
from ctypes import POINTER, Structure, c_double, byref, c_void_p, c_int32, c_uint32, c_float, cdll
from ctypes.util import find_library
CGFloat = c_double if maxsize > 2 ** 32 else c_float
class CGPoint(Structure):
_fields_ = [('x', CGFloat), ('y', CGFloat)]
class CGSize(Structure):
_fields_ = [('width', CGFloat), ('height', CGFloat)]
class CGRect(Structure):
_fields_ = [('origin', CGPoint), ('size', CGSize)]
# Library
cgs = cdll.LoadLibrary(find_library('CoreGraphics'))
# Argtypes
cgs.CGGetActiveDisplayList.argtypes = [c_uint32, POINTER(c_uint32), POINTER(c_uint32)]
cgs.CGDisplayBounds.argtypes = [c_uint32]
cgs.CGRectStandardize.argtypes = [CGRect]
cgs.CGDisplayRotation.argtypes = [c_uint32]
cgs.CGWindowListCreateImage.argtypes = [CGRect, c_uint32, c_uint32, c_uint32]
cgs.CGImageGetWidth.argtypes = [c_void_p]
cgs.CGImageGetHeight.argtypes = [c_void_p]
cgs.CGImageGetDataProvider.argtypes = [c_void_p]
cgs.CGDataProviderCopyData.argtypes = [c_void_p]
cgs.CGDataProviderRelease.argtypes = [c_void_p]
# Restypes
cgs.CGGetActiveDisplayList.restype = c_int32
cgs.CGDisplayBounds.restype = CGRect
cgs.CGRectStandardize.restype = CGRect
cgs.CGDisplayRotation.restype = c_float
cgs.CGWindowListCreateImage.restype = c_void_p
cgs.CGImageGetWidth.restype = c_uint32
cgs.CGImageGetHeight.restype = c_uint32
cgs.CGImageGetDataProvider.restype = c_void_p
cgs.CGDataProviderCopyData.restype = c_void_p
cgs.CGDataProviderRelease.restype = c_void_p
# Monitors
max_displays = 32
display_count = c_uint32(0)
active_displays = (c_uint32 * max_displays)()
cgs.CGGetActiveDisplayList(max_displays, active_displays, byref(display_count))
for idx in range(display_count.value):
display = active_displays[idx]
rect = cgs.CGDisplayBounds(display)
rect = cgs.CGRectStandardize(rect)
image_ref = cgs.CGWindowListCreateImage(rect, 1, 0, 0)
width = int(cgs.CGImageGetWidth(image_ref))
height = int(cgs.CGImageGetHeight(image_ref))
prov = cgs.CGImageGetDataProvider(image_ref)
data = cgs.CGDataProviderCopyData(prov)
# How to get raw pixels from data`?
MacOS X version 10.11.3.
Python versions 2.7.10 and 2.6.9.
I have no experience with the CoreGraphics API, but looking at the documentation it looks like CGDataProviderCopyData returns a CFDataRef object. The documentat for CFDataRef has a section on "Examining a CFData Object" which describes a CFDataGetLength function and a CFDataGetBytePtr function which return the length of the data and a UInt8*.
https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CGDataProvider/#//apple_ref/c/func/CGDataProviderCopyData
https://developer.apple.com/library/ios/documentation/CoreFoundation/Reference/CFDataRef/index.html
Thanks to Snorfalorpagus, I finally succeed:
#...
data = cgs.CGDataProviderCopyData(prov)
data_ref = cgs.CFDataGetBytePtr(data)
buf_len = cgs.CFDataGetLength()
image_data = cast(data_ref, POINTER(c_ubyte * buf_len))
cgs.CGDataProviderRelease(prov)
# Raw pixels are in image_data.contents
I want to get the raw bytes of a BITMAPINFO in python. This is my complete code:
import ctypes
from ctypes import wintypes
windll = ctypes.windll
user32 = windll.user32
gdi32 = windll.gdi32
class RECT(ctypes.Structure):
_fields_ = [
('left', ctypes.c_long),
('top', ctypes.c_long),
('right', ctypes.c_long),
('bottom', ctypes.c_long)
]
class BITMAPINFOHEADER(ctypes.Structure):
_fields_ = [
("biSize", wintypes.DWORD),
("biWidth", ctypes.c_long),
("biHeight", ctypes.c_long),
("biPlanes", wintypes.WORD),
("biBitCount", wintypes.WORD),
("biCompression", wintypes.DWORD),
("biSizeImage", wintypes.DWORD),
("biXPelsPerMeter", ctypes.c_long),
("biYPelsPerMeter", ctypes.c_long),
("biClrUsed", wintypes.DWORD),
("biClrImportant", wintypes.DWORD)
]
class RGBQUAD(ctypes.Structure):
_fields_ = [
("rgbBlue", wintypes.BYTE),
("rgbGreen", wintypes.BYTE),
("rgbRed", wintypes.BYTE),
("rgbReserved", ctypes.c_void_p)
]
class BITMAP(ctypes.Structure):
_fields_ = [
("bmType", ctypes.c_long),
("bmWidth", ctypes.c_long),
("bmHeight", ctypes.c_long),
("bmWidthBytes", ctypes.c_long),
("bmPlanes", wintypes.DWORD),
("bmBitsPixel", wintypes.DWORD),
("bmBits", ctypes.c_void_p)
]
whandle = 327756 # Just a handle of an open application
rect = RECT()
user32.GetClientRect(whandle, ctypes.byref(rect))
# bbox = (rect.left, rect.top, rect.right, rect.bottom)
hdcScreen = user32.GetDC(None)
hdc = gdi32.CreateCompatibleDC(hdcScreen)
hbmp = gdi32.CreateCompatibleBitmap(
hdcScreen,
rect.right - rect.left,
rect.bottom - rect.top
)
gdi32.SelectObject(hdc, hbmp)
PW_CLIENTONLY = 1
if not user32.PrintWindow(whandle, hdc, PW_CLIENTONLY):
raise Exception("PrintWindow failed")
bmap = BITMAP()
if not gdi32.GetObjectW(hbmp, ctypes.sizeof(BITMAP), ctypes.byref(bmap)):
raise Exception("GetObject failed")
class BITMAPINFO(ctypes.Structure):
_fields_ = [
("BITMAPINFOHEADER", BITMAPINFOHEADER),
("RGBQUAD", RGBQUAD * 1000)
]
bminfo = BITMAPINFO()
bminfo.BITMAPINFOHEADER.biSize = ctypes.sizeof(BITMAPINFOHEADER)
bminfo.BITMAPINFOHEADER.biWidth = bmap.bmWidth
bminfo.BITMAPINFOHEADER.biHeight = bmap.bmHeight
bminfo.BITMAPINFOHEADER.biPlanes = bmap.bmPlanes
bminfo.BITMAPINFOHEADER.biBitCount = bmap.bmBitsPixel
bminfo.BITMAPINFOHEADER.biCompression = 0
bminfo.BITMAPINFOHEADER.biClrImportant = 0
out = ctypes.create_string_buffer(1000)
if not gdi32.GetDIBits(hdc, hbmp, 0, bmap.bmHeight, None, bminfo, 0):
raise Exception("GetDIBits failed")
I need a way to know how long the array of RGBQUADS has to be in the BITMAPINFO struct and also the lenght of the out buffer. The 1000 is in there as a placeholder.
gdi32.GetDIBits fails with an access violation. I guess it's because i have to have the array and buffer with the correct lenght.
I post the whole source, because i don't know what's failing. Any help is appreciated.
UPDATE
corrected DWORDs in BITMAP being WORDs and a void pointer in RGBQUAD to be BYTE
getting the size of image data:
def round_up32(n):
multiple = 32
while multiple < n:
multiple += 32
return multiple
data_len = round_up32(bmap.bmWidth * bmap.bmBitsPixel) * bmap.bmHeight
Still getting access violation.
I also saw that there is no RGBQUAD array for 32-bit-per-pixel bitmaps. Is that true?
What i had wrong:
Structs (thanks David) :
BITMAP has no DWORDs; it has WORDs.
RGBQUADs rgbReserved is a BYTE not a void pointer.
BITMAPINFO doesn't need RGBQUAD array for bitmaps with 32 bits per pixel.
Pointers (thanks Roger, i come from python :P ):
GetDIBits parameter lpvBits needs a pointer to the buffer
GetDIBits parameter lpbi needs a pointer to the struct
What i didn't know:
How big the buffer had to be. Quoting Jonathan:
Each row of the bitmap is bmWidth * bmBitsPixel bits in size, rounded up to the next multiple of 32 bits. Multiply the row length by bmHeight to calculate the total size of the image data.
I came up with this:
def round_up32(n):
multiple = 32
while multiple < n:
multiple += 32
return multiple
scanline_len = round_up32(bmap.bmWidth * bmap.bmBitsPixel)
data_len = scanline_len * bmap.bmHeight
data_len is then used to initalize ctypes.create_string_buffer().
GetDIBits only returns pixel data, so i had to build the header.
After making all this changes nothing failed but the image was inverted. I found that GetDIBits returns the scanlines inverted for compatibilty reasons. I made a new PIL Image from the bytes and then flipped it.
The full source follows:
import struct
from PIL import Image
from PIL.ImageOps import flip
import ctypes
from ctypes import wintypes
windll = ctypes.windll
user32 = windll.user32
gdi32 = windll.gdi32
class RECT(ctypes.Structure):
_fields_ = [
('left', ctypes.c_long),
('top', ctypes.c_long),
('right', ctypes.c_long),
('bottom', ctypes.c_long)
]
class BITMAPINFOHEADER(ctypes.Structure):
_fields_ = [
("biSize", wintypes.DWORD),
("biWidth", ctypes.c_long),
("biHeight", ctypes.c_long),
("biPlanes", wintypes.WORD),
("biBitCount", wintypes.WORD),
("biCompression", wintypes.DWORD),
("biSizeImage", wintypes.DWORD),
("biXPelsPerMeter", ctypes.c_long),
("biYPelsPerMeter", ctypes.c_long),
("biClrUsed", wintypes.DWORD),
("biClrImportant", wintypes.DWORD)
]
class BITMAPINFO(ctypes.Structure):
_fields_ = [
("bmiHeader", BITMAPINFOHEADER)
]
class BITMAP(ctypes.Structure):
_fields_ = [
("bmType", ctypes.c_long),
("bmWidth", ctypes.c_long),
("bmHeight", ctypes.c_long),
("bmWidthBytes", ctypes.c_long),
("bmPlanes", wintypes.WORD),
("bmBitsPixel", wintypes.WORD),
("bmBits", ctypes.c_void_p)
]
def get_window_image(whandle):
def round_up32(n):
multiple = 32
while multiple < n:
multiple += 32
return multiple
rect = RECT()
user32.GetClientRect(whandle, ctypes.byref(rect))
bbox = (rect.left, rect.top, rect.right, rect.bottom)
hdcScreen = user32.GetDC(None)
hdc = gdi32.CreateCompatibleDC(hdcScreen)
hbmp = gdi32.CreateCompatibleBitmap(
hdcScreen,
bbox[2] - bbox[0],
bbox[3] - bbox[1]
)
gdi32.SelectObject(hdc, hbmp)
PW_CLIENTONLY = 1
if not user32.PrintWindow(whandle, hdc, PW_CLIENTONLY):
raise Exception("PrintWindow failed")
bmap = BITMAP()
if not gdi32.GetObjectW(hbmp, ctypes.sizeof(BITMAP), ctypes.byref(bmap)):
raise Exception("GetObject failed")
if bmap.bmBitsPixel != 32:
raise Exception("WTF")
scanline_len = round_up32(bmap.bmWidth * bmap.bmBitsPixel)
data_len = scanline_len * bmap.bmHeight
# http://msdn.microsoft.com/en-us/library/ms969901.aspx
bminfo = BITMAPINFO()
bminfo.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER)
bminfo.bmiHeader.biWidth = bmap.bmWidth
bminfo.bmiHeader.biHeight = bmap.bmHeight
bminfo.bmiHeader.biPlanes = 1
bminfo.bmiHeader.biBitCount = 24 # bmap.bmBitsPixel
bminfo.bmiHeader.biCompression = 0
data = ctypes.create_string_buffer(data_len)
DIB_RGB_COLORS = 0
get_bits_success = gdi32.GetDIBits(
hdc, hbmp,
0, bmap.bmHeight,
ctypes.byref(data), ctypes.byref(bminfo),
DIB_RGB_COLORS
)
if not get_bits_success:
raise Exception("GetDIBits failed")
# http://msdn.microsoft.com/en-us/library/dd183376%28v=vs.85%29.aspx
bmiheader_fmt = "LllHHLLllLL"
unpacked_header = [
bminfo.bmiHeader.biSize,
bminfo.bmiHeader.biWidth,
bminfo.bmiHeader.biHeight,
bminfo.bmiHeader.biPlanes,
bminfo.bmiHeader.biBitCount,
bminfo.bmiHeader.biCompression,
bminfo.bmiHeader.biSizeImage,
bminfo.bmiHeader.biXPelsPerMeter,
bminfo.bmiHeader.biYPelsPerMeter,
bminfo.bmiHeader.biClrUsed,
bminfo.bmiHeader.biClrImportant
]
# Indexes: biXPelsPerMeter = 7, biYPelsPerMeter = 8
# Value from https://stackoverflow.com/a/23982267/2065904
unpacked_header[7] = 3779
unpacked_header[8] = 3779
image_header = struct.pack(bmiheader_fmt, *unpacked_header)
image = image_header + data
return flip(Image.frombytes("RGB", (bmap.bmWidth, bmap.bmHeight), image))
Pass a window handle (int) to get_window_image() and it returns a PIL image.
The only issue is that the colors are... weird? I'll figure that out another time.
The following code is wrong
def round_up32(n):
multiple = 32
while multiple < n:
multiple += 32
return multiple
scanline_len = round_up32(bmap.bmWidth * bmap.bmBitsPixel)
data_len = scanline_len * bmap.bmHeight
You are trying to find the number of bytes to create an array or simply allocate a block of memory. Memory is always bytes. So bmap.bmBitsPixel should be converted to bytes. 32 bits is 4 bytes. Since you are already checking bmap.bmBitsPixel for 32 bits replace bmap.bmBitsPixel with 4 and remove round_up32 function.
scanline_len = bmap.bmWidth * 4
data_len = scanline_len * bmap.bmHeight
I am using the following code to read an svg:
from ctypes import CDLL, POINTER, Structure, byref, util
from ctypes import c_bool, c_byte, c_void_p, c_int, c_double, c_uint32, c_char_p
class _PycairoContext(Structure):
_fields_ = [("PyObject_HEAD", c_byte * object.__basicsize__),
("ctx", c_void_p),
("base", c_void_p)]
class _RsvgProps(Structure):
_fields_ = [("width", c_int), ("height", c_int),
("em", c_double), ("ex", c_double)]
class _GError(Structure):
_fields_ = [("domain", c_uint32), ("code", c_int), ("message", c_char_p)]
def _load_rsvg(rsvg_lib_path=None, gobject_lib_path=None):
if rsvg_lib_path is None:
rsvg_lib_path = util.find_library('rsvg-2')
if gobject_lib_path is None:
gobject_lib_path = util.find_library('gobject-2.0')
l = CDLL(rsvg_lib_path)
g = CDLL(gobject_lib_path)
g.g_type_init()
l.rsvg_handle_new_from_file.argtypes = [c_char_p, POINTER(POINTER(_GError))]
l.rsvg_handle_new_from_file.restype = c_void_p
l.rsvg_handle_render_cairo.argtypes = [c_void_p, c_void_p]
l.rsvg_handle_render_cairo.restype = c_bool
l.rsvg_handle_get_dimensions.argtypes = [c_void_p, POINTER(_RsvgProps)]
return l
_librsvg = _load_rsvg()
class Handle(object):
def __init__(self, path):
lib = _librsvg
err = POINTER(_GError)()
self.handle = lib.rsvg_handle_new_from_file(path, byref(err))
if self.handle is None:
gerr = err.contents
raise Exception(gerr.message)
self.props = _RsvgProps()
lib.rsvg_handle_get_dimensions(self.handle, byref(self.props))
def render_cairo(self, ctx):
"""Returns True is drawing succeeded."""
z = _PycairoContext.from_address(id(ctx))
return _librsvg.rsvg_handle_render_cairo(self.handle, z.ctx)
(from Error with Python ctypes and librsvg)
I call img = Handle(path) and this leaks memory. I am pretty sure this is due to incorrectly using ctypes & pointers, but I cannot find a way to fix this.
According to the documentation of rsvg_handle_new, free the handle with g_object_unref. Also, if a failed call allocates a GError, after you get the code and message you have to free the error with g_error_free.
from ctypes import *
from ctypes.util import find_library
_gobj = CDLL(find_library("gobject-2.0"))
_glib = CDLL(find_library("glib-2.0"))
class _GError(Structure):
_fields_ = [("domain", c_uint32),
("code", c_int),
("message", c_char_p)]
_GErrorP = POINTER(_GError)
_glib.g_error_free.restype = None
_glib.g_error_free.argtypes = [_GErrorP]
_gobj.g_object_unref.restype = None
_gobj.g_object_unref.argtypes = [c_void_p]
You can free the handle in the __del__ method of the Handle class:
class Handle(object):
_gobj = _gobj # keep a valid ref for module teardown
# ...
def __del__(self):
if self.handle:
self._gobj.g_object_unref(self.handle)
self.handle = None