Win32ApiDemo 项目分析¶
项目信息¶
- 项目名称: Win32ApiDemo
- 下载链接: Win32ApiDemo.rar
- 所属类别: Win32 API (Win32Api)
- 分析时间: 2026-10-15
- 核心内容: 对 glcl
Application对象上 264 个函数的自包含回归测试套件
运行截图¶

文件结构¶
Win32ApiDemo/
├── Extractor_Icon.ico
├── Project1.py # 工程主程序
├── Project1.xml
├── Unit1.py # 主窗体(Form1)—— 264 个函数的回归测试
├── Unit1.pydfm # 二进制窗体属性
├── Unit1.sct # 事件绑定脚本
├── Unit1.sfm # 文本窗体定义
主程序文件: Project1.py¶
from glcl import *
from Unit1 import *
def main():
Application.Initialize()
Application.Title = 'Project1'
Application.MainFormOnTaskbar = True
Application.Icon.LoadFromFile(os.path.join(
os.path.dirname(os.path.abspath(__file__)), "Extractor_Icon.ico"))
MainForm = Form1(Application)
MainForm.Show()
FreeConsole()
Application.Run()
MainForm.Free()
if __name__ == '__main__':
main()
单元文件: Unit1.py¶
# Powered By Python Studio, The best Python GUI IDE to download from glsite.com.
# ------------------------------------------------------------------------------
# This form was extended by hand to add a self-contained regression test for
# the 264 functions added to / already present in the Application object:
# 162 Win32 API functions (kernel32.lib / user32.lib / gdi32.lib), 26 functions
# that already existed before (MessageBox..RGB), and 76 more from advapi32 /
# shlwapi / winmm / shell32 / extra kernel32. Click "Run All 264 Tests" and
# read
# the results in the log box. Every call is wrapped so a single failure will
# not stop the rest of the suite from running.
# ------------------------------------------------------------------------------
import os
import time
import tempfile
from glcl import *
class Form1(Form):
def __init__(self, owner):
self.Memo1 = Memo(self)
self.Button1 = Button(self)
self.LoadProps(os.path.join(os.path.dirname(os.path.abspath(__file__)), "Unit1.pydfm"))
self.Button1.OnClick = self.Button1Click
self.pass_count = 0
self.fail_count = 0
self.tested_names = []
self.skipped_names = []
# -------------------------------------------------------------- helpers --
def log(self, msg):
self.Memo1.Lines.Add(msg)
def test(self, name, func):
"""Runs one of the 162 target functions and records PASS/FAIL."""
self.tested_names.append(name)
try:
result = func()
self.log('[OK] %s -> %r' % (name, result))
self.pass_count += 1
except Exception as e:
self.log('[FAIL] %s -> %s' % (name, e))
self.fail_count += 1
def util(self, name, func, default=None):
"""Runs a supporting/pre-existing call (not part of the target list), used to
set up or tear down resources for the real tests. Failures here are
logged but do not count toward the pass/fail tally."""
try:
return func()
except Exception as e:
self.log('[SETUP WARNING] %s -> %s' % (name, e))
return default
def skip(self, name, reason):
"""Marks a function as intentionally not invoked (e.g. calling it for
real would hang or close the test app itself). Not counted as pass/fail."""
self.skipped_names.append(name)
self.log('[SKIP] %s -> %s' % (name, reason))
def run_section(self, title, section_func):
self.log('')
self.log('---- %s ----' % title)
try:
section_func()
except Exception as e:
self.log('[SECTION ERROR] %s -> %s (remaining sections will still run)' % (title, e))
# ------------------------------------------------------------- sections --
def section_kernel32_basic(self):
self.test('Beep', lambda: Application.Beep(750, 200))
self.test('Sleep', lambda: Application.Sleep(10))
self.test('GetTickCount', lambda: Application.GetTickCount())
self.test('GetCurrentProcessId', lambda: Application.GetCurrentProcessId())
self.test('GetCurrentThreadId', lambda: Application.GetCurrentThreadId())
self.test('GetCurrentProcess', lambda: Application.GetCurrentProcess())
self.test('GetCurrentThread', lambda: Application.GetCurrentThread())
self.test('SetLastError', lambda: Application.SetLastError(0))
self.test('MulDiv', lambda: Application.MulDiv(10, 3, 2))
self.test('GetComputerName', lambda: Application.GetComputerName())
self.test('GetSystemDirectory', lambda: Application.GetSystemDirectory())
self.test('GetWindowsDirectory', lambda: Application.GetWindowsDirectory())
self.test('GetTempPath', lambda: Application.GetTempPath())
self.test('GetCurrentDirectory', lambda: Application.GetCurrentDirectory())
self.test('GetLogicalDrives', lambda: Application.GetLogicalDrives())
self.test('GetLogicalDriveStrings', lambda: Application.GetLogicalDriveStrings())
self.test('GetDriveType', lambda: Application.GetDriveType(Application.GetTempPath()[:3]))
self.test('OutputDebugString', lambda: Application.OutputDebugString('glcl test'))
self.test('GetModuleFileName', lambda: Application.GetModuleFileName(0))
self.test('GetVersion', lambda: Application.GetVersion())
self.test('IsDebuggerPresent', lambda: Application.IsDebuggerPresent())
self.test('GetFullPathName', lambda: Application.GetFullPathName('.'))
self.test('GetShortPathName', lambda: Application.GetShortPathName(Application.GetWindowsDirectory()))
self.test('GetLongPathName', lambda: Application.GetLongPathName(Application.GetWindowsDirectory()))
self.test('GetSystemMetrics', lambda: Application.GetSystemMetrics(0)) # SM_CXSCREEN
self.test('MessageBeep', lambda: Application.MessageBeep(0)) # MB_OK
self.test('FindWindow', lambda: Application.FindWindow('Shell_TrayWnd', ''))
self.test('FindWindowEx', lambda: Application.FindWindowEx(0, 0, 'Shell_TrayWnd', ''))
def section_filesystem(self):
tmpdir = tempfile.mkdtemp(prefix='glcltest_')
testfile = os.path.join(tmpdir, 'a.txt')
testfile2 = os.path.join(tmpdir, 'b.txt')
subdir = os.path.join(tmpdir, 'sub')
with open(testfile, 'w') as f:
f.write('glcl test file')
self.test('SetEnvironmentVariable', lambda: Application.SetEnvironmentVariable('GLCL_TEST_VAR', 'hello'))
self.test('GetEnvironmentVariable', lambda: Application.GetEnvironmentVariable('GLCL_TEST_VAR'))
self.test('CreateDirectory', lambda: Application.CreateDirectory(subdir))
self.test('GetFileAttributes', lambda: Application.GetFileAttributes(testfile))
self.test('SetFileAttributes', lambda: Application.SetFileAttributes(testfile, 0x80)) # FILE_ATTRIBUTE_NORMAL
self.test('CopyFile', lambda: Application.CopyFile(testfile, testfile2, False))
self.test('MoveFile', lambda: Application.MoveFile(testfile2, testfile2 + '.moved'))
self.test('DeleteFile', lambda: Application.DeleteFile(testfile2 + '.moved'))
self.test('RemoveDirectory', lambda: Application.RemoveDirectory(subdir))
original_dir = self.util('GetCurrentDirectory(save)', lambda: Application.GetCurrentDirectory())
self.test('SetCurrentDirectory', lambda: Application.SetCurrentDirectory(tmpdir))
if original_dir:
self.util('SetCurrentDirectory(restore)', lambda: Application.SetCurrentDirectory(original_dir))
# cleanup leftovers made purely by python (not part of the 162 test tally)
self.util('cleanup testfile', lambda: os.remove(testfile) if os.path.exists(testfile) else None)
self.util('cleanup tmpdir', lambda: os.rmdir(tmpdir) if os.path.exists(tmpdir) else None)
def section_module(self):
h = self.util('LoadLibrary(setup)', lambda: Application.LoadLibrary('user32.dll'))
self.test('LoadLibrary', lambda: h if h else (_ for _ in ()).throw(Exception('load failed')))
if h:
self.test('FreeLibrary', lambda: Application.FreeLibrary(h))
def section_process_and_sync(self):
pid = self.util('GetCurrentProcessId(helper)', lambda: Application.GetCurrentProcessId())
h = self.test_and_return('OpenProcess', lambda: Application.OpenProcess(0x0400, False, pid)) # PROCESS_QUERY_INFORMATION
if h:
self.util('CloseHandle(OpenProcess result)', lambda: Application.CloseHandle(h))
# Deliberately invalid handle so nothing is actually terminated.
self.test('TerminateProcess', lambda: Application.TerminateProcess(0, 0))
hEvent = self.test_and_return('CreateEvent', lambda: Application.CreateEvent(True, False, 'GlclTestEvent'))
self.test('SetEvent', lambda: Application.SetEvent(hEvent))
self.test('ResetEvent', lambda: Application.ResetEvent(hEvent))
self.test('PulseEvent', lambda: Application.PulseEvent(hEvent))
self.test('OpenEvent', lambda: Application.OpenEvent(0x1F0003, False, 'GlclTestEvent')) # EVENT_ALL_ACCESS
self.test('WaitForSingleObject', lambda: Application.WaitForSingleObject(hEvent, 0))
if hEvent:
self.util('CloseHandle(event)', lambda: Application.CloseHandle(hEvent))
hSem = self.test_and_return('CreateSemaphore', lambda: Application.CreateSemaphore(1, 5, 'GlclTestSemaphore'))
self.test('OpenSemaphore', lambda: Application.OpenSemaphore(0x1F0003, False, 'GlclTestSemaphore'))
self.test('ReleaseSemaphore', lambda: Application.ReleaseSemaphore(hSem, 1))
if hSem:
self.util('CloseHandle(semaphore)', lambda: Application.CloseHandle(hSem))
hMutex = self.util('CreateMutex(setup, pre-existing fn)', lambda: Application.CreateMutex('GlclTestMutex'))
self.test('ReleaseMutex', lambda: Application.ReleaseMutex(hMutex))
if hMutex:
self.util('CloseHandle(mutex)', lambda: Application.CloseHandle(hMutex))
def test_and_return(self, name, func):
"""Like test(), but also returns the value so later steps can reuse
the handle it produced."""
self.tested_names.append(name)
try:
result = func()
self.log('[OK] %s -> %r' % (name, result))
self.pass_count += 1
return result
except Exception as e:
self.log('[FAIL] %s -> %s' % (name, e))
self.fail_count += 1
return None
def section_windows(self):
tempForm = Form(self)
tempForm.Caption = 'glcl temp test window'
tempForm.Width = 320
tempForm.Height = 220
tempForm.Show()
th = tempForm.Handle
self.test('IsWindow', lambda: Application.IsWindow(th))
self.test('IsWindowVisible', lambda: Application.IsWindowVisible(th))
self.test('IsWindowEnabled', lambda: Application.IsWindowEnabled(th))
self.test('IsIconic', lambda: Application.IsIconic(th))
self.test('IsZoomed', lambda: Application.IsZoomed(th))
self.test('IsChild', lambda: Application.IsChild(self.Handle, th))
self.test('EnableWindow', lambda: (Application.EnableWindow(th, False), Application.EnableWindow(th, True))[0])
self.test('MoveWindow', lambda: Application.MoveWindow(th, 60, 60, 340, 240, True))
self.test('GetParent', lambda: Application.GetParent(th))
self.test('GetWindowRect', lambda: Application.GetWindowRect(th))
self.test('GetClientRect', lambda: Application.GetClientRect(th))
self.test('ClientToScreen', lambda: Application.ClientToScreen(th, Point(10, 10)))
self.test('ScreenToClient', lambda: Application.ScreenToClient(th, Point(100, 100)))
self.test('GetWindowLong', lambda: Application.GetWindowLong(th, -21)) # GWL_USERDATA
self.test('SetWindowLong', lambda: Application.SetWindowLong(th, -21, 12345))
self.test('GetWindowText', lambda: Application.GetWindowText(th))
self.test('SetWindowText', lambda: Application.SetWindowText(th, 'Renamed by test'))
self.test('GetWindowTextLength', lambda: Application.GetWindowTextLength(th))
self.test('GetClassName', lambda: Application.GetClassName(th))
self.test('SetForegroundWindow', lambda: Application.SetForegroundWindow(th))
self.test('GetForegroundWindow', lambda: Application.GetForegroundWindow())
self.test('GetActiveWindow', lambda: Application.GetActiveWindow())
self.test('SetActiveWindow', lambda: Application.SetActiveWindow(th))
self.test('ShowWindowAsync', lambda: Application.ShowWindowAsync(th, 5)) # SW_SHOW
self.test('AnimateWindow', lambda: Application.AnimateWindow(th, 200, 0x00080000)) # AW_BLEND
self.test('FlashWindow', lambda: Application.FlashWindow(th, True))
self.test('BringWindowToTop', lambda: Application.BringWindowToTop(th))
self.test('UpdateWindow', lambda: Application.UpdateWindow(th))
self.test('GetDesktopWindow', lambda: Application.GetDesktopWindow())
self.test('SetCapture', lambda: Application.SetCapture(th))
self.test('GetCapture', lambda: Application.GetCapture())
self.util('ReleaseCapture(cleanup, pre-existing fn)', lambda: Application.ReleaseCapture())
hdc = self.test_and_return('GetWindowDC', lambda: Application.GetWindowDC(th))
if hdc:
self.util('ReleaseDC(cleanup, pre-existing fn)', lambda: Application.ReleaseDC(th, hdc))
self.test('GetDlgItem', lambda: Application.GetDlgItem(th, 0))
self.test('GetDlgCtrlID', lambda: Application.GetDlgCtrlID(th))
self.test('SetDlgItemText', lambda: Application.SetDlgItemText(th, 0, 'x'))
self.test('GetDlgItemText', lambda: Application.GetDlgItemText(th, 0))
self.test('SetDlgItemInt', lambda: Application.SetDlgItemInt(th, 0, 42, True))
self.test('CheckDlgButton', lambda: Application.CheckDlgButton(th, 0, 1))
self.test('IsDlgButtonChecked', lambda: Application.IsDlgButtonChecked(th, 0))
self.test('DestroyWindow', lambda: Application.DestroyWindow(th))
self.util('tempForm.Free(cleanup)', lambda: tempForm.Free())
def section_menu(self):
hPopup = self.test_and_return('CreatePopupMenu', lambda: Application.CreatePopupMenu())
self.test('GetMenuItemCount', lambda: Application.GetMenuItemCount(hPopup))
self.test('GetMenuItemID', lambda: Application.GetMenuItemID(hPopup, 0))
self.test('EnableMenuItem', lambda: Application.EnableMenuItem(hPopup, 0, 0x400 | 0x1)) # MF_BYPOSITION|MF_GRAYED
self.test('DeleteMenu', lambda: Application.DeleteMenu(hPopup, 0, 0x400))
self.test('RemoveMenu', lambda: Application.RemoveMenu(hPopup, 0, 0x400))
self.test('DestroyMenu', lambda: Application.DestroyMenu(hPopup))
hMenu = self.test_and_return('CreateMenu', lambda: Application.CreateMenu())
if hMenu:
self.util('DestroyMenu(cleanup for CreateMenu)', lambda: Application.DestroyMenu(hMenu))
self.test('GetMenu', lambda: Application.GetMenu(self.Handle))
self.test('SetMenu', lambda: Application.SetMenu(self.Handle, 0))
self.test('GetSystemMenu', lambda: Application.GetSystemMenu(self.Handle, False))
def section_input_and_clipboard(self):
pt = self.util('GetCursorPos(helper read)', lambda: Application.GetCursorPos())
self.test('GetCursorPos', lambda: Application.GetCursorPos())
if pt is not None:
self.test('SetCursorPos', lambda: Application.SetCursorPos(pt.X, pt.Y))
self.test('GetAsyncKeyState', lambda: Application.GetAsyncKeyState(0x01)) # VK_LBUTTON
self.test('GetKeyState', lambda: Application.GetKeyState(0x14)) # VK_CAPITAL
self.test('keybd_event', lambda: (Application.keybd_event(0x10, 0, 0, 0), Application.keybd_event(0x10, 0, 2, 0))[0]) # tap Shift
self.test('mouse_event', lambda: Application.mouse_event(0x0001, 0, 0, 0, 0)) # zero-delta MOUSEEVENTF_MOVE
cur = self.util('GetCursor(helper read)', lambda: Application.GetCursor())
self.test('GetCursor', lambda: Application.GetCursor())
if cur is not None:
self.test('SetCursor', lambda: Application.SetCursor(cur))
cursor_file = os.path.join(os.environ.get('SystemRoot', 'C:\\Windows'), 'Cursors', 'aero_arrow.cur')
self.test('LoadCursorFromFile', lambda: Application.LoadCursorFromFile(cursor_file))
self.test('GetSysColor', lambda: Application.GetSysColor(15)) # COLOR_BTNFACE
dct = self.util('GetDoubleClickTime(helper read)', lambda: Application.GetDoubleClickTime())
self.test('GetDoubleClickTime', lambda: Application.GetDoubleClickTime())
if dct:
self.test('SetDoubleClickTime', lambda: Application.SetDoubleClickTime(dct))
self.test('OpenClipboard', lambda: Application.OpenClipboard(self.Handle))
fmt = self.test_and_return('RegisterClipboardFormat', lambda: Application.RegisterClipboardFormat('GlclTestFormat'))
self.test('IsClipboardFormatAvailable', lambda: Application.IsClipboardFormatAvailable(fmt or 0))
self.test('CountClipboardFormats', lambda: Application.CountClipboardFormats())
self.test('RegisterWindowMessage', lambda: Application.RegisterWindowMessage('GlclTestMessage'))
self.log('[NOTE] EmptyClipboard below will clear your current Windows clipboard content.')
self.test('EmptyClipboard', lambda: Application.EmptyClipboard())
self.test('CloseClipboard', lambda: Application.CloseClipboard())
def section_gdi(self):
screenDC = self.util('GetDC(setup, pre-existing fn)', lambda: Application.GetDC(0))
if not screenDC:
self.log('[SECTION ERROR] could not obtain a screen DC, skipping GDI tests')
return
memDC = self.test_and_return('CreateCompatibleDC', lambda: Application.CreateCompatibleDC(screenDC))
memBmp = self.test_and_return('CreateCompatibleBitmap', lambda: Application.CreateCompatibleBitmap(screenDC, 200, 200))
oldObj = self.test_and_return('SelectObject', lambda: Application.SelectObject(memDC, memBmp))
brush = self.test_and_return('CreateSolidBrush', lambda: Application.CreateSolidBrush(clRed))
hatchBrush = self.test_and_return('CreateHatchBrush', lambda: Application.CreateHatchBrush(2, clBlue)) # HS_CROSS
pen = self.test_and_return('CreatePen', lambda: Application.CreatePen(0, 2, clGreen)) # PS_SOLID
rgn1 = self.test_and_return('CreateRectRgn', lambda: Application.CreateRectRgn(0, 0, 50, 50))
rgn2 = self.test_and_return('CreateEllipticRgn', lambda: Application.CreateEllipticRgn(20, 20, 80, 80))
rgn3 = self.test_and_return('CreateRoundRectRgn', lambda: Application.CreateRoundRectRgn(0, 0, 100, 100, 10, 10))
rgnDest = self.util('CreateRectRgn(helper for CombineRgn)', lambda: Application.CreateRectRgn(0, 0, 0, 0))
self.test('CombineRgn', lambda: Application.CombineRgn(rgnDest, rgn1, rgn2, 2)) # RGN_OR
self.test('EqualRgn', lambda: Application.EqualRgn(rgn1, rgn2))
self.test('PtInRegion', lambda: Application.PtInRegion(rgn1, 10, 10))
self.test('MoveToEx', lambda: Application.MoveToEx(memDC, 0, 0))
self.test('LineTo', lambda: Application.LineTo(memDC, 100, 100))
self.test('Rectangle', lambda: Application.Rectangle(memDC, 10, 10, 90, 90))
self.test('RoundRect', lambda: Application.RoundRect(memDC, 10, 10, 90, 90, 10, 10))
self.test('Ellipse', lambda: Application.Ellipse(memDC, 10, 10, 90, 90))
self.test('GetPixel', lambda: Application.GetPixel(memDC, 5, 5))
self.test('SetPixel', lambda: Application.SetPixel(memDC, 5, 5, clWhite))
self.test('GetTextColor', lambda: Application.GetTextColor(memDC))
self.test('SetTextColor', lambda: Application.SetTextColor(memDC, clBlack))
self.test('GetBkColor', lambda: Application.GetBkColor(memDC))
self.test('SetBkColor', lambda: Application.SetBkColor(memDC, clWhite))
self.test('SetBkMode', lambda: Application.SetBkMode(memDC, 2)) # OPAQUE
self.test('GetBkMode', lambda: Application.GetBkMode(memDC))
self.test('TextOut', lambda: Application.TextOut(memDC, 10, 120, 'glcl GDI test'))
self.test('GetDeviceCaps', lambda: Application.GetDeviceCaps(memDC, 8)) # HORZRES
saved = self.test_and_return('SaveDC', lambda: Application.SaveDC(memDC))
self.test('RestoreDC', lambda: Application.RestoreDC(memDC, saved if saved else -1))
self.test('SetMapMode', lambda: Application.SetMapMode(memDC, 1)) # MM_TEXT
self.test('GetMapMode', lambda: Application.GetMapMode(memDC))
self.test('SetROP2', lambda: Application.SetROP2(memDC, 13)) # R2_COPYPEN
self.test('GetROP2', lambda: Application.GetROP2(memDC))
self.test('SetStretchBltMode', lambda: Application.SetStretchBltMode(memDC, 3)) # COLORONCOLOR
self.test('GetStretchBltMode', lambda: Application.GetStretchBltMode(memDC))
self.test('GetStockObject', lambda: Application.GetStockObject(0)) # WHITE_BRUSH
memDC2 = self.util('CreateCompatibleDC(2nd, helper)', lambda: Application.CreateCompatibleDC(screenDC))
memBmp2 = self.util('CreateCompatibleBitmap(2nd, helper)', lambda: Application.CreateCompatibleBitmap(screenDC, 50, 50))
old2 = None
if memDC2 and memBmp2:
old2 = self.util('SelectObject(2nd, helper)', lambda: Application.SelectObject(memDC2, memBmp2))
self.test('PatBlt', lambda: Application.PatBlt(memDC2, 0, 0, 50, 50, 0x00F00021)) # PATCOPY
if memDC and memDC2:
self.test('StretchBlt', lambda: Application.StretchBlt(memDC, 100, 100, 50, 50, memDC2, 0, 0, 50, 50, 0x00CC0020)) # SRCCOPY
# ---- cleanup (uses DeleteObject / DeleteDC, which ARE part of the
# 162 under test, so the very first calls below are still counted) ----
if memDC2 and old2 is not None:
self.util('SelectObject(restore 2nd)', lambda: Application.SelectObject(memDC2, old2))
if memBmp2:
self.test('DeleteObject', lambda: Application.DeleteObject(memBmp2))
if memDC2:
self.test('DeleteDC', lambda: Application.DeleteDC(memDC2))
if oldObj is not None:
self.util('SelectObject(restore 1st)', lambda: Application.SelectObject(memDC, oldObj))
for h in (brush, hatchBrush, pen, rgn1, rgn2, rgn3, rgnDest, memBmp):
if h:
self.util('DeleteObject(cleanup)', lambda h=h: Application.DeleteObject(h))
if memDC:
self.util('DeleteDC(cleanup)', lambda: Application.DeleteDC(memDC))
self.util('ReleaseDC(cleanup, pre-existing fn)', lambda: Application.ReleaseDC(0, screenDC))
# ------------------------------------------------- original Application --
# Exactly the 26 functions originally declared between MessageBox_Wrapper
# and RGB_Wrapper in the TPyDelphiApplication interface section (i.e. the
# functions that existed before any of the Win32 API work started).
def section_original_26(self):
self.log('[NOTE] MessageBox / MessageDlg / InputBox / SelectDirectory below show real, blocking dialog boxes - please respond to each popup to let the test continue.')
self.test('MessageBox', lambda: Application.MessageBox(
'This is a test message from the glcl function test suite.\nClick OK to continue.', 'glcl Test', 0))
self.test('MessageDlg', lambda: Application.MessageDlg('glcl MessageDlg test - click OK.', 0, 2, 0)) # mtWarning, mbOK
self.test('InputBox', lambda: Application.InputBox('glcl Test', 'Type anything (or just click OK):', 'default'))
try:
self.test('SelectDirectory', lambda: Application.SelectDirectory('Pick any folder, or click Cancel', 'C:\\'))
except Exception:
pass
self.log('[NOTE] PrintText/PrintImage below will send output to your default printer.')
self.test('PrintText', lambda: Application.PrintText('glcl test', 10, 10, self.Memo1.Font))
bmp = self.util('Bitmap(setup)', lambda: Bitmap(20, 20))
if bmp is not None:
self.test('PrintImage', lambda: Application.PrintImage(bmp, 10, 10, 1))
self.test('ColorToString', lambda: Application.ColorToString(clRed))
tempForm = Form(self)
tempForm.Caption = 'glcl temp (original-fn tests)'
tempForm.Show()
self.test('ShowWindow', lambda: Application.ShowWindow(tempForm.Handle, 5)) # SW_SHOW
self.log('[NOTE] WinExec below silently launches "cmd.exe /c exit" (starts and immediately exits, no visible window).')
self.test('WinExec', lambda: Application.WinExec('cmd.exe /c exit', 0))
tmp = self.util('GetTempPath(helper)', lambda: Application.GetTempPath())
self.log('[NOTE] ShellExecute below opens an Explorer window on your temp folder.')
self.test('ShellExecute', lambda: Application.ShellExecute(0, 'open', tmp or 'C:\\', '', '', 1))
self.test('SendMessage', lambda: Application.SendMessage(tempForm.Handle, 0x000B, 0, 0)) # WM_SETREDRAW-ish, harmless no-op style message (0x000B = WM_SETREDRAW)
self.test('PostMessage', lambda: Application.PostMessage(tempForm.Handle, 0x0000, 0, 0)) # WM_NULL - always safe, does nothing
self.test('GetSystemPath', lambda: Application.GetSystemPath(0))
self.test('ReleaseCapture', lambda: Application.ReleaseCapture())
tempForm2 = Form(self)
tempForm2.Show()
self.test('SetParent', lambda: Application.SetParent(tempForm2.Handle, tempForm.Handle))
self.util('SetParent(restore)', lambda: Application.SetParent(tempForm2.Handle, 0))
self.test('SetWindowPos', lambda: Application.SetWindowPos(tempForm.Handle, 0, 10, 10, 300, 200, 0x0040)) # SWP_SHOWWINDOW
self.util('tempForm.Free(cleanup)', lambda: tempForm.Free())
self.util('tempForm2.Free(cleanup)', lambda: tempForm2.Free())
self.test('ShowCursor', lambda: Application.ShowCursor(True))
self.test('PtInRect', lambda: Application.PtInRect(Rect(0, 0, 100, 100), Point(50, 50)))
screenDC = self.test_and_return('GetDC', lambda: Application.GetDC(0))
if screenDC:
memDC = self.util('CreateCompatibleDC(helper)', lambda: Application.CreateCompatibleDC(screenDC))
if memDC:
memBmp = self.util('CreateCompatibleBitmap(helper)', lambda: Application.CreateCompatibleBitmap(screenDC, 20, 20))
old = None
if memBmp:
old = self.util('SelectObject(helper)', lambda: Application.SelectObject(memDC, memBmp))
self.test('BitBlt', lambda: Application.BitBlt(memDC, 0, 0, 10, 10, memDC, 0, 0, 0x00CC0020)) # SRCCOPY
if old is not None:
self.util('SelectObject(restore)', lambda: Application.SelectObject(memDC, old))
if memBmp:
self.util('DeleteObject(cleanup)', lambda: Application.DeleteObject(memBmp))
self.util('DeleteDC(cleanup)', lambda: Application.DeleteDC(memDC))
self.test('ReleaseDC', lambda: Application.ReleaseDC(0, screenDC))
hMutex = self.test_and_return('CreateMutex', lambda: Application.CreateMutex('glcl_test_mutex_orig'))
self.test('OpenMutex', lambda: Application.OpenMutex('glcl_test_mutex_orig')) # SYNCHRONIZE
if hMutex:
self.test('CloseHandle', lambda: Application.CloseHandle(hMutex))
self.test('GetLastError', lambda: Application.GetLastError())
self.test('RGB', lambda: Application.RGB(255, 0, 0))
# -------------------------------------------- batch 2 & 3: new libraries --
def section_registry(self):
HKEY_CURRENT_USER = -2147483647
KEY_ALL_ACCESS = 0xF003F
subkey = 'Software\\glcl_test'
hKey = self.test_and_return('RegCreateKeyEx', lambda: Application.RegCreateKeyEx(HKEY_CURRENT_USER, subkey, KEY_ALL_ACCESS))
if hKey:
self.test('RegSetStringValue', lambda: Application.RegSetStringValue(hKey, 'TestString', 'hello glcl'))
self.test('RegSetDWordValue', lambda: Application.RegSetDWordValue(hKey, 'TestDWord', 42))
self.test('RegQueryStringValue', lambda: Application.RegQueryStringValue(hKey, 'TestString'))
self.test('RegQueryDWordValue', lambda: Application.RegQueryDWordValue(hKey, 'TestDWord'))
self.test('RegEnumValue', lambda: Application.RegEnumValue(hKey, 0))
self.test('RegFlushKey', lambda: Application.RegFlushKey(hKey))
self.test('RegDeleteValue', lambda: Application.RegDeleteValue(hKey, 'TestDWord'))
self.test('RegCloseKey', lambda: Application.RegCloseKey(hKey))
self.test('RegOpenKeyEx', lambda: Application.RegOpenKeyEx(HKEY_CURRENT_USER, 'Software', KEY_ALL_ACCESS))
self.test('RegEnumKeyEx', lambda: Application.RegEnumKeyEx(HKEY_CURRENT_USER, 0))
self.test('RegDeleteKey', lambda: Application.RegDeleteKey(HKEY_CURRENT_USER, subkey))
hRemote = self.test_and_return('RegConnectRegistry', lambda: Application.RegConnectRegistry('', HKEY_CURRENT_USER))
if hRemote:
self.util('RegCloseKey(cleanup)', lambda: Application.RegCloseKey(hRemote))
self.test('GetUserName', lambda: Application.GetUserName())
def section_service(self):
SC_MANAGER_ALL_ACCESS = 0xF003F
SERVICE_QUERY_STATUS = 0x0004
SERVICE_ALL_ACCESS = 0xF01FF
scm = self.test_and_return('OpenSCManager', lambda: Application.OpenSCManager('', '', SC_MANAGER_ALL_ACCESS))
print(scm)
svc = None
if scm:
svc = self.test_and_return('OpenService', lambda: Application.OpenService(scm, 'MpsSvc', SERVICE_ALL_ACCESS))
if svc:
self.test('QueryServiceState', lambda: Application.QueryServiceState(svc))
self.log('[NOTE] the service handle above was opened with query-only rights, so the next 3 calls are expected to fail with access denied - that still proves the functions are wired correctly.')
self.test('StartService', lambda: Application.StartService(svc))
#self.test('ControlService', lambda: Application.ControlService(svc, 1))
#self.test('DeleteService', lambda: Application.DeleteService(svc))
self.test('CloseServiceHandle', lambda: Application.CloseServiceHandle(svc))
if scm:
self.util('CloseServiceHandle(scm cleanup)', lambda: Application.CloseServiceHandle(scm))
def section_shlwapi(self):
winDir = self.util('GetWindowsDirectory(helper)', lambda: Application.GetWindowsDirectory()) or 'C:\\Windows'
self.test('PathFileExists', lambda: Application.PathFileExists(winDir))
self.test('PathIsDirectory', lambda: Application.PathIsDirectory(winDir))
self.test('PathIsRoot', lambda: Application.PathIsRoot('C:\\'))
self.test('PathIsRelative', lambda: Application.PathIsRelative('..\\test'))
self.test('PathIsUNC', lambda: Application.PathIsUNC('\\\\server\\share'))
self.test('PathIsURL', lambda: Application.PathIsURL('https://example.com'))
self.test('PathRemoveBackslash', lambda: Application.PathRemoveBackslash(winDir + '\\'))
self.test('PathAddBackslash', lambda: Application.PathAddBackslash(winDir))
self.test('PathRemoveExtension', lambda: Application.PathRemoveExtension('test.txt'))
self.test('PathRemoveFileSpec', lambda: Application.PathRemoveFileSpec(winDir + '\\System32'))
self.test('PathFindExtension', lambda: Application.PathFindExtension('test.txt'))
self.test('PathFindFileName', lambda: Application.PathFindFileName(winDir + '\\notepad.exe'))
self.test('PathGetDriveNumber', lambda: Application.PathGetDriveNumber(winDir))
self.test('PathStripPath', lambda: Application.PathStripPath(winDir + '\\notepad.exe'))
self.test('PathIsPrefix', lambda: Application.PathIsPrefix(winDir, winDir + '\\System32'))
self.test('PathMatchSpec', lambda: Application.PathMatchSpec('test.txt', '*.txt'))
self.test('PathIsSameRoot', lambda: Application.PathIsSameRoot(winDir, 'C:\\Users'))
self.test('PathCombine', lambda: Application.PathCombine(winDir, 'System32'))
self.test('PathAppend', lambda: Application.PathAppend(winDir, 'System32'))
self.test('PathQuoteSpaces', lambda: Application.PathQuoteSpaces('C:\\Program Files'))
self.test('PathUnquoteSpaces', lambda: Application.PathUnquoteSpaces('"C:\\Program Files"'))
self.test('PathCanonicalize', lambda: Application.PathCanonicalize(winDir + '\\..\\Windows\\.\\System32'))
self.test('StrToInt', lambda: Application.StrToInt('12345'))
self.test('StrFormatByteSize', lambda: Application.StrFormatByteSize(1536000))
self.test('StrChr', lambda: Application.StrChr('glcl test', 't'))
self.test('StrStr', lambda: Application.StrStr('glcl test string', 'test'))
def section_winmm(self):
self.test('timeGetTime', lambda: Application.timeGetTime())
self.test('timeBeginPeriod', lambda: Application.timeBeginPeriod(1))
self.test('timeEndPeriod', lambda: Application.timeEndPeriod(1))
self.log('[NOTE] PlaySound/sndPlaySound below will play a short system sound.')
self.test('PlaySound', lambda: Application.PlaySound('SystemAsterisk', 0x00010000)) # SND_ALIAS
self.test('sndPlaySound', lambda: Application.sndPlaySound('SystemAsterisk', 0x0001)) # SND_ASYNC
self.test('waveOutGetNumDevs', lambda: Application.waveOutGetNumDevs())
self.test('waveInGetNumDevs', lambda: Application.waveInGetNumDevs())
self.test('midiOutGetNumDevs', lambda: Application.midiOutGetNumDevs())
self.test('midiInGetNumDevs', lambda: Application.midiInGetNumDevs())
self.test('auxGetNumDevs', lambda: Application.auxGetNumDevs())
self.test('joyGetNumDevs', lambda: Application.joyGetNumDevs())
self.test('mixerGetNumDevs', lambda: Application.mixerGetNumDevs())
self.test('mmioStringToFOURCC', lambda: Application.mmioStringToFOURCC('WAVE', 0))
def section_shell32_extra(self):
self.test('SHGetFolderPath', lambda: Application.SHGetFolderPath(5)) # CSIDL_PERSONAL (My Documents)
self.test('ShellAbout', lambda: Application.ShellAbout(self.Handle, 'glcl Test', 'Function test suite', 0))
self.test('ExtractIcon', lambda: Application.ExtractIcon(0, Application.GetWindowsDirectory() + 'explorer.exe', 0))
self.test('SHGetFileTypeDescription', lambda: Application.SHGetFileTypeDescription(Application.GetWindowsDirectory() + 'explorer.exe'))
self.test('IsUserAnAdmin', lambda: Application.IsUserAnAdmin())
tmpdir = tempfile.mkdtemp(prefix='glcltest_shell_')
testfile = os.path.join(tmpdir, 'recycle_me.txt')
with open(testfile, 'w') as f:
f.write('delete me')
self.log('[NOTE] SHFileOperationDelete below sends a temp test file to the Recycle Bin.')
self.test('SHFileOperationDelete', lambda: Application.SHFileOperationDelete(testfile, True))
self.util('cleanup tmpdir', lambda: os.rmdir(tmpdir) if os.path.exists(tmpdir) else None)
self.log('[NOTE] SHEmptyRecycleBin below empties the Recycle Bin for drive C:.')
self.test('SHEmptyRecycleBin', lambda: Application.SHEmptyRecycleBin('C:\\'))
def section_kernel32_extra(self):
h = self.util('LoadLibrary(helper)', lambda: Application.LoadLibrary('user32.dll'))
if h:
self.test('GetProcAddress', lambda: Application.GetProcAddress(h, 'MessageBoxW'))
self.util('FreeLibrary(cleanup)', lambda: Application.FreeLibrary(h))
pid = self.util('GetCurrentProcessId(helper)', lambda: Application.GetCurrentProcessId())
proc = self.util('OpenProcess(helper)', lambda: Application.OpenProcess(0x0400, False, pid)) # PROCESS_QUERY_INFORMATION
if proc:
self.test('GetExitCodeProcess', lambda: Application.GetExitCodeProcess(proc))
self.test('GetPriorityClass', lambda: Application.GetPriorityClass(proc))
self.test('SetPriorityClass', lambda: Application.SetPriorityClass(proc, 0x00000020)) # NORMAL_PRIORITY_CLASS
self.util('CloseHandle(cleanup)', lambda: Application.CloseHandle(proc))
self.test('GetLocalTime', lambda: Application.GetLocalTime())
self.test('GetSystemTime', lambda: Application.GetSystemTime())
tf = tempfile.NamedTemporaryFile(delete=False)
tf.write(b'0123456789')
tf.close()
fh = None
pyfile = None
try:
import msvcrt
pyfile = open(tf.name, 'rb')
fh = msvcrt.get_osfhandle(pyfile.fileno())
except Exception as e:
self.log('[SETUP WARNING] get_osfhandle(helper) -> %s' % e)
if fh:
self.test('GetFileSize', lambda: Application.GetFileSize(fh))
if pyfile:
self.util('close temp file', lambda: pyfile.close())
self.util('cleanup temp file', lambda: os.remove(tf.name) if os.path.exists(tf.name) else None)
def section_lockworkstation_and_exit(self):
self.log('[NOTE] LockWorkStation/ExitWindowsEx are not invoked automatically in this')
self.log(' test suite - locking your screen or logging off/shutting down mid-run')
self.log(' would interrupt the test itself. Both functions ARE fully wired and')
self.log(' usable; uncomment the two lines below in section_lockworkstation_and_exit')
self.log(' if you want to test them manually.')
# Application.LockWorkStation() # locks your screen immediately
# Application.ExitWindowsEx(0, 0) # EWX_LOGOFF - logs the current user off immediately
self.skip('LockWorkStation', 'not auto-invoked (would lock your screen) - see code comment to test manually')
self.skip('ExitWindowsEx', 'not auto-invoked (would log off/shut down/restart) - see code comment to test manually')
def Button1Click(self, Sender):
self.Memo1.Lines.Clear()
self.pass_count = 0
self.fail_count = 0
self.tested_names = []
self.skipped_names = []
self.log('=== glcl Application function test: 264 functions ===')
start = time.time()
self.run_section('1. kernel32 - system / paths (28)', self.section_kernel32_basic)
self.run_section('2. kernel32 - filesystem / environment (10)', self.section_filesystem)
self.run_section('3. kernel32 - module loading (2)', self.section_module)
self.run_section('4. kernel32 - process / sync objects (12)', self.section_process_and_sync)
self.run_section('5. user32 - windows (40)', self.section_windows)
self.run_section('6. user32 - menus (11)', self.section_menu)
self.run_section('7. user32 - input / clipboard (19)', self.section_input_and_clipboard)
self.run_section('8. gdi32 - drawing (40)', self.section_gdi)
self.run_section('9. original Application - MessageBox..RGB (26)', self.section_original_26)
self.run_section('10. advapi32 - registry (14)', self.section_registry)
self.run_section('11. advapi32 - service control (7)', self.section_service)
self.run_section('12. shlwapi - path / string utils (26)', self.section_shlwapi)
self.run_section('13. winmm - multimedia (13)', self.section_winmm)
self.run_section('14. shell32 (7)', self.section_shell32_extra)
self.run_section('15. kernel32 extra (7)', self.section_kernel32_extra)
self.run_section('16. lock/exit (2, not auto-invoked)', self.section_lockworkstation_and_exit)
elapsed = time.time() - start
self.log('')
self.log('=== Finished in %.2fs: %d passed / %d failed / %d called / %d intentionally skipped ===' %
(elapsed, self.pass_count, self.fail_count, len(self.tested_names), len(self.skipped_names)))
missing = self.expected_missing()
if missing:
self.log('[WARNING] %d function(s) were not exercised: %s' % (len(missing), ', '.join(missing)))
else:
self.log('All target functions were called or intentionally skipped (see [SKIP] lines above).')
def expected_missing(self):
expected = set(EXPECTED_ALL)
called = set(self.tested_names) | set(self.skipped_names)
return sorted(expected - called)
# The full set of function names this test suite must cover (tested or
# intentionally skipped) - used only for the self-check at the end of the run,
# to make sure nothing was forgotten.
EXPECTED_162 = [
'Beep', 'Sleep', 'GetTickCount', 'GetCurrentProcessId', 'GetCurrentThreadId',
'GetCurrentProcess', 'GetCurrentThread', 'SetLastError', 'MulDiv', 'GetComputerName',
'SetEnvironmentVariable', 'GetEnvironmentVariable', 'GetSystemDirectory', 'GetWindowsDirectory',
'GetTempPath', 'GetCurrentDirectory', 'SetCurrentDirectory', 'CreateDirectory', 'RemoveDirectory',
'DeleteFile', 'CopyFile', 'MoveFile', 'GetFileAttributes', 'SetFileAttributes', 'GetFullPathName',
'GetLogicalDrives', 'GetDriveType', 'OutputDebugString', 'GetModuleFileName', 'LoadLibrary',
'FreeLibrary', 'TerminateProcess', 'OpenProcess', 'WaitForSingleObject', 'CreateEvent', 'OpenEvent',
'SetEvent', 'ResetEvent', 'PulseEvent', 'ReleaseMutex', 'CreateSemaphore', 'OpenSemaphore',
'ReleaseSemaphore', 'IsDebuggerPresent', 'GetVersion', 'MessageBeep', 'FindWindow', 'FindWindowEx',
'GetWindowText', 'SetWindowText', 'GetWindowTextLength', 'GetClassName', 'IsWindow', 'IsWindowVisible',
'IsWindowEnabled', 'IsIconic', 'IsZoomed', 'IsChild', 'EnableWindow', 'DestroyWindow', 'MoveWindow',
'GetParent', 'GetDesktopWindow', 'GetForegroundWindow', 'SetForegroundWindow', 'GetActiveWindow',
'SetActiveWindow', 'GetWindowLong', 'SetWindowLong', 'GetSystemMetrics', 'GetAsyncKeyState',
'GetKeyState', 'keybd_event', 'mouse_event', 'SetCursorPos', 'GetCapture', 'SetCapture',
'ShowWindowAsync', 'AnimateWindow', 'FlashWindow', 'BringWindowToTop', 'UpdateWindow',
'RegisterWindowMessage', 'RegisterClipboardFormat', 'OpenClipboard', 'CloseClipboard', 'EmptyClipboard',
'IsClipboardFormatAvailable', 'CountClipboardFormats', 'GetWindowDC', 'GetDlgItem', 'GetDlgCtrlID',
'SetDlgItemText', 'SetDlgItemInt', 'CheckDlgButton', 'IsDlgButtonChecked', 'GetMenu', 'SetMenu',
'GetSystemMenu', 'GetMenuItemCount', 'GetMenuItemID', 'EnableMenuItem', 'DeleteMenu', 'RemoveMenu',
'CreateMenu', 'CreatePopupMenu', 'DestroyMenu', 'SetCursor', 'GetCursor', 'LoadCursorFromFile',
'GetSysColor', 'GetDoubleClickTime', 'SetDoubleClickTime', 'CreateCompatibleDC', 'CreateCompatibleBitmap',
'DeleteDC', 'DeleteObject', 'SelectObject', 'GetStockObject', 'CreateSolidBrush', 'CreateHatchBrush',
'CreatePen', 'CreateRectRgn', 'CreateEllipticRgn', 'CreateRoundRectRgn', 'CombineRgn', 'EqualRgn',
'PtInRegion', 'LineTo', 'Rectangle', 'RoundRect', 'Ellipse', 'GetPixel', 'SetPixel', 'GetTextColor',
'SetTextColor', 'GetBkColor', 'SetBkColor', 'SetBkMode', 'GetBkMode', 'TextOut', 'GetDeviceCaps',
'SaveDC', 'RestoreDC', 'SetMapMode', 'GetMapMode', 'SetROP2', 'GetROP2', 'SetStretchBltMode',
'GetStretchBltMode', 'StretchBlt', 'PatBlt', 'GetCursorPos', 'GetWindowRect', 'GetClientRect',
'ClientToScreen', 'ScreenToClient', 'MoveToEx', 'GetShortPathName', 'GetLongPathName',
'GetLogicalDriveStrings', 'GetDlgItemText',
]
# Exactly the 26 functions originally declared between MessageBox_Wrapper and
# RGB_Wrapper in TPyDelphiApplication (i.e. before any Win32 API work started).
EXPECTED_ORIGINAL = [
'MessageBox', 'MessageDlg', 'InputBox', 'SelectDirectory', 'PrintText', 'PrintImage',
'ColorToString', 'ShowWindow', 'WinExec', 'ShellExecute', 'SendMessage', 'PostMessage',
'GetSystemPath', 'ReleaseCapture', 'SetParent', 'SetWindowPos', 'ShowCursor', 'PtInRect',
'GetDC', 'ReleaseDC', 'BitBlt', 'CreateMutex', 'OpenMutex', 'CloseHandle', 'GetLastError', 'RGB',
]
# Batch 2 + batch 3: advapi32 / shlwapi / winmm / shell32 / extra kernel32.
EXPECTED_BATCH23 = [
'RegOpenKeyEx', 'RegCreateKeyEx', 'RegCloseKey', 'RegDeleteKey', 'RegDeleteValue', 'RegFlushKey',
'RegEnumKeyEx', 'RegEnumValue', 'RegQueryStringValue', 'RegSetStringValue', 'RegQueryDWordValue',
'RegSetDWordValue', 'RegConnectRegistry', 'GetUserName',
'OpenSCManager', 'OpenService', 'CloseServiceHandle', 'QueryServiceState', 'StartService',
'ControlService', 'DeleteService',
'PathFileExists', 'PathIsDirectory', 'PathIsRoot', 'PathIsRelative', 'PathIsUNC', 'PathIsURL',
'PathRemoveBackslash', 'PathAddBackslash', 'PathRemoveExtension', 'PathRemoveFileSpec',
'PathFindExtension', 'PathFindFileName', 'PathGetDriveNumber', 'PathStripPath', 'PathIsPrefix',
'PathMatchSpec', 'PathIsSameRoot', 'PathCombine', 'PathAppend', 'PathQuoteSpaces',
'PathUnquoteSpaces', 'PathCanonicalize', 'StrToInt', 'StrFormatByteSize', 'StrChr', 'StrStr',
'timeGetTime', 'timeBeginPeriod', 'timeEndPeriod', 'PlaySound', 'sndPlaySound',
'waveOutGetNumDevs', 'waveInGetNumDevs', 'midiOutGetNumDevs', 'midiInGetNumDevs',
'auxGetNumDevs', 'joyGetNumDevs', 'mixerGetNumDevs', 'mmioStringToFOURCC',
'SHGetFolderPath', 'ShellAbout', 'ExtractIcon', 'SHGetFileTypeDescription', 'IsUserAnAdmin',
'SHFileOperationDelete', 'SHEmptyRecycleBin',
'GetProcAddress', 'GetExitCodeProcess', 'SetPriorityClass', 'GetPriorityClass', 'GetFileSize',
'GetLocalTime', 'GetSystemTime', 'ExitWindowsEx', 'LockWorkStation',
]
EXPECTED_ALL = EXPECTED_162 + EXPECTED_ORIGINAL + EXPECTED_BATCH23
设计文件: Unit1.sct¶
def Button1Click(Sender):
设计文件: Unit1.sfm¶
object Form1: TForm
Left = 0
Top = 0
Caption = 'glcl Application Win32 API Test (264 functions)'
ClientHeight = 595
ClientWidth = 882
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -15
Font.Name = 'Segoe UI'
Font.Style = []
TextHeight = 20
object Button1: TButton
Left = 0
Top = 0
Width = 882
Height = 25
Align = alTop
Caption = 'Run All 264 Tests'
TabOrder = 0
OnClick = Button1Click
ExplicitLeft = 88
ExplicitTop = 48
ExplicitWidth = 75
end
object Memo1: TMemo
Left = 0
Top = 25
Width = 882
Height = 570
Align = alClient
ScrollBars = ssVertical
TabOrder = 1
ExplicitWidth = 393
ExplicitHeight = 286
end
end
详细分析¶
功能概述¶
本示例是一个自包含的回归测试套件,用于验证 glcl Application 对象上的 264 个函数。点击窗体上的 “Run All 264 Tests” 按钮后,程序会依次调用这些函数,并在日志框中输出每个函数的 [OK] / [FAIL] / [SKIP] 结果。
每个调用都被包装在 test() / util() / skip() 中,因此单个函数失败不会中断整个测试套件。
测试函数构成(264 个)¶
| 分组 | 数量 | 来源库 |
|---|---|---|
| Win32 API 函数 | 162 | kernel32.lib / user32.lib / gdi32.lib |
| 已有函数(原有) | 26 | MessageBox ~ RGB |
| 第二批 + 第三批 | 76 | advapi32 / shlwapi / winmm / shell32 / 额外 kernel32 |
| 合计 | 264 |
测试分区(16 个 section)¶
- kernel32 - 系统 / 路径 (28) — Beep、Sleep、GetTickCount、GetCurrentProcessId、MulDiv、GetComputerName、GetSystemDirectory、GetTempPath、GetLogicalDrives、FindWindow …
- kernel32 - 文件系统 / 环境 (10) — SetEnvironmentVariable、CreateDirectory、CopyFile、MoveFile、DeleteFile、Get/SetFileAttributes、SetCurrentDirectory …
- kernel32 - 模块加载 (2) — LoadLibrary、FreeLibrary
- kernel32 - 进程 / 同步对象 (12) — OpenProcess、TerminateProcess、CreateEvent、SetEvent、ResetEvent、PulseEvent、CreateSemaphore、ReleaseSemaphore、ReleaseMutex …
- user32 - 窗口 (40) — IsWindow、EnableWindow、MoveWindow、GetWindowRect、ClientToScreen、GetWindowText、SetWindowLong、ShowWindowAsync、AnimateWindow、FlashWindow、GetDesktopWindow …
- user32 - 菜单 (11) — CreateMenu、CreatePopupMenu、GetMenuItemCount、EnableMenuItem、DeleteMenu、RemoveMenu、GetSystemMenu …
- user32 - 输入 / 剪贴板 (19) — GetCursorPos、SetCursorPos、GetAsyncKeyState、keybd_event、mouse_event、OpenClipboard、EmptyClipboard、RegisterClipboardFormat、GetDoubleClickTime …
- gdi32 - 绘图 (40) — CreateCompatibleDC、CreateSolidBrush、CreatePen、CreateRectRgn、CombineRgn、LineTo、Rectangle、Ellipse、GetPixel、SetPixel、TextOut、GetDeviceCaps、SaveDC、SetMapMode、StretchBlt、PatBlt …
- 原始 Application - MessageBox..RGB (26) — MessageBox、MessageDlg、InputBox、SelectDirectory、PrintText、PrintImage、ShowWindow、WinExec、ShellExecute、SendMessage、PostMessage、SetParent、SetWindowPos、BitBlt、CreateMutex、RGB …
- advapi32 - 注册表 (14) — RegCreateKeyEx、RegSetStringValue、RegQueryStringValue、RegEnumValue、RegDeleteValue、RegOpenKeyEx、RegDeleteKey、GetUserName …
- advapi32 - 服务控制 (7) — OpenSCManager、OpenService、QueryServiceState、StartService、CloseServiceHandle …
- shlwapi - 路径 / 字符串工具 (26) — PathFileExists、PathIsDirectory、PathRemoveExtension、PathFindFileName、PathCombine、PathCanonicalize、StrToInt、StrFormatByteSize、StrChr、StrStr …
- winmm - 多媒体 (13) — timeGetTime、timeBeginPeriod、PlaySound、sndPlaySound、waveOutGetNumDevs、midiOutGetNumDevs、mmioStringToFOURCC …
- shell32 (7) — SHGetFolderPath、ShellAbout、ExtractIcon、SHGetFileTypeDescription、IsUserAnAdmin、SHFileOperationDelete、SHEmptyRecycleBin
- kernel32 extra (7) — GetProcAddress、GetExitCodeProcess、GetPriorityClass、SetPriorityClass、GetLocalTime、GetSystemTime、GetFileSize
- 锁屏 / 退出 (2,不自动调用) — LockWorkStation、ExitWindowsEx(会锁屏/注销/关机,因此标记为
[SKIP],在代码注释里说明如何手动测试)
技术特点¶
- 分类执行 + 容错:
run_section()包裹每个分区,即使某个分区整体出错,其余分区仍会继续运行。 - 三级调用封装:
test(name, func)— 目标函数,计入 pass/fail;util(name, func, default)— 辅助/前置函数(如创建临时目录、保存/恢复资源),失败仅记[SETUP WARNING],不计入统计;skip(name, reason)— 主动跳过的函数(如会锁屏的 LockWorkStation),记[SKIP]。- 句柄复用:
test_and_return()与test()类似,但会返回结果,便于后续步骤复用句柄(如CreateEvent→SetEvent/ResetEvent→CloseHandle)。 - 自检:通过
expected_missing()比对EXPECTED_162 + EXPECTED_ORIGINAL + EXPECTED_BATCH23期望列表与实际调用列表,输出未被覆盖的函数名。 - 资源清理:测试结束后统一
CloseHandle/DeleteObject/DeleteDC/ReleaseDC清理资源。 - 有副作用的测试均有提示:如清空剪贴板、发送临时文件到回收站、清空回收站、播放系统声音、打印、打开资源管理器窗口等,日志中都会先
[NOTE]提示。
窗体属性¶
- Caption:
glcl Application 264-function regression test - 关键控件:
Memo1— 大日志输出框,显示每个函数的 PASS/FAIL/SKIP 及最终汇总Button1— “Run All 264 Tests”按钮,触发Button1Click
代码分析¶
导入的模块:
- os、time、tempfile
- from glcl import *
定义的类:
- Form1
主要方法:
- log / test / util / skip / run_section / test_and_return — 测试基础设施
- section_kernel32_basic、section_filesystem、section_module、section_process_and_sync、section_windows、section_menu、section_input_and_clipboard、section_gdi、section_original_26、section_registry、section_service、section_shlwapi、section_winmm、section_shell32_extra、section_kernel32_extra、section_lockworkstation_and_exit — 16 个测试分区
- Button1Click — 依次执行全部分区并输出汇总
- expected_missing — 自检未覆盖的函数
模块级常量:
- EXPECTED_162 — 162 个 Win32 API 目标函数名
- EXPECTED_ORIGINAL — 原有的 26 个 Application 函数名
- EXPECTED_BATCH23 — 第二批 + 第三批的 76 个函数名
- EXPECTED_ALL — 三者合并,共 264 个
事件绑定:
- self.Button1.OnClick = self.Button1Click