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_char를 ctypes.c_char_p로 형변환하면 된다.
ctypes.cast(ctypes.LP_c_char 객체, ctypes.c_char_p).value
함수 호출
Win32 API 호출
ctypes.windll뒤에 원하는 dll 모듈과 함수를 사용하면 된다. 예를 들어 kernel32의 GetsystemInfo함수를 호출한다면 아래와 같이 호출할 수 있다.
ctypes.windll.kernel32.GetsystemInfo( ... )
DLL 함수 호출
test1.dll의 void __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.dll의 BOOL __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.dll의 BOOL __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.dll의 HANDLE __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)