11/20/2003

使用虚列表和自画实现文件夹的缩略图显示

本示例演示了列表控件的虚列表和自画功能,也演示了一些系统外壳的函数和接口的使用方法。

单击 这里 下载本文的代码。如果在编译示例程序的时候出现问题,你需要去http://www.microsoft.com/msdownload/platformsdk/sdkupdate/ 升级你的头文件和库文件

预备性阅读

在阅读本文之前,建议先对列表视图控件和系统外壳有一个基本的了解。建议阅读以下SDK文章

创建应用程序

使用MFC应用程序向导创建一个SDI应用程序,在最后一步选择视图的基类为CListView。创建完成之后,在资源中去掉保存、编辑和打印等功能的菜单和工具栏按钮(因为这些功能没有实现)。

虚列表的创建

本文采用虚列表技术,使得显示信息是在第一次显示的时候才被获取。为了创建虚列表,在创建之前需要指定列表的风格

BOOL CPicViewView::PreCreateWindow(CREATESTRUCT& cs)
{
    cs.style&=~LVS_TYPEMASK;
    cs.style|=LVS_ICON|LVS_OWNERDATA;
    return CListView::PreCreateWindow(cs);
}
同时,因为列表项的Overlay图标也是被动态获取的,所以需要设置动态Overlay图标

void CPicViewView::OnInitialUpdate()
{
    CListView::OnInitialUpdate();
    GetListCtrl().SetCallbackMask(LVIS_OVERLAYMASK);
}

缓存显示信息

在列表需要显示一个范围的项目之前,列表会发送LVN_ODCACHEHINT通知,应用程序可以捕获这个消息来缓存部分列表的显示信息,以提高性能。

void CPicViewView::OnOdcachehint(NMHDR* pNMHDR, LRESULT* pResult)
{
    NMLVCACHEHINT* pCacheHint = (NMLVCACHEHINT*)pNMHDR;
    PrepCache(0,min(5,m_arpFolderItems.GetSize()));
    PrepCache(pCacheHint->iFrom,pCacheHint->iTo);
    PrepCache(max(0,m_arpFolderItems.GetSize()-5),m_arpFolderItems.GetSize());
    *pResult = 0;
}
在列表需要显示一个项目之前,列表会发送LVN_GETDISPINFO通知,应用程序可以捕获这个消息来提供项目的显示信息。如果显示时需要显示的列表项在缓存中,那么可以从缓存中获取显示信息。否则需要重新从文件获得。

void CPicViewView::OnGetdispinfo(NMHDR* pNMHDR, LRESULT* pResult)
{
    LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
    if(pDispInfo->item.iItem==-1)return;
    HRESULT hr=S_OK;
    LPCITEMIDLIST pidlItem=m_arpFolderItems[pDispInfo->item.iItem];
    CFolderItemInfo* pFolderItemInfo=FindItemInCache(pidlItem);
    BOOL bCached=TRUE;
    if(pFolderItemInfo==NULL){
        bCached=FALSE;
        pFolderItemInfo=new CFolderItemInfo;
        GetItemInfo(pidlItem,pFolderItemInfo);
    }
    if(pDispInfo->item.mask&LVIF_TEXT){
        lstrcpyn(pDispInfo->item.pszText,pFolderItemInfo->tszDisplayName,pDispInfo-   >item.cchTextMax);
    }
    if(pDispInfo->item.mask&LVIF_IMAGE){
        pDispInfo->item.iImage=pFolderItemInfo->iIcon;
    }
    if(pDispInfo->item.mask&LVIF_STATE){
        pDispInfo->item.state=pFolderItemInfo->state;
    }
    if(!bCached)
        delete pFolderItemInfo;
    *pResult = 0;
}


文件图标的显示

默认情况下,列表项的图标就是其系统图标。首先获得系统图像列表

int CPicViewView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CListView::OnCreate(lpCreateStruct) == -1)
        return -1;
    HRESULT hr = SHGetMalloc(&m_pMalloc); if(FAILED(hr)) return -1;
    hr = SHGetDesktopFolder(&m_psfDesktop);if(FAILED(hr)) return -1;
    SHFILEINFO shfi;
    ZeroMemory(&shfi,sizeof(SHFILEINFO));
    HIMAGELIST hi=(HIMAGELIST)SHGetFileInfo(NULL,0,&shfi,sizeof(SHFILEINFO),SHGFI_ICON |SHGFI_SYSICONINDEX|SHGFI_SMALLICON);
    GetListCtrl().SetImageList(CImageList::FromHandle(hi),LVSIL_SMALL);
    hi=(HIMAGELIST)SHGetFileInfo(NULL,0,&shfi,sizeof(SHFILEINFO),SHGFI_ICON |SHGFI_SYSICONINDEX|SHGFI_LARGEICON);
    GetListCtrl().SetImageList(CImageList::FromHandle(hi),LVSIL_NORMAL);
    return 0;
}

然后在获取文件信息时,从文件获得其图标在系统图像列表中的索引。

如果列表项是图像文件,并且从文件成功载入图像,那么使用自画功能以替换默认的图标。

void CPicViewView::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
    LPNMLVCUSTOMDRAW lpNMCustomDraw = (LPNMLVCUSTOMDRAW) pNMHDR;
    switch(lpNMCustomDraw ->nmcd.dwDrawStage) {
        case CDDS_PREPAINT : *pResult=CDRF_NOTIFYITEMDRAW;return;
        case CDDS_ITEMPREPAINT:*pResult=CDRF_NOTIFYPOSTPAINT;return;
        case CDDS_ITEMPOSTPAINT:
        {
            int iItem=lpNMCustomDraw ->nmcd.dwItemSpec;
            if(iItem==-1){
                *pResult=CDRF_DODEFAULT;return;
            }
            CFolderItemInfo* pItemInfo=FindItemInCache(m_arpFolderItems[iItem]);
            if(pItemInfo==NULL||pItemInfo->bFailLoadPic||pItemInfo->pic.m_pPict==NULL){
                *pResult=CDRF_DODEFAULT;return;
            }
            CRect rectIcon;
            GetListCtrl().GetItemRect(iItem,&rectIcon,LVIR_ICON);
            CDC* pDC=CDC::FromHandle(lpNMCustomDraw->nmcd.hdc);
            pItemInfo->pic.Render(pDC,rectIcon,rectIcon);
        }
        *pResult=CDRF_NEWFONT;return;
    }
    * pResult=0;
}

上面的代码是使用获取的文件显示信息中的图像,在列表项图标的区域画图。

获取显示信息

为了缓存列表项的显示信息,或者显示列表项,需要获取列表项的文字、图标、Overlay图标和缩略图等信息。这里使用了ILCombine来把缓存中的相对PIDL转化为完整的Pidl,再据此获得文件的完整路径,然后调用OleLoadPicturePath函数载入图像。

void CPicViewView::GetItemInfo(LPCITEMIDLIST pidl,CFolderItemInfo* pItemInfo)
{
    HRESULT hr = theApp.SHGetDisplayNameOf(pidl,pItemInfo->tszDisplayName);
    IShellIcon* pShellIcon=NULL;
    hr=m_psfFolder->QueryInterface(IID_IShellIcon,(LPVOID*)&pShellIcon);
    if (SUCCEEDED(hr)&&pShellIcon){
        pShellIcon->GetIconOf(pidl,0,&pItemInfo->iIcon);
        pShellIcon->Release();
    }
    IShellIconOverlay* pShellIconOverlay =NULL;
    hr=m_psfFolder->QueryInterface(IID_IShellIconOverlay,(LPVOID*)&pShellIconOverlay);
    if (SUCCEEDED(hr)&&pShellIconOverlay){
        int nOverlay=0;
        pShellIconOverlay->GetOverlayIndex(pidl,&nOverlay);
        pItemInfo->state=INDEXTOOVERLAYMASK (nOverlay);
        pShellIconOverlay->Release();
    }
    LPITEMIDLIST pidlItemFull=ILCombine(m_pidlFolder,pidl);
        if(pidlItemFull){
            if(SHGetPathFromIDList(pidlItemFull,pItemInfo->tszPath)){
                USES_CONVERSION;
                hr=OleLoadPicturePath(
                    T2OLE(pItemInfo->tszPath)
                    ,NULL,0,RGB(255,255,255)
                    ,IID_IPicture,(LPVOID*)&pItemInfo->pic.m_pPict);
            if(FAILED(hr)){
                    pItemInfo->bFailLoadPic=TRUE;
                    TRACE("OleLoadPicturePath failed %s\r\n",pItemInfo->tszPath);
                }
            }
        }
        m_pMalloc->Free(pidlItemFull);
    }
}

缓存目录的数据

在更改目录时,需要重建目录内容的缓存。这包括目录的pidl和IShellFolder接口指针,目录内容的相对pidl,以及列表项的显示信息(基于性能上的考虑,列表项的显示信息是在接收到LVN_ODCACHEHINT通知的时候缓存的)。

LPITEMIDLIST m_pidlFolder;
IShellFolder * m_psfFolder;
CTypedPtrArray<CPtrArray,LPITEMIDLIST> m_arpFolderItems;
CTypedPtrMap<CMapPtrToPtr,LPITEMIDLIST,CFolderItemInfo*> m_mapCache;
 

void CPicViewView::EnterFolder(LPCITEMIDLIST pidl)
{
    USES_CONVERSION;
    m_pidlFolder=ILClone(pidl);
    if(m_pidlFolder){
        LPENUMIDLIST ppenum = NULL;
        LPITEMIDLIST pidlItems = NULL;
        ULONG celtFetched;
        HRESULT hr;
        hr = m_psfDesktop->BindToObject(m_pidlFolder, NULL, IID_IShellFolder, (LPVOID *) &m_psfFolder);
        if(SUCCEEDED(hr)){
            hr = m_psfFolder->EnumObjects(NULL,SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &ppenum);
            if(SUCCEEDED(hr)){
                while( hr = ppenum->Next(1,&pidlItems, &celtFetched) == S_OK && (celtFetched) == 1){
                    m_arpFolderItems.Add(pidlItems);
                    }
            }
        }
        GetListCtrl().SetItemCount(m_arpFolderItems.GetSize());
    }
}

 

打开文件夹

本应用程序显示文件夹的内容而不是显示文档的内容,所以我重载了打开文件时的处理,显示目录选择对话框而不是文件打开对话框。

void CPicViewApp::OnFileOpen()
{
    TCHAR tszDisplayName[_MAX_PATH];
    TCHAR tszPathSelected[_MAX_PATH];
    LPITEMIDLIST pidlSelected=PidlBrowse(m_pMainWnd->GetSafeHwnd(),0,tszDisplayName);
    if(pidlSelected){
        if(SHGetPathFromIDList(pidlSelected,tszPathSelected)){
            CDocument* pDocument=OpenDocumentFile(tszPathSelected);
            pDocument->SetTitle(tszDisplayName);
            ILFree(pidlSelected);
        }
    }
}

注意从外壳调用获得的PIDL一般都需要调用ILFree或者IMalloc::Free释放。一个例外是调用函数SHBindToParent获得的相对pidl,因为它是输入的参数完整pidl的一部分,所以不必另外释放。

在新建或者打开“文件”时候,文档需要通知视图当前文件夹的更改,这是通过调用CDocument::UpdateAllViews和重载CView::OnUpdate实现的。视图对这个通知的处理是清除上一个目录的缓存数据,缓存新目录的数据,以及更新文档标题。
 

打开文件或者目录

为了使用方便,双击列表项时可以在同一窗口打开子目录,或者调用系统的默认处理程序打开文件。如果文件是快捷方式,那么打开快捷方式的目标。

void CPicViewView::OnDblclk(NMHDR* pNMHDR, LRESULT* pResult)
{
    LPNMLISTVIEW lpnm=(LPNMLISTVIEW)pNMHDR;
    if(lpnm->iItem==-1)return;
    *pResult = 0;
    HRESULT hr=S_OK;
    LPCITEMIDLIST pidlItem=m_arpFolderItems[lpnm->iItem];
    LPITEMIDLIST pidlItemFull=ILCombine(m_pidlFolder,pidlItem);
    LPITEMIDLIST pidlItemTarget=NULL;
    hr=theApp.SHGetTargetFolderIDList(pidlItemFull,&pidlItemTarget);
    if(pidlItemTarget){
        if(theApp.ILIsFolder(pidlItemTarget)){
            CFolderChange FolderChange;
            FolderChange.m_pidlFolder=pidlItemTarget;
            OnFolderChange(&FolderChange);
        }
        else{
            SHELLEXECUTEINFO ShExecInfo;
            ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
            ShExecInfo.fMask = SEE_MASK_IDLIST;
            ShExecInfo.hwnd = NULL;
            ShExecInfo.lpVerb = NULL;
            ShExecInfo.lpFile = NULL;
            ShExecInfo.lpIDList= pidlItemTarget;
            ShExecInfo.lpParameters = NULL;
            ShExecInfo.lpDirectory = NULL;
            ShExecInfo.nShow = SW_MAXIMIZE;
            ShExecInfo.hInstApp = NULL;
            ShellExecuteEx(&ShExecInfo);
        }
        m_pMalloc->Free(pidlItemTarget);
        m_pMalloc->Free(pidlItemFull);
    }
}
 

性能的优化

为了更好的用户体验,可以使用自定义的图标大小(这需要完全自行绘制列表项的图标区域),用单独的线程来载入图像,或者使用调整到图标大小的缩略图缓冲(这样每次绘制时不必拉伸图像)。但是这超出了本文的范围。有兴趣的读者可以自己试一下。

参考

需要更多信息的话,可以参考

11/09/2003

浏览器集成教学--在浏览器程序中添加宏支持

浏览器集成教学--在浏览器程序中添加宏支持

这个教程提供在浏览器程序中添加宏支持的方法,你会看到如何给MFC的程序添加宏支持。这篇文章也讨论了如何扩展VC6中的CHtmlView的功能,如何实现MDI结构的浏览器,以及如何分析DHTML的文档结构。

单击这里下载本文的代码

本文分为以下部分

  1. 前提和需求
  2. 介绍
  3. 活动脚本
  4. 为应用程序添加脚本支持
  5. 安全性
  6. CHtmlView的增强
  7. 脚本示例
  8. 结论
  9. 参考

前提和需求

在阅读本文之前,建议先

  • 对微软基础类(MFC)和组件对象模型(COM)有所了解
  • 熟悉活动模板库(ATL)
  • 安装了微软的因特网探索者(IE)6.0或者更高版本
  • 开发环境中具有IE6.0或者更高版本的头文件和库文件

介绍

集成浏览器控件对于快速的应用程序开发(RAD)是一个强有力的工具;你可以使用动态HTML(DHTML),或者可扩展的标记语言(XML)显示你的用户界面。一个通常的用途是用它来显示表单,然后通过分析表单网页和处理递交事件来处理表单。但是,如果你要分析表单页面的话,分析的方式完全依赖于页面的结构,也就是说,如果在应用程序中通过IE提供的接口分析网页,那么为了每个网页结构,你要编写和编译一次代码。这在应用程序和表单网页一起发布的情况下是完全没有问题的,但是对于表单网页位于远程服务器上,并且有时候会修改的情况,或者想使应用程序对其他网站有效,就必须同时修改并且重新发布应用程序。为了避免反复修改应用程序,可以使用——

活动脚本

使用活动脚本可以编写灵活的处理代码而无需重新编译程序。你可能已经在很多应用程序中见到过活动脚本,例如IE、Microsoft Office和Visual Studio。在平台SDK(Platform SDK)的微软窗口脚本技术(Microsoft Windows Script Technologies)部分的微软窗口脚本接口介绍(Microsoft Windows Script Interfaces Introduction)一文中,介绍了活动脚本的概念、背景、架构和调用步骤。

如果需要示例代码,可以在微软知识库(KB)中查找kbAXScript关键字。下面是一些示例

本文的示例代码是基于MFC6的,所以采用了Q168214中提供的代码。

为应用程序添加脚本支持

自动化对象

为了实现脚本支持,我们需要让应用程序具有自动化服务器支持。实现这个支持的最简单的办法是使用MFC的应用程序向导(Application Wizard)创建IEAutomation MDI应用程序时,在MDI向导第三步选中Automation支持。

向导自动产生的CHtmlView派生类CIEAutomationView并不是一个自动化对象,所以在建立示例工程时,我把CIEAutomationView的定义和实现文件删除,然后删除类向导中CIEAutomationView的信息,重新创建CIEAutomationView类,在创建的时候指定基类是CHtmlView并且支持自动化。

CIEAutomationView中的脚本解释器是从Q168214的示例代码修改的,去掉了一些对象,增加了DOM扩展对象的实现。

Scripter对象

脚本引擎对象,可以用名字Scripter访问。提供创建对象的方法。

WebBrowser对象

浏览器控件对象,可以用名字WebBrowser访问。可以用来访问文档对象模型。查看源代码功能也被增强了以显示文档对象模型。需要更多信息的话,可以查看文末的参考。

External对象

DOM扩展对象,可以用名字External访问。用于扩展浏览器的文档对象模型的对象。在本示例中,我也同时用这个对象转发了WebBrowser对象的事件。尽管大部分功能都实现了,但是自动完成功能似乎还有点问题,看起来和IShellUIHelper的未公开方法AutoCompleteAttatch有关。(实际上,浏览器控件需要实现IDocHostUIHandler::GetHostInfo,参考http://blog.joycode.com/jiangsheng/archive/2004/01/08/11037.aspx

类型库支持

脚本中需要对象的类型库信息来访问对象的属性、方法和事件。默认情况下,直接从CComTarget派生的类是无法通过类向导添加和删除事件的,CComTargetEx类“模拟”了ActiveX的部分特性,并且欺骗了类向导来做到这一点。在给对象添加类型信息时,参考了Q185720 HOWTO: Provide Type Information From an MFC Automation Server中的方法,把应用程序的类型库添加到资源。

安全性

尽管自动化浏览器可以提供更多的灵活性,但是这也把应用程序的一部分暴露给用户。例如,用户可能修改脚本使得应用程序不能正常工作。另外,如果用户可以查看脚本,那么就可以了解程序结构,并且能够借此攻击没有慎重设计的站点。

使用脚本创建对象也可能有安全性问题。某些对象不是安全的,例如恶意的或者不正确使用的COM对象。

如果扩展了DOM,使得网页上的脚本可以访问应用程序的功能,那么需要确保脚本是安全的,或者来自于可以信赖的站点。下面的函数用于访问DOM中Window的扩展属性之前检查安全性。

BOOL CIEAutomationView::CanAccessExternal()
{
// if the dispatch we have is safe,
// we allow access
if (IsExternalDispatchSafe())
return TRUE;

// the external dispatch is not safe, so we check
// whether the current zone allows for scripting
// of objects that are not safe for scripting
if (m_spHtmlDoc == NULL)
return FALSE;

CComPtr<IInternetHostSecurityManager> spSecMan;
m_spHtmlDoc->QueryInterface(IID_IInternetHostSecurityManager,
(void **) &spSecMan);
if (spSecMan == NULL)
return FALSE;

HRESULT hr = spSecMan->ProcessUrlAction(URLACTION_ACTIVEX_OVERRIDE_OBJECT_SAFETY,
NULL, 0, NULL, 0, 0, PUAF_DEFAULT);
if (hr == S_OK)
return TRUE;
return FALSE;
}
默认设置下,一般网页上的脚本可以访问同一站点上的网页。

CHtmlView的增强

使用高级宿主特性

使用高级宿主特性的好处可以参见我翻译的CSDN文档中心文章自定义浏览器。在本文的示例代码中,我使用这个特性扩展了DHTML文档结构模型(DOM)使得网页中的脚本可以访问应用程序。离线浏览功能的实现也可以参考这篇文章。

为了可以在MFC6的CHtmlView基础上使用高级宿主特性自定义浏览器,需要重载默认的控件客户站点(这个代码只在MFC6中有必要,MFC7的CHtmlView已经支持了高级宿主特性)。因为MFC6不能重载CWnd的虚函数CreateControlSite来创建自定义的客户站点,所以使用Q236312 HOWTO: Disable the Default Pop-up Menu for CHtmlView这篇文章中的方法,重载默认的控件客户站点管理器。然后在重载过的默认控件客户站点中保存控件宿主的指针

CCustomControlSite::CCustomControlSite(COleControlContainer *pCnt)
:COleControlSite(pCnt)
{
m_pCustomImpl=NULL;
CWnd* pWnd=pCnt->m_pWnd;
if(pWnd){
if(pWnd->IsKindOf(RUNTIME_CLASS(CIEAutomationView))){
CIEAutomationView* pView=(CIEAutomationView*)pWnd;
m_pCustomImpl=pView;
}
}

这样可以在控件客户站点的IDocHostUIHandler2实现中调用控件宿主的相应处理,例如

HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler2::GetHostInfo( DOCHOSTUIINFO* pInfo )
{
METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler2)
if(pThis->m_pCustomImpl){
return pThis->m_pCustomImpl->GetHostInfo(pInfo );
}
return S_OK;
}

高级宿主特性的应用之一就是扩展DOM,使得网页上的脚本可以使用window.external访问DOM扩展对象。IE实现的DOM扩展对象具有menuArguments属性和IShellUIHelper接口。

控制新的窗口

默认情况下,浏览器收到创建新窗口请求时,会在IE中打开新的窗口。你可以处理NewWindow2事件来在自己指定的窗口中打开请求的页面。

void CIEAutomationView::OnNewWindow2(LPDISPATCH* ppDisp, BOOL* Cancel) 
{ 
 // Get a pointer to the application object. 
 CWinApp* pApp = AfxGetApp(); 
 // Get the correct document template. 
 POSITION pos = pApp->GetFirstDocTemplatePosition(); 
 CDocTemplate* pDocTemplate = pApp->GetNextDocTemplate( pos ); 
 // Create a new frame. 
 CFrameWnd* pFrame = pDocTemplate->CreateNewFrame( 
  GetDocument(), 
  (CFrameWnd*)AfxGetMainWnd() ); 
 // Activate the frame. 
 pDocTemplate->InitialUpdateFrame( pFrame, NULL ); 
 CIEAutomationView* pView = (CIEAutomationView*)pFrame->GetActiveView(); 
 // Pass pointer of WebBrowser object. 
 pView->SetRegisterAsBrowser( TRUE ); 
 *ppDisp = pView->GetApplication(); 
} 

如果需要更多信息,参见Q184876 HOWTO: Use the WebBrowser Control NewWindow2 Event。。

MDI浏览器

本文的示例代码是基于浏览器的,为了省事起见,直接在MFC的MFCIE示例上进行了修改,改成了MDI结构。MFCIE本身就是一个简单的浏览器,但是在把代码从主框架转移到子框架之后出了一点小问题,动态建立的收藏夹菜单不见了。这是由于MDI框架的菜单替换机制在框架激活时恢复了默认菜单造成的,所以我重载了CDocument::GetDefaultMenu,以在MDI框架的菜单替换的时候恢复我修改过的菜单(参见Q145857 How to Use Multiple Menus in MFC App That Uses GetDefaultMenu)。为了在子框架的创建过程中获得文档指针来修改文档中保存的菜单,可以从创建结构获得MDI创建上下文。

MDICREATESTRUCT * pMDICreateStruct=(MDICREATESTRUCT * )lpCreateStruct->lpCreateParams;
CCreateContext *pCreateContext=(CCreateContext *)pMDICreateStruct->lParam;
pMenu =((CIEAutomationDoc *)pCreateContext->m_pCurrentDoc)->m_menuDefault.GetSubMenu(3);

收藏夹

MFCIE示例中演示了如何建立一个收藏夹菜单,但是在移植工具栏里面的显示收藏夹命令到MDI子框架的时候碰见一个问题,动态创建的收藏夹菜单的位置不固定。但是通过查找新增的“添加到收藏夹”命令的位置,可以确定这个菜单的位置。添加到收藏夹和管理收藏夹的功能是通过创建ShellUIHelper对象实现的。

自动完成

为了使用方便,我在应用程序中也添加了自动完成功能。地址栏的自动完成功能的实现比较简单,调用系统的API SHAutoComplete就可以了。在我自己扩展了DOM的情况下,表单的自动完成似乎有些问题。

需要更多信息的话,可以参考我翻译的CSDN文档中心文章在应用程序中集成自动完成功能

访问需要授权的站点

某些站点在访问时需要验证用户身份,但是默认情况下浏览器控件在无法验证用户身份时并不提示用户输入用户名和密码。通过在控件的客户站点实现IServiceProvider接口,并且同时实现IAuthenticate接口,使得应用程序具有输入身份验证信息的功能。更多信息参见微软知识库文章Q329802 错误:通过IAuthenticate进行的代理身份验证可能会在安全URL上失败

常用命令处理

为了使用方便,增加了调用查找对话框、查看源代码和设置Internet选项的功能。这是通过查询浏览器控件的IOleCommandTarget接口,执行命令组CGID_WebBrowser的命令实现的。实现这类命令的方法不只一种,例如可以载入inetcpl.cpl,调用函数LaunchInternetControlPanel来实现打开Internet选项使用IMarkupServices接口执行查找、定位和选择,以及使用流来获得/设置网页的内容。在示例代码中,演示了如何分析文档结构,以及如何编辑选定的网页元素的HTML代码或者框架的源文件。

MFC6BUG的修复

尽管应用程序已经可以具有比较完整的功能,但是为了让应用程序能够长期正常工作,需要修复MFC6中包含的一些问题。我在这里只列出文章标题,有兴趣的话可以去查看微软知识库文章或者本文的代码

脚本示例

WebBrowser.Navigate "About:<H1><B>This is a test</B></h1>"
Dim msword 
Set msword = Scripter.HostCreateObject("Word.Basic")
msword.appshow
msword.filenew
msword.Insert "hello"
Sub External_BeforeNavigate2(pDisp, URL, Flags, TargetFrameName, PostData, Headers, Cancel)
MsgBox URL
End Sub
如果你为WebBrowser对象的事件编写脚本,你会发现这些事件处理代码不会被执行,这是因为CHtmlView处理了这些事件。你可以在你的CHtmlView派生类的处理代码中触发自定义对象的相应事件。在示例代码中,我转发了BeforeNavigate2事件到自定义对象的事件。

结论

给应用程序添加脚本支持可以大幅度提高程序的灵活性和可扩展性。虽然为此会牺牲一些性能、安全性和增加一些代码量,但是很多时候这种牺牲是值得的。

尽管我在示例代码没有转发DocumentComplete事件,但是这仅仅是基于安全性考虑。自动化浏览器可以很容易地实现广告窗口过滤、自动填写表单,页面分析等脚本。如何编写这些脚本取决于你自己的需要。

参考

如何: 在Windows2000中动态禁用/启用Ctrl-Alt-Delete

如何: 在Windows2000中动态禁用/启用Ctrl-Alt-Delete



此文章的信息应用于:

  • Microsoft Windows 2000




本文的更新信息位于http://blog.joycode.com/jiangsheng/archive/2004/07/20/27909.aspx
单击这里下载本文的代码。

概要

此文章的信息来自CSDN论坛VC/MFC版的讨论

在NT/2000中怎么禁用Ctrl+Alt+Delete?(不能用gina,键盘驱动)(此帖已归档,可以在http://search.csdn.net 搜索此帖)

在Windows2000中Ctrl-Alt-Delete组合键的处理如下:

  1. Winlogon初始化的时候,在系统中注册了CTRL+ALT+DEL Secure Attention Sequence(SAS)热键,并且在WinSta0 Windows 系统中创建三个桌面。

    SAS热键的注册使得Winlogon成为第一个处理CTRL+ALT+DEL的进程,所以保证了没有其他应用程序能够处理这个热键。

    在Windows NT/Windows 2000/Windows XP中, WinSta0 是表示物理屏幕、鼠标和键盘的Windows系统对象的名字。Winlogon在WinSta0 Windows系统中创建了SAS窗口(窗口标题是"SAS Window")和如下三个桌面。

    • Winlogon 桌面
    • 应用程序桌面
    • 屏幕保护桌面
  2. 当用户按下Ctrl-Alt-Delete组合键时,Winlogon桌面上的SAS窗口收到它注册的系统热键消息(WM_HOTKEY)
  3. SAS Window窗口处理这个消息调用Graphical Identification and Authentication(GINA)动态连接库中的相关函数

要中断Ctrl-Alt-Delete组合键的处理,可以有以下方式

更多信息

鉴于系统的更新可能造成我们替换的系统文件和其他系统文件不兼容(著名的DLL地狱),所以不推荐替换Winlogon.exe和GINA的方法。这里我们讨论Hook Winlogon 上的SAS窗口的窗口过程的方法。

因为SAS窗口和我们的程序内存地址空间不同,所以要写一个动态连接库,加载到SAS窗口的内存空间中。下面是动态连接库的源代码。

//---------------------------------------------------------------------------
//作者 :韦覃武
//网上呢称:BCB_FANS(四大名捕之追杀令)(此为CSDN和www.driverdevelop.com之帐号)
//E-Mail :slwqw@163.com
//日期 :2002-10-20
//
//功能 :在2000下屏蔽Ctrl + Alt + Del组合键。(在Windows 2000 Professional SP3
// 中文版平台下面测试通过)
//原理 :采用远程线程注入技术,装载一个DLL到Winlogon进程,然后截获SAS窗口的窗
// 口过程,接管WM_HOTKEY消息,以达到屏蔽Ctrl + Alt + Del之目的。
//开发语言:Borland C++Builder 5.0 Patch2
//技术比较:关于在2000下面如何屏蔽Ctrl + Alt + Del组合键,一种常被提到的解决方法就
// 是使用自己写的GINA去替换MSGINA.DLL,然后在WlxLoggedOnSAS里边直接返回
// WLX_SAS_ACTION_NONE。嘿嘿,说到底这并不是真正地屏蔽了这个组合键,只是
// 直接返回WLX_SAS_ACTION_NONE时,Winlogon进程又自动从"Winlogon"桌面切换
// 回原来的"Default"桌面了,而不是显示安全对话框,所以看起来被屏蔽了:),
// 使用那种方法明显地看到桌面在闪烁!但是使用本文的方法时,你不会看到任
// 何闪烁!
//鸣谢 :www.driverdevelop.com上的icube和lu0。
//版权 :转载请注明原作者:)

//---------------------------------------------------------------------------

#include "stdafx.h"

#include <string>

using namespace std;

//---------------------------------------------------------------------------

HWND hSASWnd;
FARPROC FOldProc;

LRESULT CALLBACK SASWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);

BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam);

//---------------------------------------------------------------------------

HANDLE hThread = NULL;
DWORD dwThreadId;

DWORD WINAPI ThreadFunc();

//---------------------------------------------------------------------------

BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH :

hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadFunc,NULL,0,&dwThreadId);
break;
case DLL_PROCESS_DETACH :
if(FOldProc != NULL)
{
SetWindowLong(hSASWnd,GWL_WNDPROC,long(FOldProc));
}
CloseHandle(hThread);
break;
}
return TRUE;
}
//---------------------------------------------------------------------------
DWORD WINAPI ThreadFunc()
{
HDESK hDesk;

hDesk = OpenDesktop("Winlogon",0,false,MAXIMUM_ALLOWED);

FOldProc = NULL;
hSASWnd = NULL;

EnumDesktopWindows(hDesk,(WNDENUMPROC)EnumWindowsProc,0);

if(hSASWnd != NULL)
{
FOldProc = (FARPROC)SetWindowLong(hSASWnd,GWL_WNDPROC,long(SASWindowProc));
}
CloseHandle(hDesk);

return 1;
}
//---------------------------------------------------------------------------
//查找"Winlogon"桌面的窗口

BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam)
{
char ClassBuf[128];

GetWindowText(hwnd,ClassBuf,sizeof(ClassBuf));

//我自己写了一个系统服务,然后在里边查询"Winlogon"桌面上的窗口,发现桌面上存在
//窗口"SAS window"。

string ClassName(ClassBuf);
if(ClassName.find("SAS window") != -1)
{
hSASWnd = hwnd;
return false;
}
return true;
}
//---------------------------------------------------------------------------
//SAS窗口的窗口过程
LRESULT CALLBACK SASWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
//屏蔽Ctrl + Alt + Del
if(uMsg == WM_HOTKEY)
{
WORD wKey = HIWORD(lParam);
WORD wModifier = LOWORD(lParam);
bool IsCtrlDown = ((wModifier & VK_CONTROL) != 0);
bool IsAltDown = ((wModifier & VK_MENU) != 0);
bool IsShiftDown = ((wModifier & VK_SHIFT) != 0);

//按下Ctrl + Alt + Del组合键
if(IsCtrlDown && IsAltDown &amp;& wKey == VK_DELETE)
{
return 1;
}
//按下Ctrl + Shift + Esc组合键,这个组合键将显示任务管理器,可根据需要是否屏蔽。
else if(IsCtrlDown && IsShiftDown &amp;& wKey == VK_ESCAPE)
{
// Do nothing
}
}
return CallWindowProc((WNDPROC)FOldProc,hwnd,uMsg,wParam,lParam);
}
//---------------------------------------------------------------------------
这样,如果Winlogon加载了这个动态连接库,那么就替换了SAS窗口的窗口过程。如果Winlogon卸载了这个动态连接库,则恢复了SAS窗口的窗口过程。

为了让Winlogon加载我们的动态连接库,首先要找到Winlogon进程,然后在进程中分配空间存放我们的代码,再通过创建远程线程赖执行我们的代码。下面是Hook部分的代码


//---------------------------------------------------------------------------
//作者 :韦覃武,jiangsheng
//网上呢称:BCB_FANS(四大名捕之追杀令)(此为CSDN和www.driverdevelop.com之帐号)jiangsheng(此为CSDN帐号)
//E-Mail :slwqw@163.com
//日期 :2002-10-20
//2002-11-5 jingsheng修改
//功能 :在2000下屏蔽Ctrl + Alt + Del组合键。(在Windows 2000 Professional SP3
// 中文版平台下面测试通过)
//原理 :采用远程线程注入技术,装载一个DLL到Winlogon进程,然后截获SAS窗口的窗
// 口过程,接管WM_HOTKEY消息,以达到屏蔽Ctrl + Alt + Del之目的。
//开发语言:Borland C++Builder 5.0 Patch2,Visual C++ 6.0 SP5
//技术比较:关于在2000下面如何屏蔽Ctrl + Alt + Del组合键,一种常被提到的解决方法就
// 是使用自己写的GINA去替换MSGINA.DLL,然后在WlxLoggedOnSAS里边直接返回
// WLX_SAS_ACTION_NONE。嘿嘿,说到底这并不是真正地屏蔽了这个组合键,只是
// 直接返回WLX_SAS_ACTION_NONE时,Winlogon进程又自动从"Winlogon"桌面切换
// 回原来的"Default"桌面了,而不是显示安全对话框,所以看起来被屏蔽了:),
// 使用那种方法明显地看到桌面在闪烁!但是使用本文的方法时,你不会看到任
// 何闪烁!
//鸣谢 :www.driverdevelop.com上的icube和lu0。
//版权 :转载请注明原作者:)

//---------------------------------------------------------------------------

#include "stdafx.h"
#include <tlhelp32.h>
#include <lmerr.h>

#include "Hook.h"
//add by jiangsheng 2002-11-5
#include "TaskKeyMgr.h"
#include "Wrappers.h"//复制自MSDN杂志Windows XP Escape from DLL Hell with Custom Debugging and Instrumentation Tools and Utilities的代码
extern BOOL Is_Terminal_Services () ;//复制自Platform SDK文档: Windows System Information /Verifying the System Version
//end add by jiangsheng 2002-11-5
//---------------------------------------------------------------------------
//错误代码格式化函数
//replaced by jiangsheng 2002-11-5
//from Q149409 HOWTO: Get Message Text from Networking Error Codes

CString __fastcall SysErrorMessage(DWORD dwLastError )
{
CString strRet(_T("Unknown error"));
HMODULE hModule = NULL; // default to system source
LPSTR MessageBuffer;
DWORD dwBufferLength;

DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER
FORMAT_MESSAGE_IGNORE_INSERTS
FORMAT_MESSAGE_FROM_SYSTEM ;

//
// If dwLastError is in the network range,
// load the message source.
//

if(dwLastError >= NERR_BASE && dwLastError <= MAX_NERR) {
hModule = LoadLibraryEx(TEXT("netmsg.dll"),NULL,LOAD_LIBRARY_AS_DATAFILE);
if(hModule != NULL)
dwFormatFlags = FORMAT_MESSAGE_FROM_HMODULE;
}

//
// Call FormatMessage() to allow for message
// text to be acquired from the system
// or from the supplied module handle.
//


if(dwBufferLength = FormatMessageA(
dwFormatFlags,
hModule, // module to get message from (NULL == system)
dwLastError,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language
(LPSTR) &MessageBuffer,
0,
NULL
))
{

//
// Output message string on stderr.
//
strRet=CString(MessageBuffer,dwBufferLength);
//
// Free the buffer allocated by the system.
//

LocalFree(MessageBuffer);
}

//
// If we loaded a message source, unload it.
//
if(hModule != NULL)
FreeLibrary(hModule);
return strRet;
}
//end replaced by jiangsheng 2002-11-5
//---------------------------------------------------------------------------


#ifdef UNICODE
LPCSTR LoadLibraryFuncStr = "LoadLibraryW";
LPCSTR GetModuleHandleFuncStr = "GetModuleHandleW";
#else
LPCSTR LoadLibraryFuncStr = "LoadLibraryA";
LPCSTR GetModuleHandleFuncStr = "GetModuleHandleA";
#endif
LPCSTR FreeLibraryFuncStr = "FreeLibrary";
LPCSTR GetProcAddressFuncStr = "GetProcAddress";
LPCSTR GetLastErrorFuncStr = "GetLastError";

//---------------------------------------------------------------------------
//removed by jiangsheng 2002-11-5
//const char* const RemoteDllName = "RemoteDll.Dll";
//end removed by jiangsheng 2002-11-5

LPCTSTR szRemoteProcessName = "Winlogon.exe";

typedef HINSTANCE (WINAPI *PLOADLIBRARY)(LPCTSTR );
typedef BOOL (WINAPI *PFREELIBRARY)(HINSTANCE);
typedef HMODULE (WINAPI* PGETMODULEHANDLE)(LPCTSTR );
typedef PVOID (WINAPI* PGETPROCADDRESS)(HINSTANCE,LPCSTR);
typedef DWORD (WINAPI* PGETLASTERROR)(VOID);

BOOL __fastcall EnablePrivilege(LPCTSTR lpszPrivilegeName,BOOL bEnable);
DWORD __fastcall GetPIDFromName(LPCTSTR lpszProcName);

//---------------------------------------------------------------------------

typedef struct
{
PLOADLIBRARY pfnLoadLibrary;
PGETLASTERROR pfnGetLastError;
TCHAR szDllName[1024];
DWORD dwReturnValue;
} INJECTLIBINFO;

typedef struct
{
PFREELIBRARY pfnFreeLibrary;
PGETMODULEHANDLE pfnGetModuleHandle;
PGETLASTERROR pfnGetLastError;
DWORD dwReturnValue;
TCHAR szDllName[1024];

} DEINJECTLIBINFO;

//---------------------------------------------------------------------------
//远程线程,用来装载DLL
static DWORD WINAPI ThreadFuncAttach(INJECTLIBINFO *pInfo)
{
HINSTANCE hDll=NULL;
pInfo->dwReturnValue = 0;
hDll = (HINSTANCE)pInfo->pfnLoadLibrary(pInfo->szDllName);
if(hDll == NULL)
pInfo->dwReturnValue = pInfo->pfnGetLastError();
return((DWORD)hDll);
}

//---------------------------------------------------------------------------
//占位函数,用来计算ThreadFuncAttach的大小

static void AfterThreadFuncAttach(void)
{
}

//---------------------------------------------------------------------------
//远程线程,用来卸载DLL

static DWORD WINAPI ThreadFuncDetach(DEINJECTLIBINFO *pInfo)
{
HINSTANCE hDll = NULL;
BOOL bResult=FALSE;
BOOL bHasFoundModule = FALSE;

pInfo->dwReturnValue = 0;//意味成功,如果这个值不是0,则是一个错误代码。

while((hDll = pInfo->pfnGetModuleHandle(pInfo->szDllName)) != NULL)
{
bHasFoundModule = TRUE;

bResult = pInfo->pfnFreeLibrary(hDll);
if(bResult == FALSE)
{
pInfo->dwReturnValue = pInfo->pfnGetLastError();
break;
}
}

if(pInfo->dwReturnValue == 0 && !bHasFoundModule)
{
pInfo->dwReturnValue = pInfo->pfnGetLastError();
}

return 1;
}

//---------------------------------------------------------------------------
//占位函数,用来计算ThreadFuncDetach的大小
static void AfterThreadFuncDetach(void)
{
}

//---------------------------------------------------------------------------
//修改本进程的权限
BOOL __fastcall EnablePrivilege(LPCTSTR lpszPrivilegeName,BOOL bEnable)
{
HANDLE hToken;
TOKEN_PRIVILEGES tp;
LUID luid;

if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES
TOKEN_QUERY TOKEN_READ,&hToken))
return FALSE;
if(!LookupPrivilegeValue(NULL, lpszPrivilegeName, &luid))
return TRUE;

tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = (bEnable) ? SE_PRIVILEGE_ENABLED : 0;

AdjustTokenPrivileges(hToken,FALSE,&tp,NULL,NULL,NULL);

CloseHandle(hToken);

return (GetLastError() == ERROR_SUCCESS);
}
//---------------------------------------------------------------------------
//通过进程名称得到进程的ID(这里使用方法Toolhelp函数,也可使用PSAPI)

DWORD __fastcall GetPIDFromName(LPCTSTR lpszProcName)
{
HANDLE hSnapshot;
PROCESSENTRY32 ProcStruct;
DWORD dwProcessID = -1;
//added by jiangsheng 2002-11-8
BOOL bIsTerminalServices=Is_Terminal_Services();
if(bIsTerminalServices){

//复制自MSDN杂志Windows XP Escape from DLL Hell with Custom Debugging and Instrumentation Tools and Utilities的代码
//get current session ID
CWTSWrapper WTS;
if (WTS.IsValid())
{
DWORD dwCurSessionID = -1;
LPTSTR pSessionInfo=NULL;
DWORD dwBytes;
if(WTS.WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE,WTS_CURRENT_SESSION,
WTSSessionId, (LPTSTR*)&pSessionInfo, &dwBytes)){
dwCurSessionID =*((DWORD*)pSessionInfo);
// enumerate processes
PWTS_PROCESS_INFO pProcessInfo = NULL;
DWORD ProcessCount = 0;
BOOL bFound;
if (WTS.WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, 0, 1,
&pProcessInfo, &ProcessCount)){
for (DWORD CurrentProcess = 0; CurrentProcess < ProcessCount; CurrentProcess++){
CString strCurExePath(pProcessInfo[CurrentProcess].pProcessName);
CString strRemoteProc(lpszProcName);
strCurExePath.MakeLower();
strRemoteProc.MakeLower();
bFound = (strCurExePath.Find(strRemoteProc) != -1);
if(bFound && dwCurSessionID==pProcessInfo[CurrentProcess].SessionId) {
dwProcessID = pProcessInfo[CurrentProcess].ProcessId;
break;
}
}
}
WTS.WTSFreeMemory(pSessionInfo);
}
}
}
else{
//end added by jiangsheng 2002-11-8
BOOL bResult;
hSnapshot = CreateToolhelp32Snapshot((DWORD)TH32CS_SNAPPROCESS,0);
ProcStruct.dwSize = sizeof(PROCESSENTRY32);
bResult = Process32First(hSnapshot,&ProcStruct);
while(bResult)
{
BOOL bFound;
CString strCurExePath(ProcStruct.szExeFile);
CString strRemoteProc(lpszProcName);
strCurExePath.MakeLower();
strRemoteProc.MakeLower();
bFound = (strCurExePath.Find(strRemoteProc) != -1);
if(bFound)
{
dwProcessID = ProcStruct.th32ProcessID;
break;
}
bResult = Process32Next(hSnapshot,&ProcStruct);
}
CloseHandle(hSnapshot);
}
return dwProcessID;
}
//---------------------------------------------------------------------------
// 插入代码
//---------------------------------------------------------------------------
//InjectFunc
void __fastcall InjectFunc()
{
HANDLE hRemoteProcess=NULL;
DWORD dwRemoteProcess=NULL;

DWORD dwThreadSize=0;
INJECTLIBINFO InjectLibInfo;
PVOID pRemoteThread=NULL;
PVOID pRemoteParam=NULL;
DWORD dwWriten=0;
DWORD dwRet=0;

//提升本进程权限然后打开目的进程
//当前用户必须具有调试权限
EnablePrivilege(SE_DEBUG_NAME,true);
dwRemoteProcess = GetPIDFromName(szRemoteProcessName);
if(dwRemoteProcess == (DWORD)-1)
{
MessageBox(NULL,_T("Failed to Query Process ID."),NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
return;
}
hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS,false,dwRemoteProcess);
if(hRemoteProcess == NULL)
{
MessageBox(NULL,_T("Failed to Open Process. Err = ") + SysErrorMessage(GetLastError()),
NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
return;
}
//初始化参数
ZeroMemory(&InjectLibInfo,sizeof(INJECTLIBINFO ));
InjectLibInfo.pfnLoadLibrary = (PLOADLIBRARY)GetProcAddress(GetModuleHandle("Kernel32.dll"),LoadLibraryFuncStr);
InjectLibInfo.pfnGetLastError = (PGETLASTERROR)GetProcAddress(GetModuleHandle("Kernel32.dll"),GetLastErrorFuncStr);
lstrcpyn(InjectLibInfo.szDllName,CTaskKeyMgr::strRemoteDllName,CTaskKeyMgr::strRemoteDllName.GetLength()+1);
//在远程线程分配内存来存放参数
pRemoteParam = VirtualAllocEx(hRemoteProcess,NULL,sizeof(INJECTLIBINFO),MEM_COMMIT,PAGE_READWRITE);
if(pRemoteParam == NULL)
{
MessageBox(NULL,_T("Failed to Allocate Memory at Remote Process for Param.Err = ") + SysErrorMessage(GetLastError()),
NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
return;
}
dwRet = WriteProcessMemory(hRemoteProcess,pRemoteParam,(LPVOID)&InjectLibInfo,sizeof(INJECTLIBINFO),&amp;dwWriten);
if(dwRet == 0)
{
MessageBox(NULL,_T("Failed to Write Param to Remote Process.Err = ") + SysErrorMessage(GetLastError()),
NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
return;
}

//拷贝线程体
dwThreadSize = (int)AfterThreadFuncAttach - (int)ThreadFuncAttach + 1024 + sizeof(INJECTLIBINFO);

pRemoteThread = VirtualAllocEx(hRemoteProcess,NULL,dwThreadSize,MEM_COMMIT,PAGE_READWRITE);
if(pRemoteThread == NULL)
{
MessageBox(NULL,_T("Failed to Allocate Memory at Remote Process for Thread Code.Err = ") + SysErrorMessage(GetLastError()),
NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
return;
}
dwRet = WriteProcessMemory(hRemoteProcess,pRemoteThread,(LPVOID)ThreadFuncAttach,dwThreadSize,&dwWriten);
if(dwRet == 0)
{
MessageBox(NULL,_T("Failed to Write Thread Code to Remote Process.Err = ") + SysErrorMessage(GetLastError()),
NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
return;
}
//启动远程线程
HANDLE hRemoteThread;

hRemoteThread = CreateRemoteThread(hRemoteProcess,0,0,(DWORD(__stdcall *)(VOID*))pRemoteThread,(INJECTLIBINFO*)pRemoteParam,0,&dwWriten);
::WaitForSingleObject(hRemoteThread,INFINITE);

if(hRemoteThread == NULL)
{
MessageBox(NULL,_T("Failed to create unload thread.Err=") + SysErrorMessage(GetLastError()),NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
}
else
{
;
}

//读卸载返回值
dwRet =ReadProcessMemory(hRemoteProcess,pRemoteParam,(LPVOID)&InjectLibInfo,sizeof(INJECTLIBINFO),&amp;dwWriten);
if(dwRet == 0)
{
MessageBox(NULL,_T("Unable to read load return value.Err=") + SysErrorMessage(GetLastError()),
NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
}
else
{
if(InjectLibInfo.dwReturnValue == 0)
{
;
}
else
{
MessageBox(NULL,_T("Failed to load library to Winlogon.Err=") +SysErrorMessage(InjectLibInfo.dwReturnValue),NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
}
}

//恢复权限
EnablePrivilege(SE_DEBUG_NAME,false);
CloseHandle(hRemoteProcess);
}
//---------------------------------------------------------------------------
// 卸载线程
//---------------------------------------------------------------------------
//DeinjectFunc
void __fastcall DeinjectFunc()
{
HANDLE hRemoteProcess=NULL;
DWORD dwRemoteProcess=0;

DWORD dwThreadSize=0;
DEINJECTLIBINFO DeinjectLibInfo;

PVOID pRemoteThread=NULL;
PVOID pRemoteParam=NULL;
DWORD dwWriten=0;
DWORD Ret=0;

//提升本进程权限然后打开目的进程
EnablePrivilege(SE_DEBUG_NAME,true);

dwRemoteProcess = GetPIDFromName(szRemoteProcessName);
if(dwRemoteProcess == (DWORD)-1)
{
MessageBox(NULL,_T("Failed to Query Process ID."),NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
return;
}
hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS,false,dwRemoteProcess);
if(hRemoteProcess == NULL)
{
MessageBox(NULL,_T("Failed to Open Process. Err = ") + SysErrorMessage(GetLastError()),
NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
return;
}

//初始化参数
ZeroMemory(&DeinjectLibInfo,sizeof(DEINJECTLIBINFO ));
DeinjectLibInfo.pfnFreeLibrary = (PFREELIBRARY)GetProcAddress(GetModuleHandle("Kernel32.dll"),FreeLibraryFuncStr);
DeinjectLibInfo.pfnGetModuleHandle = (PGETMODULEHANDLE)GetProcAddress(GetModuleHandle("Kernel32.dll"),GetModuleHandleFuncStr);
DeinjectLibInfo.pfnGetLastError = (PGETLASTERROR)GetProcAddress(GetModuleHandle("Kernel32.dll"),GetLastErrorFuncStr);

lstrcpyn(DeinjectLibInfo.szDllName,CTaskKeyMgr::strRemoteDllName,CTaskKeyMgr::strRemoteDllName.GetLength()+1);

//在远程线程分配内存来存放参数
pRemoteParam = VirtualAllocEx(hRemoteProcess,NULL,sizeof(DEINJECTLIBINFO),MEM_COMMIT,PAGE_READWRITE);
if(pRemoteParam == NULL)
{
MessageBox(NULL,_T("Failed to Allocate Memory at Remote Process.Err = ") + SysErrorMessage(GetLastError()),
NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
}
Ret = WriteProcessMemory(hRemoteProcess,pRemoteParam,(LPVOID)&DeinjectLibInfo,sizeof(DEINJECTLIBINFO),&amp;dwWriten);
if(Ret == 0)
{
MessageBox(NULL,_T("Failed to Write Param to Remote Process.Err = ") + SysErrorMessage(GetLastError()),
NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
return;
}

//拷贝线程体
dwThreadSize = (int)AfterThreadFuncDetach - (int)ThreadFuncDetach + 1024 + sizeof(DEINJECTLIBINFO);
pRemoteThread = VirtualAllocEx(hRemoteProcess,NULL,dwThreadSize,MEM_COMMIT,PAGE_READWRITE);
if(pRemoteThread == NULL)
{
MessageBox(NULL,_T("Failed to Allocate Memory at Remote Process for Thread Code.Err = ") + SysErrorMessage(GetLastError()),
NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
return;
}
Ret = WriteProcessMemory(hRemoteProcess,pRemoteThread,(LPVOID)ThreadFuncDetach,dwThreadSize,&dwWriten);
if(Ret == 0)
{
MessageBox(NULL,_T("Failed to Write Thread Code to Remote Process.Err = ") + SysErrorMessage(GetLastError()),
NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
return;
}

//启动远程线程
HANDLE hRemoteThread;

hRemoteThread = CreateRemoteThread(hRemoteProcess ,0,0,(DWORD(__stdcall *)(VOID*))pRemoteThread,(DEINJECTLIBINFO*)pRemoteParam,0,&dwWriten);
if(hRemoteThread == NULL)
{
MessageBox(NULL,_T("Failed to create remote unload thread.Err=") + SysErrorMessage(GetLastError()),NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
}
else
{
CloseHandle(hRemoteThread);
}

//读卸载返回值
Ret = ReadProcessMemory(hRemoteProcess,pRemoteParam,(LPVOID)&DeinjectLibInfo,sizeof(DEINJECTLIBINFO),&amp;dwWriten);
if(Ret == 0)
{
MessageBox(NULL,_T("Unable to read unload return value.Err=") + SysErrorMessage(GetLastError()),
NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
}
else
{
if(DeinjectLibInfo.dwReturnValue == 0)
{

}
else
{
MessageBox(NULL,_T("Failed to unload .Err=")+ SysErrorMessage(DeinjectLibInfo.dwReturnValue),NULL,MB_OK MB_APPLMODAL MB_ICONWARNING);
}
}

//恢复权限
CloseHandle(hRemoteProcess);
EnablePrivilege(SE_DEBUG_NAME,false);
}
//---------------------------------------------------------------------------
//使用方法
 

BOOL CTaskKeyMgr::IsCtrlAltDeleteDisabled(){return bInjectFuncLoaded;} 

if (dwFlags & CTRLALTDEL) {
if(bDisable&&!IsCtrlAltDeleteDisabled()){
InjectFunc();
bInjectFuncLoaded=TRUE;
}
if(!bDisable&&IsCtrlAltDeleteDisabled()){
DeinjectFunc();
bInjectFuncLoaded=FALSE;
}
}

注意

如果Windows的后续版本更改了Ctrl+Alt+Delete的处理,本文所提供的技术可能不再工作。如果你在你的代码中使用了本文的技术,请注意你可能必须在未来修改你的代码。

已知问题

  • 尚无Unicode版本
  • VirtualAllocEx分配的内存没有用VirtualFreeEx释放
  • 如果编译时加入开关的话,在Debug方式下运行会造成Winlogon出错(出错后请不要确认或取消那个出错对话框,然后保存打开的所有文档,关闭所有程序,通过正常的途径关机,否则Windows会立刻关机)

参考

如果需要更多信息,参考CSDN论坛中的讨论

在NT/2000中怎么禁用Ctrl+Alt+Delete?(不能用gina,键盘驱动)。  

单击这里下载本文的代码。

MSDN文档库中的文章

  • Q226359 HOWTO: Disable Task Switching on Win32 Platforms
  • Q195027 STOP 0xC000021A in Winlogon Caused by PCAnywhere
  • Q229033 Programs That Replace Msgina.dll May Cause "STOP 0x0000001E" Error Message
  • Q192298 Third Party GINAs May Fail with Service Pack 4 Causing STOP 0x21A in WINLOGON
  • Q164486 Winlogon May Fail if the Third-Party Gina.dll File is Missing or Corrupted
  • Q180854 Access Violation in Winlogon with Third-Party Gina.dll
  • Q193361 MSGINA.DLL does not Reset WINLOGON Structure

MSDN杂志中的文章

VC知识库中的文章

文章类型 : kbhowto

历史:
上次更新: 2002年11月10日