为了解Wine 中针对GDI 绘图,完整的调用链路。以下以一个简单的示例demo 进行验证如下。
以创建画笔并绘制一条线段为例;
1、创建画笔
HPEN hPen = CreatePen(style, width, color);
此时会调用 gdi32.dll(objects.c) 中函数
/***********************************************************************
* CreatePen (GDI32.@)
*/
HPEN WINAPI CreatePen( INT style, INT width, COLORREF color )
{
if (style < 0 || style > PS_INSIDEFRAME) style = PS_SOLID;
return NtGdiCreatePen( style, width, color, NULL );
}
在win32u.spec 文件中 NtGdiCreatePen 函数定义为
@ stdcall -syscall NtGdiCreatePen(long long long long)
声明中有 -syscall 也即其有系统调用过程,会转到内核层进行调用。
也即会调用win32u.so (pen.c) 中函数。
/***********************************************************************
* NtGdiCreatePen (win32u.@)
*/
HPEN WINAPI NtGdiCreatePen( INT style, INT width, COLORREF color, HBRUSH brush )
{
if (brush) FIXME( "brush not supported\n" );
if (style == PS_NULL) return GetStockObject( NULL_PEN );
return create_pen( style, width, color );
}
2、画线
此时会调用 gdi32.dll(dc.c) 中函数
/***********************************************************************
* LineTo (GDI32.@)
*/
BOOL WINAPI LineTo( HDC hdc, INT x, INT y )
{
DC_ATTR *dc_attr;
TRACE( "%p, (%d, %d)\n", hdc, x, y );
if (is_meta_dc( hdc )) return METADC_LineTo( hdc, x, y );
if (!(dc_attr = get_dc_attr( hdc ))) return FALSE;
if (dc_attr->emf && !EMFDC_LineTo( dc_attr, x, y )) return FALSE;
return NtGdiLineTo( hdc, x, y );
}
继续调用到wrappers.c 中函数
BOOL WINAPI NtGdiLineTo( HDC hdc, INT x, INT y )
{
if (!unix_funcs) return FALSE;
return unix_funcs->pNtGdiLineTo( hdc, x, y );
}
此处 unix_funcs 是一个结构体,它定义了一些函数原型指针;
struct unix_funcs
{
/* win32u functions */
...
INT (WINAPI *pNtGdiIntersectClipRect)( HDC hdc, INT left, INT top, INT right, INT bottom );
BOOL (WINAPI *pNtGdiInvertRgn)( HDC hdc, HRGN hrgn );
BOOL (WINAPI *pNtGdiLineTo)( HDC hdc, INT x, INT y );
BOOL (WINAPI *pNtGdiMaskBlt)( HDC hdc, INT x_dst, INT y_dst, INT width_dst, INT height_dst,
HDC hdc_src, INT x_src, INT y_src, HBITMAP mask,
INT x_mask, INT y_mask, DWORD rop, DWORD bk_color );
BOOL (WINAPI *pNtGdiModifyWorldTransform)( HDC hdc, const XFORM *xform, DWORD mode );
BOOL (WINAPI *pNtGdiMoveTo)( HDC hdc, INT x, INT y, POINT *pt );
INT (WINAPI *pNtGdiOffsetClipRgn)( HDC hdc, INT x, INT y );
...
}
该结构体的初始化函数为:
extern void wrappers_init( unixlib_handle_t handle )
{
const void *args;
if (!__wine_unix_call( handle, 1, &args )) unix_funcs = args;
}
在该模块dllmain 中被调用:
BOOL WINAPI DllMain( HINSTANCE inst, DWORD reason, void *reserved )
{
switch (reason)
{
case DLL_PROCESS_ATTACH:
LdrDisableThreadCalloutsForDll( inst );
if (__wine_syscall_dispatcher) break; /* already set through Wow64Transition */
if (!NtQueryVirtualMemory( GetCurrentProcess(), inst, MemoryWineUnixFuncs,
&win32u_handle, sizeof(win32u_handle), NULL ))
{
__wine_unix_call( win32u_handle, 0, &__wine_syscall_dispatcher );
wrappers_init( win32u_handle );
}
break;
}
return TRUE;
}
继续追踪代码至ntdll.so 的virtual.c 文件中。了解其获取了win32u.so中关于内存和函数调用相关的函数指针;
NTSTATUS WINAPI NtQueryVirtualMemory( HANDLE process, LPCVOID addr,
MEMORY_INFORMATION_CLASS info_class,
PVOID buffer, SIZE_T len, SIZE_T *res_len )
{
case MemoryWineUnixFuncs:
case MemoryWineUnixWow64Funcs:
if (len != sizeof(unixlib_handle_t)) return STATUS_INFO_LENGTH_MISMATCH;
if (process == GetCurrentProcess())
{
void *module = (void *)addr;
const void *funcs = NULL;
status = get_builtin_unix_funcs( module, info_class == MemoryWineUnixWow64Funcs, &funcs );
if (!status) *(unixlib_handle_t *)buffer = (UINT_PTR)funcs;
return status;
}
return STATUS_INVALID_HANDLE;
...
}
按照上述方法查找,能够查到win32u.so painting.c 中实现的NtGdiLineTo 函数
/***********************************************************************
* NtGdiLineTo (win32u.@)
*/
BOOL WINAPI NtGdiLineTo( HDC hdc, INT x, INT y )
{
DC * dc = get_dc_ptr( hdc );
PHYSDEV physdev;
BOOL ret;
if(!dc) return FALSE;
update_dc( dc );
physdev = GET_DC_PHYSDEV( dc, pLineTo );
ret = physdev->funcs->pLineTo( physdev, x, y );
if(ret)
{
dc->attr->cur_pos.x = x;
dc->attr->cur_pos.y = y;
}
release_dc_ptr( dc );
return ret;
}
可以看到,通过一个 gdi_dc_funcs 结构体(physdev->funcs)来运行时指定待执行的指针函数;
gdi_driver.h 文件中定义该结构体的函数指针如下:
struct gdi_dc_funcs
{
INT (CDECL *pAbortDoc)(PHYSDEV);
BOOL (CDECL *pAbortPath)(PHYSDEV);
BOOL (CDECL *pAlphaBlend)(PHYSDEV,struct bitblt_coords*,PHYSDEV,struct bitblt_coords*,BLENDFUNCTION);
BOOL (CDECL *pAngleArc)(PHYSDEV,INT,INT,DWORD,FLOAT,FLOAT);
BOOL (CDECL *pArc)(PHYSDEV,INT,INT,INT,INT,INT,INT,INT,INT);
BOOL (CDECL *pArcTo)(PHYSDEV,INT,INT,INT,INT,INT,INT,INT,INT);
BOOL (CDECL *pBeginPath)(PHYSDEV);
DWORD (CDECL *pBlendImage)(PHYSDEV,BITMAPINFO*,const struct gdi_image_bits*,struct bitblt_coords*,struct bitblt_coords*,BLENDFUNCTION);
BOOL (CDECL *pChord)(PHYSDEV,INT,INT,INT,INT,INT,INT,INT,INT);
BOOL (CDECL *pCloseFigure)(PHYSDEV);
BOOL (CDECL *pCreateCompatibleDC)(PHYSDEV,PHYSDEV*);
BOOL (CDECL *pCreateDC)(PHYSDEV*,LPCWSTR,LPCWSTR,const DEVMODEW*);
BOOL (CDECL *pDeleteDC)(PHYSDEV);
BOOL (CDECL *pDeleteObject)(PHYSDEV,HGDIOBJ);
BOOL (CDECL *pEllipse)(PHYSDEV,INT,INT,INT,INT);
INT (CDECL *pEndDoc)(PHYSDEV);
INT (CDECL *pEndPage)(PHYSDEV);
BOOL (CDECL *pEndPath)(PHYSDEV);
BOOL (CDECL *pEnumFonts)(PHYSDEV,LPLOGFONTW,FONTENUMPROCW,LPARAM);
INT (CDECL *pExtEscape)(PHYSDEV,INT,INT,LPCVOID,INT,LPVOID);
BOOL (CDECL *pExtFloodFill)(PHYSDEV,INT,INT,COLORREF,UINT);
BOOL (CDECL *pExtTextOut)(PHYSDEV,INT,INT,UINT,const RECT*,LPCWSTR,UINT,const INT*);
BOOL (CDECL *pFillPath)(PHYSDEV);
BOOL (CDECL *pFillRgn)(PHYSDEV,HRGN,HBRUSH);
BOOL (CDECL *pFontIsLinked)(PHYSDEV);
BOOL (CDECL *pFrameRgn)(PHYSDEV,HRGN,HBRUSH,INT,INT);
...
}
此处,其找到了 dc.c 文件中dibdrv_LineTo 函数,完成线段的绘制。
/***********************************************************************
* dibdrv_LineTo
*/
BOOL CDECL dibdrv_LineTo( PHYSDEV dev, INT x, INT y )
{
dibdrv_physdev *pdev = get_dibdrv_pdev(dev);
DC *dc = get_physdev_dc( dev );
POINT pts[2];
HRGN region = 0;
BOOL ret;
pts[0] = dc->attr->cur_pos;
pts[1].x = x;
pts[1].y = y;
lp_to_dp(dc, pts, 2);
if (pdev->pen_uses_region && !(region = NtGdiCreateRectRgn( 0, 0, 0, 0 ))) return FALSE;
reset_dash_origin(pdev);
ret = pdev->pen_lines(pdev, 2, pts, FALSE, region);
add_pen_lines_bounds( pdev, 2, pts, region );
if (region)
{
ret = pen_region( pdev, region );
NtGdiDeleteObjectApp( region );
}
return ret;
}
此函数中 lp_to_dp/pen_region 等子函数会继续调用到winex11.so 中,后续再细究。
小提示:
1、文件头中有如下标识
#pragma makedep unix
则表明该文件导出的是.so 文件,可以直接与linux 内核函数进行互操作;
2、Nt 开头的函数一般都需要从用户态转成内核态;会在对应的dll
以及so 动态库中均存在。