Fewmitz
Fewmitz

Reputation: 497

Python - ctypes and mutable buffers

I'm trying to work with ctypes, and I can't get the call to FormatMessage() to work properly.

Here's the code I have so far; I think the only issue is passing in a mutable buffer; I'm getting an ArgumentError from ctypes about lpBuffer

import ctypes
from ctypes.wintypes import DWORD

def main():
    fm = ctypes.windll.kernel32.FormatMessageA
    fm.argtypes = [DWORD,DWORD,DWORD,DWORD,ctypes.wintypes.LPWSTR(),DWORD]

    dwFlags = DWORD(0x1000) # FORMAT_MESSAGE_ALLOCATE_BUFFER |FORMAT_MESSAGE_FROM_SYSTEM
    lpSource = DWORD(0)
    dwMessageId = DWORD(0x05)
    dwLanguageId = DWORD(0)
    #buf = ctypes.wintypes.LPWSTR()
    #lpBuffer = ctypes.byref(buf)
    lpBuffer = ctypes.create_string_buffer(512)
    nSize = DWORD(512)

    res = fm(dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize)
    print res

I'm getting an error on the lpBuffer argument saying it's a wrong type, but I've tried as many variations of passing in the buffer as I could think of. I've tried doing it similar to here: https://gist.github.com/CBWhiz/6135237 and setting FORMAT_MESSAGE_ALLOCATE_BUFFER then passing in a LPWSTR() byref, I've also tried changing the argtype, pointer and casting to a variety of LPWSTR(), c_char_p, etc, but no matter what I do it keeps complaining.

What's the proper syntax to get the function to execute properly? I know ctypes can be finnicky but I haven't found anything in the documentation to resolve the issue (I know the documentation uses prototype() but I'd like to do it this way for now)

Thanks

Upvotes: 0

Views: 2200

Answers (1)

Eryk Sun
Eryk Sun

Reputation: 34260

Here's the argtypes definition for FormatMessageW (note "W" for Unicode):

import ctypes
from ctypes import wintypes

fm = ctypes.windll.kernel32.FormatMessageW
fm.argtypes = [
    wintypes.DWORD,    # dwFlags
    wintypes.LPCVOID,  # lpSource
    wintypes.DWORD,    # dwMessageId
    wintypes.DWORD,    # dwLanguageId
    wintypes.LPWSTR,   # lpBuffer
    wintypes.DWORD,    # nSize
    wintypes.LPVOID,   # Arguments (va_list *)
]

FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x100  
FORMAT_MESSAGE_FROM_SYSTEM = 0x1000

If FormatMessage allocates the buffer, you have to instead pass a reference to lpBuffer. Just cast the reference to get around the TypeError. Also, remember to call kernel32.LocalFree to free the buffer:

def main():
    dwFlags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER
    lpSource = None
    dwMessageId = 5
    dwLanguageId = 0    
    lpBuffer = wintypes.LPWSTR()
    nSize = 0  # minimum size
    Arguments = None

    if not fm(dwFlags, lpSource, dwMessageId, dwLanguageId, 
              ctypes.cast(ctypes.byref(lpBuffer), wintypes.LPWSTR), 
              nSize, Arguments):
        raise ctypes.WinError()

    msg = lpBuffer.value.rstrip()
    ctypes.windll.kernel32.LocalFree(lpBuffer)

    return msg

Upvotes: 3

Related Questions