12/18/2003

Ask Mr JS

[url=http://expert.csdn.net/expert/Topicview2.asp?id=2562836] 如何访问 WebBrowser 控件中的XML源码[/url]
总结:注意使用IHtmlDocument::get_Script(唯一的一个方法@_@bb)就可以获得脚本的顶层对象,然后就可以用GetIdsOfNames开始遍历文档结构了

[url=http://expert.csdn.net/expert/Topicview2.asp?id=2560341]ActiveX控件上想添加一个工具栏[/url]
总结:另外一种方式是弹出工具栏,参见[url=http://support.microsoft.com/default.aspx?scid=kb;en-us;166193]Knowledge Base Q166193 SAMPLE: ColorFrm Demonstrates ActiveX Control with Popup Toolbar[/url]
[url=http://expert.csdn.net/expert/Topicview2.asp?id=2572992]如何把word菜单融入到应用程序中[/url]
总结:不光Word,支持ActiveDocument的都可以,比如Adobe Acrobat Reader
[url=http://expert.csdn.net/Expert/TopicView1.asp?id=2288266]怎么用VC修改任意网页中text文本框的值[/url]
总结:有了技术不要做坏事哟

12/17/2003

问题:Internet Explorer中的控件在可见之前没有被创建

Knowledge Base
Q195188 PRB: ActiveX Control Window Is Not Created Until Visible in Internet Explorer
使用知识库里面的方法
// CMyControl is derived from CComControl
STDMETHOD(SetClientSite)(IOleClientSite *pClientSite)
{
if (pClientSite)
{
RECT rc = {0,0,0,0};
// Don't have access to the container's window so just use the
// desktop. Window will be resized correctly during in-place
// activation.
HWND hWnd = CreateControlWindow(::GetDesktopWindow(), rc);
_ASSERT (hWnd);
}
return IOleObjectImpl::SetClientSite (pClientSite);
}
在Windows XP中,可以在任务栏上看到控件的窗口,很是不雅观
解决的方法是尽可能用GetForegroundWindow替代GetDesktopWindow(GetForegroundWindow有时返回NULL)

12/16/2003

Ask Mr JS

[url=http://community.csdn.net/expert/Topicview2.asp?id=2542812]怎样修改theApp.m_pszAppName[/url]总结:可以参考微软知识库Q154744[url=http://community.csdn.net/expert/Topicview2.asp?id=1988055]BBS灌水机[/url]总结:太复杂了一点,而且依赖于网页结构。后来我写了个支持脚本的,参见[url=http://www.csdn.net/develop/author/netauthor/jiangsheng/]我的专栏[/url]
[url=http://community.csdn.net/expert/Topicview2.asp?id=2521954]获得设备安装和卸载的通知,控件访问所在网页的DHTML文档对象模型[/url]总结:抄微软的代码,没什么特别的[url=http://community.csdn.net/expert/Topicview2.asp?id=2568865]编译时报告线程函数类型错误[/url]总结:非静态成员函数具有隐含this指针参数,会造成这个编译错误。解决方案是 m_pThreadWrite=AfxBeginThread(ThreadProc,(LPVOID)this);
UINT CMyClass::ThreadProc(LPVOID lp){ CMicrophoneInput* pInput=(CMicrophoneInput*)lp; return pInput->Run();}UINT CMyClass::Run(){ HRESULT hr; if(!InitInstance()){ TRACE("InitInstance failed\r\n"); return ExitInstance(); } while(!IsKilling()){ //do something } return ExitInstance();}BOOL CMyClass::InitInstance(){ m_eventKill.ResetEvent(); m_eventDead.ResetEvent(); //do something return TRUE}UINT CMyClass::ExitInstance(){ //do something m_eventDead.SetEvent(); return 0;}
BOOL CMyClass::IsDead(){ return WaitForSingleObject(m_eventDead,0)==WAIT_OBJECT_0;}BOOL CMyClass::IsKilling(){ return WaitForSingleObject(m_eventKill,0)==WAIT_OBJECT_0;}在外部可以这样终止线程 //check if dead if(!IsDead()&&m_pThreadWrite!=NULL){ m_eventKill.SetEvent(); WaitForSingleObject(m_eventDead,INFINITE); m_pThreadWrite=NULL; }

12/12/2003

Ask Mr JS

[url=http://community.csdn.net/expert/Topicview2.asp?id=2535830]关于DAO和多线程的问题 [/url]总结:放弃希望吧,你们这些使用过时技术的人[url=http://community.csdn.net/expert/Topicview2.asp?id=2532086]转换字符串到指定代码页 [/url][url=http://community.csdn.net/expert/Topicview2.asp?id=2554149]怎么实现JIS内码到SJIS的转换 [/url]总结:IE的国际化做的是我见过得最好的,为什么不借来用用呢 [url=http://community.csdn.net/expert/Topicview2.asp?2554403.xml]ActiveX控件中访问所在页面的指定元素的属性[/url][url=http://community.csdn.net/expert/Topicview2.asp?id=2545179] CListView是如此封装CLisCtrl的呢?[/url]总结:不仅派生类的指针可以转化为基类指针,基类指针也可以转化为派生类指针,只要派生类没有数据成员和虚函数。CEdit,CListCtrl,CTreeCtrl都是这样派生的,所以CEditView,CListVew,CTreeView都可以这么操作。 [url=http://community.csdn.net/expert/Topicview2.asp?id=2545686]ADO中使用带参数的SQL和传递参数 [/url]总结:其实存储过程比较好用,用法也类似

12/08/2003

DHTML应用于windows Applicatoin界面

[url=http://community.csdn.net/Expert/TopicView1.asp?id=2408994]DHTML应用于windows Applicatoin界面的讨论![/url]入门[url=http://www.csdn.net/develop/read_article.asp?id=9408]在对话框中使用网页输入数据[/url][url=http://www.csdn.net/develop/read_article.asp?id=14752]如何: 通过HTML文档对象模型访问文档中的ActiveX控件的属性[/url][url=http://www.csdn.net/develop/read_article.asp?id=18998]浏览器集成教学 在你的应用程序中集成WebBrowser控件[/url][url=http://www.csdn.net/develop/read_article.asp?id=19627]浏览器集成教学 在你的应用程序中集成WebBrowser控件[/url][url=http://www.csdn.net/develop/read_article.asp?id=20439]关于MSHTML [/url][url=http://www.csdn.net/develop/read_article.asp?id=21702]浏览器集成教学--脚本化浏览器[/url]

12/07/2003

ActiveX控件访问所在网页的DHTML文档对象模型(MFC)


void CICWXPlayerCtrl::SetHolderID(CICWXPlayerWindowHolder::enumHolderIndex iHolderIndex,LPCTSTR lpszElementID)
{
IOleClientSite* pClientSite=GetClientSite();
do{
if(pClientSite==NULL)break;
CComQIPtr pISP(pClientSite);
if(pISP==NULL)break;
CComPtr pIWebBrowser2;
pISP->QueryService(IID_IWebBrowserApp, IID_IWebBrowser2,
(void **)&pIWebBrowser2);
if(pIWebBrowser2==NULL)break;
CComPtr spDocument;
pIWebBrowser2->get_Document(&spDocument);
CComQIPtr pIHTMLDocument2(spDocument);
if(pIHTMLDocument2==NULL)break;
CComPtr pHTMLElementCollection;
pIHTMLDocument2->get_all(&pHTMLElementCollection);
if(pHTMLElementCollection==NULL)break;
COleVariant varName(lpszElementID,VT_BSTR);
COleVariant varindex;

CComPtr spElement;
pHTMLElementCollection->item(varName,varindex,&spElement);
if(spElement==NULL)break;
//is it an Iframe?
CComQIPtr pFrame(spElement);
if(pFrame){
switch(iHolderIndex){
case CICWXPlayerWindowHolder::WebHolder:
case CICWXPlayerWindowHolder::TextHolder:
m_holders[iHolderIndex].SetTarget(spElement);
break;
}
}
CComQIPtr pObjectElement(spElement);
if(pObjectElement==NULL)break;
CComPtr spObject;
pObjectElement->get_object(&spObject);
m_holders[iHolderIndex].SetTarget(spObject);
}
while(FALSE);
}

12/04/2003

Ask Mr JS

Ask Mr JS控件找不到容器的窗口 [http://search.csdn.net/Expert/topic/2518/2518945.xml]
总结:其实并不是写ActiveX就不能用WINAPI
to be continued...

12/01/2003

Ask Mr JS

今天心血来潮,整理了一下上个月在CSDN得分的问题我参与的帖子太多,整理起来有难度,所以只整理结了的贴子
希望把这个习惯保留下去[url=http://community.csdn.net/Expert/TopicView2.asp?id=2501675]为什么一按Ctrl+Alt + DEL后退回到在桌面上画的字就消失了呢[/url]总结:重画时被擦掉了[url=http://community.csdn.net/Expert/TopicView2.asp?id=2497461]多采集卡环境下用DirectX SDK中的AMCap示例无法切换使用的硬件[/url]总结:驱动的问题总是很难检查出来的[url=http://community.csdn.net/Expert/TopicView2.asp?id=2497558]IPicture显示图片时有内存泄漏[/url]总结:分配内存和获得COM对象接口指针之后记得要释放[url=http://community.csdn.net/Expert/TopicView2.asp?id=2498143]缓冲区的同步[/url]总结:最好异步使用以提高性能,同步的话记得要加锁[url=http://community.csdn.net/Expert/TopicView2.asp?id=2406640]高效的屏幕截取方法[/url]总结:只能看情况了,要质量就不能要低带宽[url=http://community.csdn.net/Expert/TopicView2.asp?id=2465634]Shell Namespace Extension Example[/url]总结:有必要扩展windows的shell么?要知道这会降低windows的性能[url=http://community.csdn.net/Expert/TopicView2.asp?id=2487688]分析网页和自动提交网页表单[/url]总结:经常被提出的问题,但是网页千奇百怪,要写个通用的不容易[url=http://community.csdn.net/Expert/TopicView2.asp?id=2399653]两幅BMP 图像进行比较[/url]总结:异或,更好的办法是用MPEG等算法[url=http://community.csdn.net/Expert/TopicView2.asp?id=2463663]实现文件夹的缩略图[/url]总结:见我的CSDN专栏文章。实际上那篇文章没什么,缩略浏览的技术核心是如何将图象进行格式转换和压缩存储,以及缩略图的缓冲。[url=http://community.csdn.net/Expert/TopicView2.asp?id=2429877]从vc工程生成类关系图[/url]总结:Visio很不错的,建议买一套[url=http://community.csdn.net/expert/TopicView2.asp?id=2465941]处理CDialogbar对象中控件的通知[/url]总结:也是问了很多遍的问题,不知道微软在MFC6中为什么对CDialogbar的处理和CDialog的处理不一样[url=http://community.csdn.net/Expert/TopicView2.asp?id=2481697]黑白的内存DC[/url]总结:同样是问了很多遍的问题,不知道为什么这么多人抄示例代码的时候抄漏一两行[url=http://community.csdn.net/Expert/TopicView2.asp?id=2379164]启动新的IE窗口时,隐藏菜单和控件[/url]总结:IWebBrowser2接口提供了这个功能,只需要获得创建的窗口的这个接口而已[url=http://community.csdn.net/Expert/TopicView2.asp?id=2465834]两个类之间如何访问[/url]总结:C++是VC的基础[url=http://community.csdn.net/Expert/TopicView2.asp?id=2464391]如何调用网页中Script中的函数?[/url]总结:虽然答案很简单,但是其他人的回答也介绍了脚本如何"发现"对象的属性和方法,调用默认属性/方法,根据名字调用方法和访问网页中的脚本[url=http://community.csdn.net/Expert/TopicView2.asp?id=2355813]数据库中的表保存为一个Excel文件[/url]总结:介绍了用DAO和自动化访问Excel[url=http://community.csdn.net/Expert/TopicView2.asp?id=2270225]获得DC中的位图数据[/url]总结:还是问了很多遍的问题,上面那个高效录制屏幕的基础[url=http://community.csdn.net/Expert/TopicView2.asp?id=2465752]拖拉窗口时只显示边框[/url]总结:效率就是这么提高的[url=http://community.csdn.net/Expert/TopicView2.asp?id=2363440]VC编辑文件时突然报告文件不存在,是否创建[/url]总结:VC的临时文件机制和病毒防火墙的隔离机制之间发生冲突。编译大工程时VC非法操作也是这个原因[url=http://community.csdn.net/Expert/TopicView2.asp?id=2334466]根据URL,从IE缓存里读出内容[/url]总结:缓存浏览器?[url=http://community.csdn.net/Expert/TopicView2.asp?id=2400520]控件中使用控件[/url]总结:用这种方法在控件中创建对话框,可以集成其他控件,以及处理它们的事件。[url=http://community.csdn.net/Expert/TopicView2.asp?id=2426826]读当前计算机的DNS名,但,GetComputerNameEx调用出错[/url]总结:记得多看MSDN中的函数说明[url=http://community.csdn.net/Expert/TopicView2.asp?id=2405317]win2000下的进程隐藏[/url]总结:不是很正当的功能需要不是很正统的方法[url=http://community.csdn.net/Expert/TopicView2.asp?id=2227425]捕获DHTML中脚本动态创建的对象的事件时出现问题[/url]总结:微软的IE有待改进[url=http://community.csdn.net/Expert/TopicView2.asp?id=2250244]编程访问获取本地策略中的系统安全/密码/帐号策略[/url]总结:WMI这个东东真好用[url=http://community.csdn.net/Expert/TopicView2.asp?id=2430758]编程修改链接的目标时内存泄漏[/url]总结:同上面的泄漏问题,这回是字符串没释放[url=http://community.csdn.net/Expert/TopicView2.asp?id=2431944]24位的RGB转成16位的RGB[/url]总结:什么时候DirectX才支持这个转化啊,最好用硬件来做比较快[url=http://community.csdn.net/Expert/TopicView2.asp?id=2442983]派生类对于基类的CRuntimeClass验证失败[/url]总结:记得编译的时候使用同一个版本的MFC库文件[url=http://community.csdn.net/Expert/TopicView2.asp?id=2438141]自动化接口要传递自定义的结构体类型的参数[/url]总结:还是传IDispatch借口好了[url=http://community.csdn.net/Expert/TopicView2.asp?id=2449479]在DirectShow中使用DivX5.0.5时的filter连接问题[/url]总结:DirectShow未熟[url=http://community.csdn.net/Expert/TopicView2.asp?id=2454122]去掉分割窗口的多余滚动条[/url]总结:总是要重复劳动[url=http://community.csdn.net/Expert/TopicView2.asp?id=2442149]编程调整工具栏的停靠位置[/url]总结:用程序来模拟鼠标的拖放操作[url=http://community.csdn.net/Expert/TopicView2.asp?id=2442463]在线程中调用其他线程创建的MFC对象指针时出现错误[/url]总结:MFC的局限

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日

10/23/2003

写篇文章真累

http://www.csdn.net/develop/read_article.asp?id=21702
最近一篇文章长长短短,写了两个礼拜吧。
写这篇文章的主要原因是想把网页分析做得更加灵活。这篇文章的基础是我以前为一个EBS游戏写的外挂,可以自动修改网页内容(主要是表单)和定时submit表单(有的网站的submit有时间限制)。以前的代码是用VC来写的,和网页的修改同步很不方便。很多功能依赖于网页结构,网页结构一变的话,就需要重新编译代码。所以在重写的时候,想考虑做成类似于outlook的规则。但是在编写的时候,发现规则编写起来实在是太繁琐了。其实这些规则用VB来写脚本的话,可能就几句话。所以做得更加通用一点,用vbs算了。这样的话,修改的时候改的少些,虽然使用门槛要高些。
主要碰见的技术问题是 脚本中的浏览器的事件处理代码不能执行(CHtmlView捕获了事件,所以要在CHtmlView里面转发事件) 移植MFCIE的代码到MDI的时候的菜单出现很多问题,主要是MDI的菜单替换,以及插入MDI系统菜单之后收藏夹的位置变化
移植部分MFC7的代码到MFC6,中间还结合了KB的文章的代码,真是faint 无法直接创建支持事件的CCmdTaget类(ActiveX好像可以……) MFC的类向导不支持自动化中的默认参数 上面两个问题使得我不得不手动改ODL文件,中间出现无数问题…… 关闭窗口时出现非法操作(最后捕获了WindowClosing事件,Cancel掉了系统的处理之后自己关闭) 表单的自动完成没有资料,没做成功。好像和隐藏方法IShellUIHelper::AutoCompleteAttatch有关。 编辑网页源代码时文档结构的刷新有问题,IHtmlDomNote的Child在SetOutHTML之后全部不见了,最后是刷新整个文档结构树才解决。

9/26/2003

ASF学习笔记

使用回调方法 一些Windows Media Format SDK的接口的方法是异步执行的,很多这样的方法使用回调方法和应用程序通讯。
使用OnStatus回调 在Windows Media Format SDK中,IWMStatusCallback::OnStatus 被很多对象调用。OnStatus接收SDK操作状态的变化。每种对象可能有不同的方式连接到IWMStatusCallback。
使用事件进行同步调用 1 使用Platform SDK的API CreateEvent创建一个事件对象 2 实现回调函数,,捕获事件,并且调用SetEvent函数标记事件对象 3 在应用程序中调用WaitForSingleObject 、监视事件对象。如果你是在为Windows程序编写代码,你必须创建一个消息循环对用户操作做出相应。
使用上下文参数 Windows Media Format SDK的一些回调函数具有pvContext参数,这个值是你在异步操作启动时传递给对象的。 通常,多个对象使用同一个回调时传递对象指针作为这个参数。
使用设置 设置的主要目的是描述其中的对象,以及对象之间的关系。不管是否使用编码/解码器,某些流需要配置才可以工作。流的配置信息可以用IWMCodecInfo3 接口的方法获得,但是不要手动配置一个使用了Windows Media编码/解码器的流。 创建/编辑设置的步骤 1 创建空设置,或者打开旧设置 2 配置每个流,如果需要的话,使用从编码/解码器获得的数据 3 配置互斥(可选) 4 配置带宽共享(可选) 5 配置优先级(可选)
设计设置
选择编码方式 1-pass Constant Bit Rate (CBR) 直播的唯一选择。以预定的码流率编码,并且质量最低。 2-pass CBR 文件形式的流媒体,长度固定,质量比1-pass Constant Bit Rate (CBR)好 1-pass Variable Bit Rate (VBR) 需要指定质量时使用,本地播放或者下载后播放 2-pass VBR – unconstrained 需要指定带宽时使用,但是真实带宽占用可以偏离指定带宽,本地播放或者下载后播放 2-pass VBR – constrained 需要指定带宽时使用,但是真实带宽占用不能大于指定带宽,本地播放或者下载后播放
码流率 除了数据之外,分包也要占用一定的带宽。如果流包含数据单位扩展,那么这将大大增加流的码流率。 同时,除了应用程序之外的任何连接都和应用程序共享网络带宽,所以不能认为应用程序可以完全使用客户的网络带宽。
配置流 如果流是视频/音频,使用Windows Media编码/解码器,那么你必须使用IWMCodecInfo3的方法从编码/解码器获得流配置对象。 如果流是其他类型,使用IWMProfile::CreateNewStream.创建一个新的流配置对象。 每个流配置都必须设置名字、连接名和流序号(从1到63)。 可能会修改使用Windows Media编码/解码器的two-pass VBR 音频流的VBR设置。视频流无需修改配置。 根据类型配置其他类型流。所有的这种流需要设置比特率和缓冲窗口。 使用IWMProfile::AddStream. 将流添加到媒体。 大部分设置可以通过IWMMediaProps访问。这些设置保存在WM_MEDIA_TYPE 结构中。对于音频和视频,WM_MEDIA_TYPE结构指针指向媒体特定的更多信息,通常是WAVEFORMATEX 或者WMVIDEOINFOHEADER结构。视频有第三个结构BITMAPINFOHEADER描述了视频的桢。
从编码/解码器获得流配置信息 使用Windows Media编码/解码器的视频/音频流需要从编码/解码器获得流配置信息。尽管你可以自行设置这些配置,从编码/解码器获得流配置信息使得数据是准确的。除非文档推荐,否则不要修改获得的流配置信息。 可以从设置管理器的IWMCodecInfo, IWMCodecInfo2, 和IWMCodecInfo3接口获得信息。
枚举安装的编码/解码器 编码/解码器的编号从0开始,音频和视频的编码/解码器有独立的编号。
枚举编码/解码器支持的格式
配置音频流 不要手动修改获得的配置的质量设置,而应该用IWMPropertyVault接口修改。 音频流的缓冲窗口不应该设置得比视频流的缓冲窗口大,否则会造成播放不同步。通常,音频流的缓冲窗口是1.5-3秒,视频流的缓冲窗口是3-5秒。
配置视频流 除非是RGB24数据,否则大小应该是4的倍数,否则会有非法格式/非法配置等错误。
配置屏幕流 和视频流一样,但是如果复杂度设置为0,那么IWMVideoMediaProps::SetQuality设置的质量会被忽略。
图像流 包含JPEG形式的图像数据。
视频流的定位性能 可以使用IWMVideoMediaProps::SetMaxKeyFrameSpacing设置关键桢间隔。增加关键桢数目会降低视频质量。
未压缩的音视频格式 不能用于流,必须手动设置带宽,缓冲窗口应该设为0
配置其他流 通常,这种流只需要比特率和缓冲窗口和WM_MEDIA_TYPE 中的媒体主类型设置。但是某些类型的流还需要其他设置
脚本流 WM_MEDIA_TYPE的成员formattype 要设置为WMFORMAT_Script,指明pbFormat成员指向一个WMSCRIPTFORMAT 结构。 只有一种脚本媒体类型,WMSCRIPTTYPE_TwoStrings。
文件传输流 每个采样需要一个数据单位扩展,你需要实现一个数据单位扩展系统。 调用IWMStreamConfig2::AddDataUnitExtension添加数据单位扩展到流。 hr = pStreamConfig2->AddDataUnitExtension(CLSID_WMTPropertyFileName, -1, NULL, 0);
网页流 WM_MEDIA_TYPE.majortype WMMEDIATYPE_Filetransfer. WM_MEDIA_TYPE.subtype WMMEDIASUBTYPE_WebStream. WM_MEDIA_TYPE.bFixedSizeSamples False. WM_MEDIA_TYPE.bTemporalCompression True. WM_MEDIA_TYPE.lSampleSize 0. WM_MEDIA_TYPE.formattype WMFORMAT_WebStream. WM_MEDIA_TYPE.pUnk NULL. WM_MEDIA_TYPE.cbFormat sizeof(WMT_WEBSTREAM_FORMAT). WM_MEDIA_TYPE.pbFormat 一个配置好的WMT_WEBSTREAM_FORMAT结构的指针. WMT_WEBSTREAM_FORMAT.cbSampleHeaderFixedData sizeof(WMT_WEBSTREAM_SAMPLE_HEADER). WMT_WEBSTREAM_FORMAT.wVersion 1. WMT_WEBSTREAM_FORMAT.wreserved 0.
文本流 媒体类型WMMEDIATYPE_TEXT
计算比特率和缓冲窗口 简单的办法是设置为数据长度/时间.但是图像和文件流可能突发数据很多,但是有很多空闲时间.缓冲窗口必须设置得足够大.需要的时候,可以适当增加这些值.
变码流率流
数据单位扩展
保存/重新使用配置 不要手动更改PRX文件。看起来很小的改变会使得配置无效。
互斥
流优先级
带宽共享
包大小
写ASF文件 使用IWMWriter::SetProfile对写入对象进行设置。但是,设置了之后,对设置对象的修改不会自动反映到写入对象,除非再次调用IWMWriter::SetProfile。 设置写入对象会复位全部头属性,所以必须在设置之后再修改这些属性。
输入
设置对象中的每个连接有一个输入号。除非配置中有互斥流,否则每个流有一个连接。互斥流共享连接。 写入流时需要用输入号来区别每个流,所以必须用连接名字来判断每个流的输入号。
枚举输入格式 SDK可以对输入进行预处理来判断输入的格式是否支持。
设置输入格式 找到符合数据的输入格式之后,可以调用IWMWriter::SetInputProps让它可以被写入对象使用。对于视频流,必须设置桢的大小。
其他类型的流和预压缩流 其他类型的流无需设置。 预压缩流需要设置输入格式为NULL。这个设置必须在BeginWriting之前完成。同时需要调用IWMHeaderInfo3::AddCodecInfo设置预压缩流的格式。
BeginWriting之前,还可以用IWMWriterAdvanced2::SetInputSetting设置和流无关的设置。
元数据 使用写入对象的IWMHeaderInfo 或者IWMHeaderInfo2接口访问元数据。必须在IWMWriter::BeginWriting之前完成元数据的写入。 注意,如果创建了写入对象而没有释放,然后再创建写入对象,一些元数据会被复制到新的对象中。
写入采样 写入采样之前要调用IWMWriter::BeginWriting. 1 用IWMWriter::AllocateSample分配缓冲区,并且获得其INSSBuffer接口 2 用INSSBuffer::GetBuffer获得缓冲区地址 3 复制数据到缓冲区中 4 用INSSBuffer::SetLength设置复制的数据长度 5 把缓冲区、输入编号和媒体时间传递给IWMWriter::WriteSample方法。音频数据持续时间是一样的,所以可以简单地在现有时间上加上一个常数。对于视频,需要根据桢率计算媒体时间。 WriteSample是异步调用,在下一次WriteSample调用之前可能没有结束。所以要在每次写入采样之前调用AllocateSample获取缓冲区对象。 所有采样写完之后,调用IWMWriter::EndWriting完成写入操作。 流数据应该几乎同时结束,否则某些流数据可能丢失。
写入压缩采样 使用IWMWriterAdvanced::WriteStreamSample 替代IWMWriter::WriteSample。
写入图像采样 必须用IWMWriterAdvanced2::SetInputSetting设置图像质量g_wszJPEGCompressionQuality,范围从1到100。图像采样压缩比通常很大,所以要使用尝试的方法设置缓冲窗口大小。
强制关键桢 使用INSSBuffer3::SetProperty设置缓冲区对象的WM_SampleExtensionGUID_OutputCleanPoint为TRUE。

9/25/2003

ASF学习笔记

设置(Profile)

一个设置是一个ASF的配置(configuration)的描述数据集合。一个设置必须至少包含一个流的配置设置。

流信息
设置中的流信息包含流的比特率(bit rate),缓冲窗口和媒体属性的设置。视频和音频的流信息准确描述了文件中的媒体配置,包括压缩数据使用的编码和解码器(如果有的话)。

一个设置也包含很多创建ASF文件时使用的ASF的特性,这包括互斥、媒体优先级、带宽共享和数据单位扩展。

每次写文件是必须提供设置。你可以调用IWMWriter::SetProfile指定一个设置。

设置有三种形式,应用程序中设置对象包含的数据,XML文件,或者ASF文件头。

设置对象
可以用设置管理器创建空设置对象,然后从现有数据载入设置

XML文件
具有PRX扩展名.注意Windows Media 9 Series 中没有原来的系统设置(system profiles)也不再使用,而作为这种形式存在。保存自定义设置时必须保存成这种文件。

ASF文件头
ASF读者创建一个设置对象,然后从ASF文件头载入格式信息。但是修改文件头不会影响文件的内容。可以重新对文件编码来完成格式的修改。

使用设置编辑器
除了用Windows Media Format SDK之外,还可以用Windows Media Encoder 9 Series中包含的设置编辑器创建设置。在应用程序中使用IWMProfileManager::LoadProfileByData载入预定义的设置。但是,启用“视频大小:和输入相同”这个选项将设置视频的大小为0;Windows Media Encoder 9 Series可以识别并且处理这种情况,但是Windows Media Format SDK的写入对象不会自动处理,所以应用程序必须并且处理这种情况.

下面是一个XML格式的配置


// 73647561-0000-0010-8000-00AA00389B71 ''auds'' == WMMEDIATYPE_Audio

// 00000001-0000-0010-8000-00AA00389B71 WMMEDIASUBTYPE_PCM




// 73647561-0000-0010-8000-00AA00389B71 ''auds'' == WMMEDIATYPE_Audio


// 56555949-0000-0010-8000-00AA00389B71 ''YV12'' == MEDIASUBTYPE_IYUV








// 73636d64-0000-0010-8000-00AA00389B71 ''scmd'' == WMMEDIATYPE_Script


// 82f38a70-c29f-11d1-97ad-00a0c95ea850 WMSCRIPTTYPE_TwoStrings





媒体采样(Media Sample)
媒体采样,或者采样,是一块数字媒体数据。采样是Windows Media Format SDK可以读写的数据的最小单位。采样内容由采样相关的媒体类型指出。对于视频,每个采样表示一个桢,每个单独采样中包含的数据量由创建ASF时指定的设置设置。
采样可以包含未压缩的数据,或者压缩过的数据,这时被称为流采样。创建ASF时,采样被传递给写入对象,写入对象使用相关的编码器压缩数据,并且写入ASF文件的数据段。播放时,读出对象读出压缩的数据,解压数据,并且提供未压缩格式的数据。
采样被封装在Windows Media Format SDK的自动分配的缓冲区对象中。需要的时候,你也可以自己分配缓冲区对象,使用它的读写特性。
这里的采样并非音频采样。通常,音频采样质量用每秒录制的采样数据数量表示,例如CD质量是44,100采样/秒,或者44.1 kHz。

输入,流和输出
输入对象是你用于写入文件的任何数字媒体流,必须是可以支持的格式。支持很多标准RGB和YUV作为视频输入格式,PCM作为音频输入格式。如果编码器不支持某种输入格式,那么写入对象会初始化一个辅助对象,转换输入流到可以支持的格式,例如调整色深转换、缩放,调整声音质量、采样率和频道数目。某些情况下,压缩格式的食品和音频可用于输入。输入也可以是其他格式,例如文字,脚本命令,图像,或者任意文件数据。

输出是读取对象传递给应用程序,提供用户体验的数据。一个输出等同于一个流。如果你使用互斥属性,那么所有互斥数据共享一个输出。

一个流是一个ASF文件中包含的数据。一个流的生命期中只有一种压缩设置。一个简单的ASF具有两种流:视频和音频。更加复杂的ASF文件可以包含两路音频和多路视频。音频可以有同样的压缩设置,但是内容不同,例如不同语言的讲解;视频可以有同样的内容,但是具有不同的压缩比例。格式是在设置对象中指定的。

某些输入可以是压缩过的,这时读取对象必须以流编号依次访问数据,而不是按输出顺序访问数据。

编号
流具有从1开始的编号,这是在设置中指定的。同时,流具有一个索引以在设置中枚举流。这两个数字并不相关,例如输入1并不一定是编号为1的流,编号为1的流并不一定是输入1,等等。

格式
每种媒体类型的全部信息。每个格式有一个主类型,例如音频或视频,并且可能有一个子类型。格式包含依赖于主类型的不同信息。视频和音频格式比其他格式需要更多信息。

输入格式
描述你传递给写入对象的数字媒体类型。如果ASF文件中的流是用编码器压缩,那么编码器只支持某些输入格式。使用Windows Media 音频和视频编码器时,可以使用写入对象枚举支持的输入格式。写到文件时,你有责任选择一个匹配输入媒体的输入格式。
某些格式不必匹配编码器指明的输入格式,编码器可以自行转换数据到需要的格式。

流格式
ASF文件中的数据保存形式。在设置中描述,可以符合或不符合输入、输出格式(例如使用了某种编码/解码器)。可能必须获得编码/解码器信息之后,才可以设置流格式

输出格式。
描述你传递给读出对象的数字媒体类型。如果ASF文件中的流是用编码器压缩,那么编码器只支持某些输出格式。使用Windows Media 音频和视频编码器时,可以使用读出对象枚举支持的输出格式。读出文件时,你有责任选择一个匹配输出媒体的输出格式。
某些格式不必匹配编码器指明的输出格式,编码器可以自行转换数据到需要的格式。

比特率(Bit Rate)
每秒传递给ASF的数据的数量,以位/秒(bps)或者千位/秒(kbps)为单位。经常与带宽混淆,带宽也以bps或者kbps为单位。
如果用户的带宽小于ASF的比特率,那么播放可能中断。通常,带宽不足会导致跳过某些采样,或者更多的数据缓冲时间。
每个ASF文件创建时被指定一个比特率,它基于文件中流的数量。不同的流可以有不同的比特率。比特率可以是常数(压缩的数据可以以基本同样的速度被传输)或者可变(保留压缩的数据质量,即使可能造成突发数据溢出)。
同一个内容可以被压缩成多个比特率不同的流,然后你可以配置他们为互斥的。这个属性叫多比特率(multiple bit rate), 或者MBR.

元数据
描述ASF文件或者文件内容的信息,位于文件头。元数据的项称为属性。每一个属性由名字和值组成。全局常数用于标识属性,例如ASF文件的标题被保存在 g_wszWMTitle 属性中。在Windows Media Format SDK 中定义了最常用的内建属性,但是你也可以定义自己的属性。由于其他开发者可能和你是用同样的名字,所以可能造成冲突。
一些全局属性可以被修改,例如g_wszWMSeekable属性(文档是否可以从任意点被读取)
一些属性纯粹用于信息用途,并且必须被设置,例如g_wszWMAuthor属性(作者)
属性可以被应用到整个文件或者单独的流。
你可以用Windows Media Format SDK编辑MP3文件的元数据,但是必须使用ID3-compliant属性保留与其他MP3应用程序的兼容性。

媒体时间
自第一个采样开始的时间计量方式,单位和SDK其他时间的单位一样,是100纳秒。它使得文件中不同的流可以被同步。你写入的每一个采样都必须有媒体时间。ASF文件数据段中每一个数据对象都有媒体时间。每一个输出的数据也都有媒体时间。

缓冲
读取对象打开流文件时从文件头的信息决定缓冲区大小。实际比特率是变化的,但是平均值应该是设置中指定的值。

缓冲窗口是以可以缓冲的数据时间长度来衡量的。例如,32Kbps的流,3秒的缓冲窗口,意味着缓冲区大小为 12,000字节(32000*3/8)。解码器限制了这个数值,所以缓冲窗口的平均比特率不大于流的比特率。
通常在设置中指定这个值,写入对象处理剩下的部分。写入压缩数据到流时,必须自己确定写入的速度不会超出这个值

ASF文件中的段
一个ASF文件中的段以对象的方式组织起来。一共有三种顶层对象,必须有的头对象(Head),数据对象(Data),以及可选的索引对象(Index)。

每个对象都以全球唯一标志(GUID)和大小开始。这些数字使得文件读者可以解析这些信息,并且载入到相应的对象。因为这些GUID,底层的对象可以以任何顺序排列,并且仍然可以被识别。这使得一个不完整的ASF文件仍然可被正确读取,只要有一个完整的文件头和至少一个数据对象。某些对象,例如流属性对象,可能有多个示例。

头对象包含文件的描述信息,同时是唯一的顶层对象容器。

数据对象以包的格式存储流数据。数据对象还具有文件ID和包总个数属性,但是对于流格式,包总个数属性没有意义。

每一个数据包包含发送时间和持续时间。这使得读者可以发现流传输的中断。
数据包的数据被封装到载荷(payloads)中。一个载荷可以包含一个或者多个媒体对象(media objects),媒体对象的一个例子是视频流的一个桢。大的媒体对象,例如视频流的一个关键桢,可能被扩展到多个载荷,甚至多个包。为了跟踪对象的片断,每个对象的段具有从0到255的编号。
除了数据之外,载荷也具有以毫秒为单位的时间戳。
所有的包具有头对象中指定的统一的大小。当一个包包含的数据少于指定大小时,用数据("padding" data )填充不足部分。

索引对象包含时间《-》关键桢的配对,以更有效地在文件中定位。因为它处于文件末尾,实时媒体不能访问这个对象。