[ctypes bug] Cannot copy data to clipboard via python

[ctypes bug] Cannot copy data to clipboard via python

mikey.n
Enthusiast Enthusiast
3,369 Views
3 Replies
Message 1 of 4

[ctypes bug] Cannot copy data to clipboard via python

mikey.n
Enthusiast
Enthusiast

Hi Folks,

I want to copy data from python to my windows clipboard, to be able to paste in a text editor.

I would prefer to not have to introduce yet another package the environment though.

 

I found this code using ctypes on this SO page: https://stackoverflow.com/a/27291478/8886135 

 

#https://stackoverflow.com/questions/579687/how-do-i-copy-a-string-to-the-clipboard-on-windows-using-python
import ctypes

OpenClipboard = ctypes.windll.user32.OpenClipboard
EmptyClipboard = ctypes.windll.user32.EmptyClipboard
GetClipboardData = ctypes.windll.user32.GetClipboardData
SetClipboardData = ctypes.windll.user32.SetClipboardData
CloseClipboard = ctypes.windll.user32.CloseClipboard
CF_UNICODETEXT = 13

GlobalAlloc = ctypes.windll.kernel32.GlobalAlloc
GlobalLock = ctypes.windll.kernel32.GlobalLock
GlobalUnlock = ctypes.windll.kernel32.GlobalUnlock
GlobalSize = ctypes.windll.kernel32.GlobalSize
GMEM_MOVEABLE = 0x0002
GMEM_ZEROINIT = 0x0040

unicode_type = type(u'')

def get():
    text = None
    OpenClipboard(None)
    handle = GetClipboardData(CF_UNICODETEXT)
    pcontents = GlobalLock(handle)
    size = GlobalSize(handle)
    if pcontents and size:
        raw_data = ctypes.create_string_buffer(size)
        ctypes.memmove(raw_data, pcontents, size)
        text = raw_data.raw.decode('utf-16le').rstrip(u'\0')
    GlobalUnlock(handle)
    CloseClipboard()
    return text

def put(s):
    if not isinstance(s, unicode_type):
        s = s.decode('mbcs')
    data = s.encode('utf-16le')
    OpenClipboard(None)
    EmptyClipboard()
    handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, len(data) + 2)
    pcontents = GlobalLock(handle)
    ctypes.memmove(pcontents, data, len(data))
    GlobalUnlock(handle)
    SetClipboardData(CF_UNICODETEXT, handle)
    CloseClipboard()

put(u'test data')

 

The put() function works in standalone python 2.7, however it is broken when run in Maya.

Error: WindowsError: file <maya console> line xx: exception: access violation writing 0x0000000000000000 

 

I've narrowed it down to this line: 

pcontents = GlobalLock(handle)

According to the windows documentation, GlobalLock() should return a pointer to a section of memory that you can copy the python data into. In Maya this always returns 0. This causes an access violation when trying to copy over the data buffer from python.

 

Is there a workaround for this issue? Is there perhaps a different approach that could be used? All other answers involve installing a dependency or dabbling in subprocess, which I'd rather avoid if I can.

 

For those interested in a repro, here are the steps:

  1. Be on Windows 10, Version 1903
  2. Be on Maya 2018.2
  3. Copy the code above into Maya Script Editor
  4. Run the script
  5. You should get the access violation error

 

I've also seen that someone has had a similar problem before, but no answer: https://forums.autodesk.com/t5/maya-programming/changes-in-using-ctypes-python-for-maya-2014-2015/m-...

0 Likes
Accepted solutions (1)
3,370 Views
3 Replies
Replies (3)
Message 2 of 4

zewt
Collaborator
Collaborator
Accepted solution

The ctypes bindings are broken.  They're 32-bit bindings in a 64-bit build, so all of the pointers being passed around get truncated.  You'll need to declare them yourself.  I've only tested this quickly:

 

 

import ctypes
from ctypes import wintypes
CF_UNICODETEXT = 13

user32 = ctypes.WinDLL('user32')
kernel32 = ctypes.WinDLL('kernel32')

OpenClipboard = user32.OpenClipboard
OpenClipboard.argtypes = wintypes.HWND,
OpenClipboard.restype = wintypes.BOOL
CloseClipboard = user32.CloseClipboard
CloseClipboard.restype = wintypes.BOOL
EmptyClipboard = user32.EmptyClipboard
EmptyClipboard.restype = wintypes.BOOL
GetClipboardData = user32.GetClipboardData
GetClipboardData.argtypes = wintypes.UINT,
GetClipboardData.restype = wintypes.HANDLE
SetClipboardData = user32.SetClipboardData
SetClipboardData.argtypes = (wintypes.UINT, wintypes.HANDLE)
SetClipboardData.restype = wintypes.HANDLE

GlobalLock = kernel32.GlobalLock
GlobalLock.argtypes = wintypes.HGLOBAL,
GlobalLock.restype = wintypes.LPVOID
GlobalUnlock = kernel32.GlobalUnlock
GlobalUnlock.argtypes = wintypes.HGLOBAL,
GlobalUnlock.restype = wintypes.BOOL
GlobalAlloc = kernel32.GlobalAlloc
GlobalAlloc.argtypes = (wintypes.UINT, ctypes.c_size_t)
GlobalAlloc.restype = wintypes.HGLOBAL
GlobalSize = kernel32.GlobalSize
GlobalSize.argtypes = wintypes.HGLOBAL,
GlobalSize.restype = ctypes.c_size_t

GMEM_MOVEABLE = 0x0002
GMEM_ZEROINIT = 0x0040

def get():
    OpenClipboard(None)
    
    handle = GetClipboardData(CF_UNICODETEXT)
    pcontents = GlobalLock(handle)
    size = GlobalSize(handle)
    if pcontents and size:
        raw_data = ctypes.create_string_buffer(size)
        ctypes.memmove(raw_data, pcontents, size)
        text = raw_data.raw.decode('utf-16le').rstrip(u'\0')
    else:
        text = None

    GlobalUnlock(handle)
    CloseClipboard()
    return text

def put(s):
    if not isinstance(s, unicode):
        s = s.decode('mbcs')
    data = s.encode('utf-16le')
    OpenClipboard(None)
    EmptyClipboard()
    handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, len(data) + 2)
    pcontents = GlobalLock(handle)
    ctypes.memmove(pcontents, data, len(data))
    GlobalUnlock(handle)
    SetClipboardData(CF_UNICODETEXT, handle)
    CloseClipboard()   

 

 

Message 3 of 4

mikey.n
Enthusiast
Enthusiast

Ah! So the pointer we try to GlobalLock is invalid in the first place?
I've looked up a little of ctypes, but can you explain what the difference to your code is to mine?
I'm guessing you're manually declaring the argument and return types to not be 64 bit pointers, but 32 bit ones?

I've tested your code and the put() seems to work well, we don't need to get clipboard data just yet. Thank you so much!

0 Likes
Message 4 of 4

zewt
Collaborator
Collaborator

Those are just declaring what the parameters and return type are for each function.  ctypes.windll.user32.OpenClipboard is just a built-in declaration that does the exact same thing, except they're using 32-bit data types for some (all?) of them.  This just declares it manually using the correct data types.

 

0 Likes