3

I've seen this question and i followed every step, changing the code to satisfy my requirements, that are Python3, Pillow, and ctypes. The less libraries, the better.

import ctypes
from PIL import ImageGrab, Image
from io import BytesIO

user32 = ctypes.windll.user32

img = ImageGrab.grab()
output = BytesIO()
img.convert("RGB").save(output, "BMP")
data = output.getvalue()[14:]
output.close()

user32.OpenClipboard()
user32.EmptyClipboard()
user32.SetClipboardData(user32.CF_DIB, data)
user32.CloseClipboard()

That is the stripped code from my script that, i think, is the same code in the question ported to my requirements. When executed, it should copy the current desktop to the clipboard. I get this instead:

File "C:\Users\Gcq\Documents\python\Screen\Screen.py", line 132, in shot
    user32.OpenClipboard()
ValueError: Procedure probably called with not enough arguments (4 bytes missing)

I'm sorry i'm asking such a (probably) easy question here, but i really don't know what is failing, and ctypes is not my thing.

Community
  • 1
  • 1
gcq
  • 937
  • 3
  • 13
  • 19

2 Answers2

6

The example uses pywin32, which is Python wrapper around Win32 API that hides some low level details you need to take care yourself of if you want to use ctypes.

Here is how you do it using ctypes, it adds a functionally of creating globally allocated buffer and copy the data into that buffer:

#!python

from PIL import Image
#from cStringIO import StringIO
from io import BytesIO
from ctypes import *
from ctypes.wintypes import *

HGLOBAL = HANDLE
SIZE_T = c_size_t
GHND = 0x0042
GMEM_SHARE = 0x2000

GlobalAlloc = windll.kernel32.GlobalAlloc
GlobalAlloc.restype = HGLOBAL
GlobalAlloc.argtypes = [UINT, SIZE_T]

GlobalLock = windll.kernel32.GlobalLock
GlobalLock.restype = LPVOID
GlobalLock.argtypes = [HGLOBAL]

GlobalUnlock = windll.kernel32.GlobalUnlock
GlobalUnlock.restype = BOOL
GlobalUnlock.argtypes = [HGLOBAL]

CF_DIB = 8

OpenClipboard = windll.user32.OpenClipboard
OpenClipboard.restype = BOOL 
OpenClipboard.argtypes = [HWND]

EmptyClipboard = windll.user32.EmptyClipboard
EmptyClipboard.restype = BOOL
EmptyClipboard.argtypes = None

SetClipboardData = windll.user32.SetClipboardData
SetClipboardData.restype = HANDLE
SetClipboardData.argtypes = [UINT, HANDLE]

CloseClipboard = windll.user32.CloseClipboard
CloseClipboard.restype = BOOL
CloseClipboard.argtypes = None

#################################################

image = Image.new("RGB", (200, 200), (255, 0, 0))

#output = StringIO()
output = BytesIO()
image.convert("RGB").save(output, "BMP")
data = output.getvalue()[14:]
output.close()

hData = GlobalAlloc(GHND | GMEM_SHARE, len(data))
pData = GlobalLock(hData)
memmove(pData, data, len(data))
GlobalUnlock(hData)

OpenClipboard(None)
EmptyClipboard()
SetClipboardData(CF_DIB, pData)
CloseClipboard()
4

Whew. Apparently the win32clipboard library does simplify some things when compared to ctypes. Your attempt to simply replace one with the other is far from correct.

So I booted up my Windows virtual machine, installed Pillow and rewrote your program, learning from two other answers:

import io

import ctypes
msvcrt = ctypes.cdll.msvcrt
kernel32 = ctypes.windll.kernel32
user32 = ctypes.windll.user32

from PIL import ImageGrab

img = ImageGrab.grab()
output = io.BytesIO()
img.convert('RGB').save(output, 'BMP')
data = output.getvalue()[14:]
output.close()

CF_DIB = 8
GMEM_MOVEABLE = 0x0002

global_mem = kernel32.GlobalAlloc(GMEM_MOVEABLE, len(data))
global_data = kernel32.GlobalLock(global_mem)
msvcrt.memcpy(ctypes.c_char_p(global_data), data, len(data))
kernel32.GlobalUnlock(global_mem)
user32.OpenClipboard(None)
user32.EmptyClipboard()
user32.SetClipboardData(CF_DIB, global_mem)
user32.CloseClipboard()
Community
  • 1
  • 1
Oleh Prypin
  • 33,184
  • 10
  • 89
  • 99
  • +1. More generally, just blindly calling functions out of Win32 DLLs is never a good idea. At least it won't actually crash quite as often as when you do that on POSIX… but you do need to look the function on MSDN, send the right parameters, and often set `argtypes` and/or `restype`, before you can use it. – abarnert Jan 23 '14 at 21:32
  • Yep, i feel stupid now. It works! Now i have another problem: I can't paste the image. At least Word does not accept it. – gcq Jan 23 '14 at 21:40
  • @gcq Still trying to figure it out. Apparently it's much tougher than you thought. – Oleh Prypin Jan 23 '14 at 22:12
  • `global_data` is a pointer, as is the handle `global_mem` (to an internal data structure; it's not a small index into a handle table). You need to declare `restype` for these, else your code isn't correct on Win64. ctypes defaults to casting numbers to C `int`. Also, if you don't wrap manually with `c_void_p`, you need to set `argtypes` as well. – Eryk Sun Jan 23 '14 at 23:10
  • Hmm, I'm definitely not an expert in this. @gcq, you should probably use the other answer (it's fixed for Python 3 now). – Oleh Prypin Jan 23 '14 at 23:18