Wednesday, May 12, 2004

 

Some notes on CPropertySheet

今天写程序突然要用到CPropertySheet,这才发现用了这么多年VC,居然一次CPropertySheet也没用过。其实用户也就是要求使用一个程序Option设置的东西,于是上网找到了这几个好东西

CTreePropSheet - A Netscape/Visual Studio .NET like Preferences Dialog
By Sven Wiegand
http://www.codeproject.com/property/treepropsheet.asp

High color icons for CPropertySheet
By Yves Tkaczyk
http://www.codeproject.com/property/HighColorTab.asp

Hacking the CPropertySheet
By Mustafa Demirhan
http://www.codeproject.com/property/hacking_the_cpropertyshee.asp

1.
这才发现原来可以这样激活Apply Button

// Enable Apply Now button
hWnd = ::GetDlgItem(m_hWnd, ID_APPLY_NOW);
if (hWnd != NULL)
{
::ShowWindow (hWnd, SW_SHOW);
::EnableWindow (hWnd, TRUE);
}

而下面的标准做法则变得无效

To enable this control programmatically, once a control becomes dirty, call the CPropertyPage::SetModified(). Its syntax is:

void SetModified(BOOL bChanged = TRUE);
This method is called by the control whose value you want to validate once the user has modified it.

2.
CPropertyPage检测数据有效性后避免自动关闭
from http://blog.csdn.net/111222/archive/2004/09/07/96375.aspx

今天用CPropertyPage的派生类接收用户输入的数据.

class CMovieIssueBasicPage : public CPropertyPage

在用户点"确定"的时候, 首先要检测数据是否有效, 然后再决定是否进行下一步操作. 一旦数据不符合标准, 要求用户重新输入. 这就要让属性页在数据无效的时候不能自动关闭.

习惯性地重载了OnOK虚函数

void CMovieIssueBasicPage::OnOK()
{

if(IsDataAvailably())
CPropertyPage::OnOK();

}

却发现, 无论IsDataAvailably()函数返回TRUE还是FALSE, 无论CPropertyPage::OnOK();是否被执行到了,属性页都执着地要自动关闭.

难道就不能干预了么?

先看看CPropertyPage::OnOK();前后都干了些什么.

在void CMovieIssueBasicPage::OnOK() 内部设置断点,然后开始调试.

执行到OnOK()的时候, 看Call Stack:

CMovieIssueBasicPage::OnOK() line 137
CPropertyPage::OnApply() line 302
CMovieIssueBasicPage::OnApply() line 337

显然OnOK之前先执行了OnApply(), OnApply里面又干了些什么呢? go to code:

BOOL CPropertyPage::OnApply()
{
ASSERT_VALID(this);

OnOK();
return TRUE;
}

很显然, MFC的属性页默认是通过OnApply调用OnOK的, 那么重载OnApply函数, 通过该函数的返回值就可以决定属性页是否自动关闭了.

最终, 代码写成这样:

BOOL CMovieIssueBasicPage::OnApply()
{

if(!IsDataAvailably())
return FALSE;

return TRUE;

}

要想了解函数调用的前前后后, 看Call Stack尤为重要.

3.
发信人: Quaful (夸父), 信区: NewSoftware
标 题: [系统补丁]修复MFC对话框/属性页文字显示不全的BUG
发信站: BBS 水木清华站 (Sat Sep 11 14:52:24 2004), 站内

很多用MFC编写的应用程序,特别是英文的应用程序,如NOD32,还有Visual Studio 6.0自身,在中文Windows下,对话框中的文字都有可能显示不全,文字超出了边框范围,其效果如附件中的图BeforePatch.png。

cygwin指出了这是MFC的一个bug,不过cygwin提出的修改仅解决了对话框的问题,属性页仍然有同样的问题。这里发布的补丁两者的问题都可以解决。使用补丁后的效果如附件中的图AfterPatch.png。

补丁的原理见NewSoftClub版9732文。cygwin提出的补丁方案见NewSoftClub版9607文。

补丁的使用方法:

你可以根据补丁的原理自己编译一个新的MFC42.DLL(Unicode版本是MFC42U.DLL)。如果你没有VS6,那么也可以用我编译好的文件:下载附件中的MFC42.rar文件,将里面的文件解压缩到有问题的应用程序的目录。例如,NOD32在C:\Program Files\ESET目录下,就把rar解压缩到这个目录下。重新启动NOD32就可以生效。

http://proxy.smth.edu.cn:8000/bbscon.php?ftype=0&bid=99&id=524234&ap=195416

--

Two famous quotes:

The world will never need more than five computers (Thomas Watson, IBM).
640K memory is more than will ever be needed (Bill Gates, Microsoft).


※ 来源:·BBS 水木清华站 http://smth.org·[FROM: 166.111.62.*]


发信人: cygwin (毕业了吧☆滚蛋了吧), 信区: MSDN
标 题: 也谈修复MFC导致对话框/属性页文字显示不全BUG
发信站: BBS 水木清华站 (Fri Sep 17 16:11:09 2004), 站内

【 以下文字转载自 NewSoftClub 讨论区 】
发信人: cygwin (毕业了吧☆滚蛋了吧), 信区: NewSoftClub
标 题: 也谈修复MFC导致对话框/属性页文字显示不全BUG
发信站: BBS 水木清华站 (Fri Sep 17 16:10:02 2004), 站内

在中文系统下,经常有的英文软件字体显示不全。
经过研究这是由于mfc的BUG导致的。
我曾经在 smth\msdn 指出过这个bug:

MFC\SRC\dlgcore.cpp:

// On DBCS systems, also change "MS Sans Serif" or "Helv" to system font.
if ((!bSetSysFont) && GetSystemMetrics(SM_DBCSENABLED))
{
bSetSysFont = (strFace == _T("MS Shell Dlg") ||
strFace == _T("MS Sans Serif") || strFace == _T("Helv"));
if (bSetSysFont && (wSize == 8))
wSize = 0;
}

if (bSetSysFont)
{
CDialogTemplate dlgTemp(lpDialogTemplate);
dlgTemp.SetSystemFont(wSize); //到此处即删除了对话框中原有的字体。
hTemplate = dlgTemp.Detach();
}
改掉后,对话框的字体就好了,但是我当时没有深入研究此问题。
quaful@smth 发现如法修改后,属性页(property page)的字体还是不对。
并且提出这是另一段代码出的问题:
MFC\SRC\ccdata.cpp :
BOOL AFXAPI AfxGetPropSheetFont(CString& strFace, WORD& wSize, BOOL bWizard)。。。
quaful的解决方案是在这个函数里强制把字体指定为MS Shell Dlg
但这并不一定好,因为资源中本来是有字体的,那个字体不一定是MS Shell Dlg
引用到这个函数的有:
mfc\src\ctlppg.cpp:
if (bSetSysFont && AfxGetPropSheetFont(strFace, wSize, FALSE))
dt.SetFont(strFace, wSize);
dlgprop.cpp:
AFX_STATIC DLGTEMPLATE* AFXAPI
_AfxChangePropPageFont(const DLGTEMPLATE* pTemplate, BOOL bWizard)
{
CString strFaceDefault;
WORD wSizeDefault;

if (!AfxGetPropSheetFont(strFaceDefault, wSizeDefault, bWizard))
return NULL;
...
}
...
HGLOBAL hTemplate = _AfxChangePropPageFont(pTemplate, bWizard);
...
if (hTemplate != NULL)
{
pTemplate = (LPCDLGTEMPLATE)hTemplate;
m_hDialogTemplate = hTemplate;
}
可以看出,最简单的办法是让AfxGetPropSheetFont返回FALSE.

quaful的修改方式是重编一份,放到对应的可持行文件目录,这不失为一个好方法,
但是如果这样的可持行文件一多了就要到处放,能不能有个简单的方法呢?

一种修改的方案是,对于代码中指定要删除的三个字体,可以查找字符串,将字符串替换成别的。
比如?? Shell Dlg。对于AfxGetPropSheetFont,可以让它返回false,因为它带三个参数,最小修改代码为
五字节,即在它入口处写入 33 c0 c2 0c 00相应汇编代码为
xor eax,eax
retn 0ch

然后,用什么样的方式来动态修改呢?直接改可持行文件?问题就在于这个文件受系统保护,
要是一起改了system32中的和dllcache中的,windows文件保护就会跳出来,为此废掉文件
保护也不值得。另外,一般发布的软件都喜欢自带mfc42.dll,如果要改文件就到处要改,麻烦。
如果注入dll改远程process,那也不方便。

常常想,要是系统load完指定的一个dll后,就给发个通知就好了。然而系统没有这样一个功能,
不过,很自然的想到,为什么不在loadlibrary上做动作呢?

如是方案就出来了,可以写个KMD驱动,在内核态hook住loadlibrary必然要经过的一个系统服务,
然后在那里判断修改不就好了?

查源码知loadlibrary会调用NtOpenFile,NtCreateSection,NtMapViewOfSection,NtClose等函数。
最好下手的地方自然就是NtMapViewOfSection.

然后是如何hook系统服务,现在网上这样的代码很多,一般的方式都是得到
KeServiceDescriptorTable和KeServiceDescriptorTableShadow然后改掉其中某个表项。
然而,为何不直接hook目标例程呢?改那个表就好像在user模式下改可持行文件的引入表,
而ms提供的detours却能直接hook目标而不是只改引入表。要是在kernel mode下也能用
detours就好了。

事实上这一点不难做到,把detours中的相关api调用删掉,它就能用在内核模式下了。

注意到ntdll的NtMapViewOfSection通过系统服务表后最终调用ntoskrnl的NtMapViewOfSection
如是便用detours hook这个东西。

接下来的工作就简单了,分析可持行文件格式看它是不是mfc的dll,是就用在数据节里找字体名称把它改
掉,通过输出表找到AfxGetPropSheetFont把它前五字节改掉。

源代码太长,就不贴了。

--

※ 来源:·BBS 水木清华站 [FROM: 219.133.40.*]

发信人: cygwin (毕业了吧☆滚蛋了吧), 信区: MSDN
标 题: Re: 也谈修复MFC导致对话框/属性页文字显示不全BUG (转载)
发信站: BBS 水木清华站 (Sat Sep 18 17:22:57 2004), 站内


思路你不要,冗长而复杂的代码你倒想要
给点片断吧:

NTSTATUS DriverDispatch( IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
KdPrint(("DriverDispatch\n"));

Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest (Irp,IO_NO_INCREMENT);

return Irp->IoStatus.Status;
}
/* > */
PBYTE g_pbTrampoline = 0;
PBYTE g_pbDetour = 0;
DWORD g_ServiceNoZwProtectVM = 0;

void NTAPI ForceUseParam(LPVOID par);

/*< DriverUnload */
void DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
__asm {cli};
DetourRemove(g_pbTrampoline,g_pbDetour);
__asm {sti};

KdPrint(("DriverUnload\n"));
}
/* > */
#define DECL(TYPE,NAME,X) NAME = (TYPE*)(void*)(X);
.....

void HookDll(PBYTE exeInst,SIZE_T viewSize)
{
IMAGE_DOS_HEADER * DosHeader;
IMAGE_NT_HEADERS * NtHeader;
DWORD expTableVA;
IMAGE_EXPORT_DIRECTORY * expDir;
DWORD * Funcs;

PBYTE dllname;
PBYTE exeUp;

DECL(IMAGE_DOS_HEADER, DosHeader,exeInst);
DECL(IMAGE_NT_HEADERS, NtHeader, exeInst + DosHeader->e_lfanew);
if( (PBYTE)NtHeader > exeInst+viewSize ) return;
if(NtHeader->Signature != 0x00004550 ) return;
if(NtHeader->FileHeader.Machine != IMAGE_FILE_MACHINE_I386) return;
if(NtHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC)return;

expTableVA = NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress;
if(expTableVA == 0) return; //there is no export table.

DECL(IMAGE_EXPORT_DIRECTORY,expDir,expTableVA + exeInst);
if( (PBYTE)expDir > viewSize+exeInst )return;
dllname = (PBYTE)(expDir->Name + exeInst);
exeUp = NtHeader->OptionalHeader.SizeOfImage + exeInst;
if(exeUp > exeInst + viewSize)return;

if(dllname > exeUp || dllname < exeInst || dllname[0] == 0) return;

/*
./Intel/MFC42.DEF: ?AfxGetPropSheetFont@@YGHAAVCString@@AAGH@Z @ 1172 NONAME
./Intel/MFC42D.DEF: ?AfxGetPropSheetFont@@YGHAAVCString@@AAGH@Z @ 1104 NONAME
./Intel/MFC42U.DEF: ?AfxGetPropSheetFont@@YGHAAVCString@@AAGH@Z @ 1169 NONAME
./Intel/MFC42UD.DEF: ?AfxGetPropSheetFont@@YGHAAVCString@@AAGH@Z @ 1102 NONAME
*/
/*< mfc42.dll */
if(stricmp(dllname,"MFC42.dll") == 0)
{
PBYTE Target;
DWORD nSec,oldProtect;
const ULONG Size = 5;
ULONG _Size;
PBYTE _Target;
IMAGE_SECTION_HEADER * secs;
IMAGE_SECTION_HEADER emptySection;
DWORD dw;

ForceUseParam(&Target);
ForceUseParam(&_Target);
ForceUseParam(&_Size);
ForceUseParam(&oldProtect);
//OK. we found it.
DECL(DWORD,Funcs,expDir->AddressOfFunctions+exeInst);
Target = Funcs[1172 - expDir->Base] + exeInst;
_Target = Target;
_Size = Size;
ZwProtectVirtualMemory((HANDLE)~0,(PVOID*)&_Target,&_Size,PAGE_EXECUTE_READWRITE,&oldProtect);

RtlCopyMemory(Target,"\x33\xC0\xC2\x0C\x00",5);

_Target = Target;
_Size = Size;
ZwProtectVirtualMemory((HANDLE)~0,(PVOID*)&_Target,&_Size,oldProtect,&oldProtect);

//TODO: replace strings.
nSec = NtHeader->FileHeader.NumberOfSections;
DECL(IMAGE_SECTION_HEADER,secs,(DWORD)NtHeader+sizeof(IMAGE_NT_HEADERS));
memset(&emptySection,0,sizeof(emptySection));

for(dw=0;dw< /**/ nSec;++dw)
{
DWORD dw2;

if(memcmp(secs+dw,&emptySection,sizeof(IMAGE_SECTION_HEADER)) == 0)
break;
if(strnicmp((const char*)secs[dw].Name,".rdata",6) == 0)
{
LPBYTE ps = exeInst + secs[dw].VirtualAddress, _ps = ps;
DWORD nb = secs[dw].SizeOfRawData,dw2,_nb = nb;

for(dw2 = 0;dw2 < nb; ++dw2)
{
/*
bSetSysFont = (strFace == _T("MS Shell Dlg") ||
strFace == _T("MS Sans Serif") || strFace == _T("Helv"));
*/
if(strcmp((LPCSTR)ps+dw2,"MS Shell Dlg") == 0 ||
strcmp((LPCSTR)ps+dw2,"MS Sans Serif") == 0 ||
strcmp((LPCSTR)ps+dw2,"Helv") == 0 )
{
LPBYTE t,ft;
ULONG sz = 2;

ForceUseParam(&ft);
ForceUseParam(&sz);
ForceUseParam(&oldProtect);

t = ps+dw2;
ft = t;
ZwProtectVirtualMemory((HANDLE)~0,(PVOID*)&ft,&sz,PAGE_EXECUTE_READWRITE,&oldProtect);
RtlCopyMemory(t,"??",2);

ft = t;
sz = 2;
ZwProtectVirtualMemory((HANDLE)~0,(PVOID*)&ft,&sz,oldProtect,&oldProtect);
}
}
}
}

KdPrint(("addr=%p Target=%p %02X %02X",exeInst,Target,Target[0],Target[1]));
}
/* > */
/*< mfc42u.dll */
else if(stricmp(dllname,"MFC42U.dll") == 0)
{
......................
}
}
/*< hooked ntmapviewofsection */
DETOUR_IMPLEMENT_ENTRY(NTSTATUS __stdcall,NtMapViewOfSection,(
IN HANDLE SectionHandle,
IN HANDLE ProcessHandle,
IN OUT PVOID *BaseAddress,
IN ULONG ZeroBits,
IN ULONG CommitSize,
IN OUT PLARGE_INTEGER SectionOffset OPTIONAL,
IN OUT PSIZE_T ViewSize,
IN SECTION_INHERIT InheritDisposition,
IN ULONG AllocationType,
IN ULONG Protect
))
{
NTSTATUS status;

status = trampoline_NtMapViewOfSection(SectionHandle,ProcessHandle,BaseAddress,ZeroBits,CommitSize,
SectionOffset,ViewSize,InheritDisposition,AllocationType,Protect);
if( NT_SUCCESS(status) && BaseAddress && *BaseAddress && ViewSize && *ViewSize)
{
PBYTE addr = (PBYTE)*BaseAddress;
if( Protect != PAGE_READWRITE )
{
}
else if( ((DWORD)addr & 0x7fff0000) == (DWORD)addr /* && MmIsAddressValid(addr)*/)
{
int step = 0;
__try
{
++ step;
if(addr[0] == 'M' && addr[1] == 'Z')
{
++ step;
HookDll(addr,*ViewSize);
}
}
__except (1)
{
KdPrint(("Addr %p[%#x] is non readable step=%d",addr,*ViewSize,step));
}
}
}
else
{
}
return status;
}
/* > */
#pragma code_seg("INIT")
/*< DriverEntry */

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath)
{
NTSTATUS status;
HANDLE hNtdll,hZwProtectVirtualMemory;

status = NTKPGetModuleHandle("NTDLL.DLL",&hNtdll,NULL);
if(! NT_SUCCESS(status) ) return status;
status = NTKPGetProcAddress(hNtdll,"ZwProtectVirtualMemory",&hZwProtectVirtualMemory);
if(! NT_SUCCESS(status) ) return status;
KdPrint(("ZwProtectVirtualMemory = %p Ntdll=%p\n",hZwProtectVirtualMemory,hNtdll));
g_ServiceNoZwProtectVM = *(PDWORD)((DWORD)hZwProtectVirtualMemory + 1);
KdPrint(("service no=%x\n",g_ServiceNoZwProtectVM));

g_pbTrampoline = _GetFinalPos((PBYTE)trampoline_NtMapViewOfSection);
g_pbDetour = _GetFinalPos((PBYTE)my_NtMapViewOfSection);

DriverObject->MajorFunction[IRP_MJ_CREATE] =
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DriverDispatch;
DriverObject->DriverUnload = DriverUnload;

__asm { cli };
DetourFunctionWithTrampoline(g_pbTrampoline,g_pbDetour);
__asm { sti };

return STATUS_SUCCESS;
}

【 在 RoachCock (chen3feng~shuaigment) 的大作中提到: 】
: 没有代码

--

※ 来源:·BBS 水木清华站 smth.org·[FROM: 219.133.40.*]


发信人: rogerz (章鱼·Bless Kloy & redair), 信区: NewSoftware
标 题: 也谈修复MFC导致对话框/属性页文字显示不全BUG
发信站: BBS 水木清华站 (Fri Sep 17 17:35:18 2004), 站内

【 以下文字转载自 NewSoftClub 讨论区 】
发信人: cygwin (毕业了吧☆滚蛋了吧), 信区: NewSoftClub
标 题: 也谈修复MFC导致对话框/属性页文字显示不全BUG
发信站: BBS 水木清华站 (Fri Sep 17 16:10:02 2004), 站内

在中文系统下,经常有的英文软件字体显示不全。
经过研究这是由于mfc的BUG导致的。
我曾经在 smth\msdn 指出过这个bug:

MFC\SRC\dlgcore.cpp:

// On DBCS systems, also change "MS Sans Serif" or "Helv" to system font.
if ((!bSetSysFont) && GetSystemMetrics(SM_DBCSENABLED))
{
bSetSysFont = (strFace == _T("MS Shell Dlg") ||
strFace == _T("MS Sans Serif") || strFace == _T("Helv"));
if (bSetSysFont && (wSize == 8))
wSize = 0;
}

if (bSetSysFont)
{
CDialogTemplate dlgTemp(lpDialogTemplate);
dlgTemp.SetSystemFont(wSize); //到此处即删除了对话框中原有的字体。
hTemplate = dlgTemp.Detach();
}
改掉后,对话框的字体就好了,但是我当时没有深入研究此问题。
quaful@smth发现如法修改后,属性页(property page)的字体还是不对。
并且提出这是另一段代码出的问题:
MFC\SRC\ccdata.cpp :
BOOL AFXAPI AfxGetPropSheetFont(CString& strFace, WORD& wSize, BOOL bWizard)。。。
quaful的解决方案是在这个函数里强制把字体指定为MS Shell Dlg
但这并不一定好,因为资源中本来是有字体的,那个字体不一定是MS Shell Dlg
引用到这个函数的有:
mfc\src\ctlppg.cpp:
if (bSetSysFont && AfxGetPropSheetFont(strFace, wSize, FALSE))
dt.SetFont(strFace, wSize);
dlgprop.cpp:
AFX_STATIC DLGTEMPLATE* AFXAPI
_AfxChangePropPageFont(const DLGTEMPLATE* pTemplate, BOOL bWizard)
{
CString strFaceDefault;
WORD wSizeDefault;

if (!AfxGetPropSheetFont(strFaceDefault, wSizeDefault, bWizard))
return NULL;
...
}
...
HGLOBAL hTemplate = _AfxChangePropPageFont(pTemplate, bWizard);
...
if (hTemplate != NULL)
{
pTemplate = (LPCDLGTEMPLATE)hTemplate;
m_hDialogTemplate = hTemplate;
}
可以看出,最简单的办法是让AfxGetPropSheetFont返回FALSE.

quaful的修改方式是重编一份,放到对应的可持行文件目录,这不失为一个好方法,
但是如果这样的可持行文件一多了就要到处放,能不能有个简单的方法呢?

一种修改的方案是,对于代码中指定要删除的三个字体,可以查找字符串,将字符串替换成别的。
比如?? Shell Dlg。对于AfxGetPropSheetFont,可以让它返回false,因为它带三个参数,最小修改代码为
五字节,即在它入口处写入 33 c0 c2 0c 00相应汇编代码为
xor eax,eax
retn 0ch

然后,用什么样的方式来动态修改呢?直接改可持行文件?问题就在于这个文件受系统保护,
要是一起改了system32中的和dllcache中的,windows文件保护就会跳出来,为此废掉文件
保护也不值得。另外,一般发布的软件都喜欢自带mfc42.dll,如果要改文件就到处要改,麻烦。
如果注入dll改远程process,那也不方便。

常常想,要是系统load完指定的一个dll后,就给发个通知就好了。然而系统没有这样一个功能,
不过,很自然的想到,为什么不在loadlibrary上做动作呢?

如是方案就出来了,可以写个KMD驱动,在内核态hook住loadlibrary必然要经过的一个系统服务,
然后在那里判断修改不就好了?

查源码知loadlibrary会调用NtOpenFile,NtCreateSection,NtMapViewOfSection,NtClose等函数。
最好下手的地方自然就是NtMapViewOfSection.

然后是如何hook系统服务,现在网上这样的代码很多,一般的方式都是得到
KeServiceDescriptorTable和KeServiceDescriptorTableShadow然后改掉其中某个表项。
然而,为何不直接hook目标例程呢?改那个表就好像在user模式下改可持行文件的引入表,
而ms提供的detours却能直接hook目标而不是只改引入表。要是在kernel mode下也能用
detours就好了。

事实上这一点不难做到,把detours中的相关api调用删掉,它就能用在内核模式下了。

注意到ntdll的NtMapViewOfSection通过系统服务表后最终调用ntoskrnl的NtMapViewOfSection
如是便用detours hook这个东西。

接下来的工作就简单了,分析可持行文件格式看它是不是mfc的dll,是就用在数据节里找字体名称把它改
掉,通过输出表找到AfxGetPropSheetFont把它前五字节改掉。

源代码太长,就不贴了。

--

※ 来源:·BBS 水木清华站 http://smth.org·[FROM: 219.133.40.*]



<< Home

This page is powered by Blogger. Isn't yours?