Win9x下用Win32程序调用Win16函数

Win9x下有些系统函数只在Win16中存在,如GetFreeSystemResources

有些操作也只有在Win16环境中可以进行,如调用int 2fh等软中断服务,这时就需要调用Win16的DLL实现

Win9x下Win32程序可以使用Flat Thunk调用Win16的DLL,但官方的方法是使用Thunk Compiler,非常麻烦

使用Thunk Compiler的步骤(详见Q154093,注意早期MSDN将16位thunk dll的DllEntryPoint错写成DllMain):

1、需要编写一个32to16 thunk script(如果需要回调,还需要编写一个16to32 thunk script)

2、使用thunk compiler将32to16 thunk script编译成.asm(如果有16to32也需要编译)

3、按照要求使用MASM 6.1及以上版本将.asm汇编成32位和16位的两种.obj(如果有16to32也需要汇编)

4、按照要求编写16位thunk dll的DllEntryPoint和32位thunk dll的DllMain及各自的.def

5、编译链接16位和32位的thunk dll(链接对应版本的.obj,32位的thunk dll还要链接thunk32.lib)

6、在Win32程序中使用32位版本的thunk dll(在Win16程序中也可以使用16位版本的thunk dll)

实际上如果只是为了调用少量Win16函数,没必要这么麻烦,可以使用以下未文档化函数实现:

1、DWORD WINAPI LoadLibrary16(LPSTR) = kernel32.dll@35(按序号导出,获取方法见下文)

2、void WINAPI FreeLibrary16(DWORD) = kernel32.dll@36(按序号导出,获取方法见下文)

3、DWORD WINAPI GetProcAddress16(DWORD, LPSTR) = kernel32.dll@37(按序号导出,获取方法见下文)

4、QT_Thunk(按名称导出)(汇编函数,这个函数使用比较麻烦,见下文)

QT_Thunk使用比较麻烦------

1、QT_Thunk要求当前函数ebp-64到ebp是64字节的供QT_Thunk使用的缓冲区

2、edx指向要调用的16:16位函数指针

3、要使用16位PASCAL方式压入参数(push word ptr、顺序压入),call QT_Thunk后自动弹出

4、QT_Thunk不进行参数转换和地址映射,需手动进行

如果需要在16:16位分段地址和32位平坦地址之间映射,则需要使用MapLS、UnMapLS、MapSL等函数(按名称导出),需注意:

1、位于16位地址空间的16:16位分段地址需要GlobalFix后才会有不变的32位平坦地址(注意GMEM_FIXED和GlobalLock不起作用)

2、位于32位地址空间的地址在16位函数退出后可能因为进程切换而不再有效

也可以使用文档化的Generic Thunk的函数进行地址转换和16位内存分配

但是要注意Generic Thunk是为NTVDM设计的,在NT系统中Win32进程不能加载Win16的DLL,因此:

1、未包含与LoadLibrary16、FreeLibrary16、GetProcAddress16等价的函数

2、WOWCallback16和WOWCallback16Ex在Win9x的Win32进程中不可用(Win32程序必须使用Flat Thunk调用Win16的DLL函数)

3、Win9x的USER和GDI是16位的,因此不需要转换USER和GDI句柄,WOWHandle16和WOWHandle32始终返回相同值

4、Win9x下应使用WOWGetVDMPointerFix和WOWGetVDMPointerUnfix而不是WOWGetVDMPointer,除非已经GlobalFix

这些是可以在Win9x的Win32进程中使用的Generic Thunk函数(wownt32.h和wow32.lib):

WOWGetVDMPointer

WOWGetVDMPointerFix

WOWGetVDMPointerUnfix

WOWGlobalAlloc16

WOWGlobalAllocLock16

WOWGlobalFree16

WOWGlobalLock16

WOWGlobalLockSize16

WOWGlobalUnlock16

WOWGlobalUnlockFree16

WOWHandle16

WOWHandle32

另一个难点是Win9x的GetProcAddress拒绝解析kernel32.dll的导出序号,有两种方案:

1、手动解析kernel32.dll的PE文件头

2、使用下面的.def使用lib /def:xxx.def /out:xxx.lib生成一个导入库:

复制代码
LIBRARY KERNEL32
EXPORTS
    LoadLibrary16@4                 @35
    FreeLibrary16@4                 @36
    GetProcAddress16@8              @37

QT_Thunk和MapLS、UmMapLS、MapSL等函数可以:

1、直接链接thunk32.lib

2、在上面的导入库.def中声明

3、使用GetProcAddress获取地址

参考:

https://jacobfilipp.com/DrDobbs/articles/DDJ/1996/9614/9614c/9614c.htm

https://rgmroman.narod.ru/qtthunk.htm

https://rgmroman.narod.ru/flthunk.htm

获取未公开函数和16位函数指针:

cpp 复制代码
typedef DWORD (WINAPI*PLoadLibrary16)(LPSTR);
typedef void (WINAPI*PFreeLibrary16)(DWORD);
typedef DWORD (WINAPI*PGetProcAddress16)(DWORD, LPSTR);
static PLoadLibrary16 pLoadLibrary16;
static PFreeLibrary16 pFreeLibrary16;
static PGetProcAddress16 pGetProcAddress16;
static FARPROC pQT_Thunk;
static DWORD huser;
static DWORD addrGetFreeSystemResources;
if ((GetVersion() & 0x80000000) && LOWORD(GetVersion()) >= 4) {
	// Win9x GetProcAddress refuses to resolve kernel32.dll ordinals
	HINSTANCE hk32;
	PBYTE k32base;
	PIMAGE_DOS_HEADER k32dos;
	PIMAGE_NT_HEADERS k32nt;
	PIMAGE_EXPORT_DIRECTORY k32exp;
	PDWORD k32exparr;
	hk32 = GetModuleHandle("KERNEL32.DLL");
	k32base = (PBYTE)hk32;
	k32dos = (PIMAGE_DOS_HEADER)k32base;
	k32nt = (PIMAGE_NT_HEADERS)(k32base + k32dos->e_lfanew);
	k32exp = (PIMAGE_EXPORT_DIRECTORY)(k32base +
		k32nt->OptionalHeader.DataDirectory[
		IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
	k32exparr = (PDWORD)(k32base + k32exp->AddressOfFunctions);
	pLoadLibrary16 = (PLoadLibrary16)(k32base + k32exparr[35 - k32exp->Base]);
	pFreeLibrary16 = (PFreeLibrary16)(k32base + k32exparr[36 - k32exp->Base]);
	pGetProcAddress16 = (PGetProcAddress16)(k32base + k32exparr[37 - k32exp->Base]);
	pQT_Thunk = (FARPROC)GetProcAddress(hk32, "QT_Thunk");
	huser = pLoadLibrary16("USER");
	pFreeLibrary16(huser);
	addrGetFreeSystemResources = pGetProcAddress16(huser, "GETFREESYSTEMRESOURCES");
}

使用QT_Thunk调用16位函数:

cpp 复制代码
// use global / static variables to avoid accessing ebp
static WORD pcSys, pcGdi, pcUser;
pcSys = pcGdi = pcUser = 0;
if (pQT_Thunk) {
	_asm push ebp // constructs a special ebp frame
	_asm mov ebp, esp
	_asm sub esp, 64 // QT_Thunk requires a 64-byte scratch buffer below ebp

	_asm push word ptr 0 // GFSR_SYSTEMRESOURCES
	_asm mov edx, addrGetFreeSystemResources
	_asm call pQT_Thunk
	_asm mov pcSys, ax
	
	_asm push word ptr 1 // GFSR_GDIRESOURCES
	_asm mov edx, addrGetFreeSystemResources
	_asm call pQT_Thunk
	_asm mov pcGdi, ax
	
	_asm push word ptr 2 // GFSR_USERRESOURCES
	_asm mov edx, addrGetFreeSystemResources
	_asm call pQT_Thunk
	_asm mov pcUser, ax
	
	_asm leave // single instruction for 'mov esp, ebp' and 'pop ebp'
}
static char buf[1024];
wsprintf(buf, "System: %u%%\r\nGDI: %u%%\r\nUSER: %u%%", pcSys, pcGdi, pcUser);
MessageBox(NULL, buf, "Free System Resources", MB_ICONINFORMATION);
相关推荐
me83237 分钟前
【Tool】Windows 计划任务 + VBScript 弹窗提醒:从零到一的完整指南
windows
2601_962201722 小时前
Java进阶,集合,Colllection,常见数据结构
java·数据结构·windows
loong_XL4 小时前
Windows WSL2 Ubuntu 搭建 Xvfb 虚拟桌面环境,完美支持多 Agent 隔离
linux·windows·ubuntu
DogDaoDao13 小时前
Windows 开发提效工具全景指南:60+ 工具的工程化分层配置
windows·git·程序员·开发工具·powershell·everything·msys2
水饺编程17 小时前
第5章,[Win32 章节] :Polygon 函数和多边形填充模式
c语言·c++·windows·visual studio
百事牛科技18 小时前
文档不想再加密了?Word打开密码移除的两种方法
windows·word
软件资深者20 小时前
千千静听典藏修复版 使用教程:经典本地音乐播放器回归,歌词同步、皮肤皮肤自由换,音乐播放器怀旧新手 5 分钟上手(2026)
windows
captain37621 小时前
文件与IO(2)
java·开发语言·windows·java-ee
星栖与芯1 天前
# STM32MP157+LiteOS-M GCC极简工程:启动源码适配+Windows Make终极避坑
windows·stm32·单片机