Create TOC

2012년 8월 13일

Python/ctypes in Win32

ctypes를 이용해 Win32 환경에서 작업하는 예제 기록.

자료구조

ctypes vs Win32

ctypes.wintypes를 참고한다.

배열

WCHAR [1000]의 배열을 선언한다고하면

FileNameType = c_wchar * 1000
a = FileNameType()

익명 구조체/공용체 선언

SYSTEM_INFO 구조체는 아래와 같이 역명 구조체와 공용체를 가지고 있다.

typedef struct _SYSTEM_INFO {
	union {
		DWORD  dwOemId;
		struct {
			WORD wProcessorArchitecture;
			WORD wReserved;
		};
	};
	DWORD     dwPageSize;
	LPVOID    lpMinimumApplicationAddress;
	LPVOID    lpMaximumApplicationAddress;
	DWORD_PTR dwActiveProcessorMask;
	DWORD     dwNumberOfProcessors;
	DWORD     dwProcessorType;
	DWORD     dwAllocationGranularity;
	WORD      wProcessorLevel;
	WORD      wProcessorRevision;
} SYSTEM_INFO;

이 구조체를 ctypes로 표시하면 아래와 같다.

class _Noname1(ctypes.Structure):
    _fields_ = [("wProcessorArchitecture", ctypes.c_ushort),
                ("wReserved", ctypes.c_short)]


class _Noname2(ctypes.Union):
    _anonymous_ = ("s",)
    _fields_ = [('dwOemId', ctypes.c_ulong),
                ('s', _Noname1)]


class SYSTEM_INFO(ctypes.Structure):
    _anonymous_ = ("u",)
    _fields_ = [("u", _Noname2),
                ("dwPageSize", ctypes.c_ulong),
                ("lpMinimumApplicationAddress", ctypes.c_void_p),
                ("lpMaximumApplicationAddress", ctypes.c_void_p),
                ("dwActiveProcessorMask", ctypes.c_ulong),  # 64 bit에서는 c_longlong이 되어야 한다.
                ("dwNumberOfProcessors", ctypes.c_ulong),
                ("dwProcessorType", ctypes.c_ulong),
                ("dwAllocationGranularity", ctypes.c_ulong),
                ("wProcessorLevel", ctypes.c_ushort),
                ("wProcessorRevision", ctypes.c_ushort)]

pointer type 선언

위에서 선언한 SYSTEM_INFO에 대한 pointer type으로 LPSYSTEM_INFO을 선언한다고 하면

LPSYSTEM_INFO = ctypes.POINTER(SYSTEM_INFO)

ctypes.LP_c_char를 문자열로 변환

ctypes.LP_c_charctypes.c_char_p로 형변환하면 된다.

ctypes.cast(ctypes.LP_c_char 객체, ctypes.c_char_p).value

함수 호출

Win32 API 호출

ctypes.windll뒤에 원하는 dll 모듈과 함수를 사용하면 된다. 예를 들어 kernel32GetsystemInfo함수를 호출한다면 아래와 같이 호출할 수 있다.

ctypes.windll.kernel32.GetsystemInfo( ... )

DLL 함수 호출

test1.dllvoid __cdecl testfunction1() 함수를 호출한다고 하면

test1 = ctypes.CDLL('test1.dll')
if test1:
	test1.testfunction1()

함수 인자로 pointer 전달

si = SYSTEM_INFO()
ctypes.windll.kernel32.GetSystemInfo(ctypes.byref(si))

함수 인자로 string buffer 전달

buf = ctypes.create_unicode_buffer(4096)
r = ctypes.windll.kernel32.GetWindowsDirectoryW(buf, 4096)
if r > 0:
	print buf.value

함수 반환값 검사

함수 객체의 errcheck를 지정하면 함수의 반환값 검사를 모아서 할 수 있다. 예를 들어 test2.dllBOOL __cdecl testfunction2() 함수에 대해서 코드를 작성해보면 아래와 같다.

>def checkBOOL(result, function, args):
    if result == 0:
		raise ctypes.WinError()
	return args

test2 = ctypes.CDLL('test2.dll')
test2.testfunction2.errcheck = checkBOOL

test2.testfunction2()  # 함수 호출이 끝나면 바로 checkBOOL 함수가 호출되서 반환값 검사를 할 수 있다.

명시적인 함수 인자 지정

함수 객체의 argtypes를 이용해서 함수의 인자를 명시적으로 지정할 수 있다. 예를 들어 test3.dllBOOL __cdecl testfunction3(LPCWSTR, LPBOOL)에 대해서 코드를 작성해보면 아래와 같다.

test3 = ctypes.CDLL('test3.dll')
test3.testfunction3.argtypes = [ctypes.c_wchar_p, ctypes.POINTER(ctypes.c_long)]

b = ctypes.c_long()
r = test3.testfunction3(u"hello, world!", ctype.byref(b))

명시적인 함수 반환형 지정

함수 객체의 restype을 시용해서 함수의 반환형을 명시적으로 지정할 수 있다(함수 반환형이 void라면 None을 사용한다). 예를 들어 test4.dllHANDLE __cdecl testfunction4()에 대해서 코드를 작성해보면 아래와 같다.

test4 = ctypes.CDLL('test4.dll')
test4.testfunction4.restype = ctypes.c_void_p

h = test4.testfunction4()

callback 함수

callback 함수 형식에 따라 ctypes.CFUNCTYPE 또는 ctypes.WINFUNCTYPE을 사용해서 callback 함수 형을 만들면 된다.

CFUNCTYPE

python 문서에 나온 예제를 Win32에 맞게 변형했다.

CMPFUNC = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int))


def py_cmp_func(a, b):
    print 'py_cmp_func', a[0], b[0]
    return 0

cmp_func = CMPFUNC(py_cmp_func)

IntArray5 = ctypes.c_int * 5
ia = IntArray5(5, 1, 7, 33, 99)
qsort = ctypes.windll.msvcrt.qsort
qsort.restype = None
qsort(ia, len(ia), ctypes.sizeof(ctypes.c_int), cmp_func)

WINFUNCTYPE

LF_FACESIZE = 32
LF_FULLFACESIZE = 64


class LOGFONT(ctypes.Structure):
    _fields_ = [
        ('lfHeight', ctypes.c_long),
        ('lfWidth', ctypes.c_long),
        ('lfEscapement', ctypes.c_long),
        ('lfOrientation', ctypes.c_long),
        ('lfWeight', ctypes.c_long),
        ('lfItalic', ctypes.c_byte),
        ('lfUnderline', ctypes.c_byte),
        ('lfStrikeOut', ctypes.c_byte),
        ('lfCharSet', ctypes.c_byte),
        ('lfOutPrecision', ctypes.c_byte),
        ('lfClipPrecision', ctypes.c_byte),
        ('lfQuality', ctypes.c_byte),
        ('lfPitchAndFamily', ctypes.c_byte),
        ('lfFaceName', ctypes.c_wchar * LF_FACESIZE)]
PLOGFONT = ctypes.POINTER(LOGFONT)


class ENUMLOGFONT(ctypes.Structure):
    _fields_ = [
        ('elfLogFont', LOGFONT),
        ('elfFullName', ctypes.c_wchar * LF_FULLFACESIZE),
        ('elfStyle', ctypes.c_wchar * LF_FACESIZE)]
PENUMLOGFONT = ctypes.POINTER(ENUMLOGFONT)


if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p):
    LPARAM = ctypes.c_long
elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p):
    LPARAM = ctypes.c_longlong

#int CALLBACK EnumFontFamProc(ENUMLOGFONT *lpelf,__in  NEWTEXTMETRIC *lpntm, DWORD FontType, LPARAM lParam;
EnumFontFamProc = ctypes.WINFUNCTYPE(ctypes.c_int, PENUMLOGFONT, ctypes.c_void_p, ctypes.c_long, LPARAM)


def py_enum_font_fam_proc(lpelf, lpntm, FontType, lparam):
    print 'py_enum_font_fam_proc', lpelf.contents.elfFullName
    return 1

enum_font_proc = EnumFontFamProc(py_enum_font_fam_proc)

EnumFontFamilies = ctypes.windll.gdi32.EnumFontFamiliesW
hdc = ctypes.windll.user32.GetDC(0)
EnumFontFamilies(hdc, 0, enum_font_proc, 0)
ctypes.windll.user32.ReleaseDC(hdc)