jiangsheng

http://www.csdn.net/develop/author/netauthor/jiangsheng/
随笔 - 142, 评论 - 629, 引用 - 27

导航

关于

 
这下要维护3个BLOG了,faint 其他的地址:

所有的文章版权归原文作者所有,任何人需要转载文章,必须征得原文作者授权。
我的MVP配置

标签

每月存档

最新留言

广告

Visual Studio 2005 RTM

Quan To在其BLOG(http://blogs.msdn.com/quanto/archive/2005/10/25/485008.aspx) 中提到Visual Studio 2005 RTM已经定版,并且给出了卸载发布候选版本的方法。目前还不知道SQL Server 2005是否定版。尽管MSDN的订阅者并未像预期的那样在上周就看到VS2005的MSDN订阅下载,但是个人估计在正式发布日之前还是可以下载到的。

 

posted on 2005-10-26 22:16:00 by jiangsheng  评论(6) 阅读(5531)

创建和自动化Internet Explorer和资源管理器窗口

我在很久之前就开始用程序自动化Shell窗口——主要对象是IE窗口。有时浏览器控件或者MFC类CHTMLView可以满足我的需要,但是很多时候我需要从头嵌入浏览器控件并且尽可能模拟IE的行为,例如实现IDocHostUIHandler来启用自动完成功能。一个很自然的替代方案是直接操作IE窗口。

创建新的Internet Explorer窗口

最简单的方法是调用Windows API ShellExecute (Ex),Paul DiLascia在他的C++ Q&A专栏文章"Browser Detection Revisited, Toolbar Info, IUnknown with COM and MFC"里面有一段示例代码:

/// As I've shown in many programs... ShellExecute(0, _T("open"), pszMyHTMLFile, 0, 0, SW_SHOWNORMAL);
但是,这样没法控制新的窗口,而且在用户关闭程序之后会留下一个IE窗口。为了扫我自己的门前雪,我需要找到我创建的窗口,并且控制它。

我的下一个尝试是创建和控制一个InternetExplorer对象,并且在必要时关掉它。微软知识库中有这么一篇文章"How To Automate Internet Explorer to POST Form Data" 基本上描述的就是我想要的,除了最后的关闭窗口。嗯,简单的调用IWebBrowser2::Quit就可以做到这一点

// create a new IE instance and show it //CComQIPtr m_pWebBrowser2; m_pWebBrowser2.CoCreateInstance(CLSID_InternetExplorer); HRESULT hr; hr = m_pWebBrowser2->put_StatusBar(VARIANT_TRUE); hr = m_pWebBrowser2->put_ToolBar(VARIANT_TRUE); hr = m_pWebBrowser2->put_MenuBar(VARIANT_TRUE); hr = m_pWebBrowser2->put_Visible(VARIANT_TRUE); if(!::PathIsURL(m_strFileToFind)) m_strFileToFind=_T("http://blog.joycode.com/jiangsheng"); COleVariant vaURL( ( LPCTSTR) m_strFileToFind); m_pWebBrowser2->Navigate2( &vaURL, COleVariant( (long) 0, VT_I4), COleVariant((LPCTSTR)NULL, VT_BSTR), COleSafeArray(), COleVariant((LPCTSTR)NULL, VT_BSTR) ); void CAutomationDlg::OnDestroy() { //close the IE window created by this program before exit if(m_pWebBrowser2) { if(m_bOwnIE) { m_pWebBrowser2->Quit(); m_bOwnIE=FALSE; } UnadvisesinkIE(); m_pWebBrowser2=(LPUNKNOWN)NULL; } CDialog::OnDestroy(); }
还有一个问题。要是用户在我的WM_TIMER处理函数中操作窗口之前关掉了新的IE窗口怎么办?可以IWebBrowser2接口控制的IE对象现在不再存在了。幸亏微软考虑到了这一点,程序不会崩溃,但是最好还是能够知道什么时候它会关闭,这样我可以避免意外发生。

处理Internet Explorer的事件

Internet Explorer对象在退出时会触发DWebBrowserEvents2::OnQuit事件。这是一个理想的释放控制的时机。因为对象要被销毁,所以我同时也停止监视对象的事件

if(m_pWebBrowser2) { UnadvisesinkIE(); m_pWebBrowser2=(LPUNKNOWN)NULL; }
连接到当前的Internet Explorer窗口

虽然我不在乎我会控制到哪个IE窗口,但是既然微软知识库里面有"如何连接到一个Internet Explorer的实例"这样一篇文章,我假定一些人会觉得"如何连接到当前的Internet Explorer实例"这样一篇文章比较有用.

这样的话,什么是“当前的Internet Explorer实例”?实际上,它就是最后一个活动的IE窗口。因为Windows会把活动的窗口移动到z-order的顶部,所以它会保留在所有IE窗口的z-order的最高处。因此我需要做的就是找到哪个IE窗口具有最高的z-order值。这样我需要先判断哪个窗口是IE窗口。在一些和Spy++有关的调查之后,我假定IE窗口具有一个共同的窗口类"IEFrame",然后编写了一个函数来获得Shell窗口的窗口类:

//shell windows object will list both IE and Explorer windows //use their window class names to identify them. CString CAutomationDlg::GetWindowClassName(IWebBrowser2* pwb) { TCHAR szClassName[_MAX_PATH]; ZeroMemory( szClassName, _MAX_PATH * sizeof( TCHAR)); HWND hwnd=NULL; if (pwb) { LONG_PTR lwnd=NULL; pwb->get_HWND(&lwnd); hwnd=reinterpret_cast(lwnd); ::GetClassName( hwnd, szClassName, _MAX_PATH); } return szClassName; }
剩下的问题就很简单了:沿Z轴枚举顶层窗口,找到第一个Shell窗口列表中的具有窗口类"IEFrame"的第一个实例。之后我操作了一下IE的DHTML文档对象模型(也称为DOM,它只在IE窗口触发最后一个DocumentComplete事件只后有效)来确认成功连接到窗口。
void CAutomationDlg::DocumentComplete(IDispatch *pDisp, VARIANT *URL) { //HTML DOM is available AFTER the DocumentComplete event is fired. //For more information, please visit KB article //"How To Determine When a Page Is Done Loading in WebBrowser Control" //http://support.microsoft.com/kb/q180366/ CComQIPtr pWBUK(m_pWebBrowser2); CComQIPtr pSenderUK( pDisp); USES_CONVERSION; TRACE( _T( "Page downloading complete:\r\n")); CComBSTR bstrName; m_pWebBrowser2->get_LocationName(&bstrName); CComBSTR bstrURL; m_pWebBrowser2->get_LocationURL(&bstrURL); TRACE( _T( "Name:[ %s ]\r\nURL: [ %s ]\r\n"), OLE2T(bstrName), OLE2T(bstrURL)); if (pWBUK== pSenderUK) { CComQIPtr pHTMLDocDisp; m_pWebBrowser2->get_Document(&pHTMLDocDisp); CComQIPtr pHTMLDoc(pHTMLDocDisp); CComQIPtr ecAll; CComPtr pTagLineDisp; if(pHTMLDoc) { CComBSTR bstrNewTitle(_T("Sheng Jiang's Automation Test")); pHTMLDoc->put_title(bstrNewTitle); pHTMLDoc->get_all(&ecAll); } if(ecAll) { ecAll->item(COleVariant(_T("tagline")),COleVariant((long)0),&pTagLineDisp); } CComQIPtr eTagLine(pTagLineDisp); if(eTagLine) { eTagLine->put_innerText( CComBSTR(_T("Command what is yours, conquer what is not. --Kane"))); } } }
现在控制的窗口和IE打开文件时选择的一样了。

副产品: 连接到当前的Windows Explorer窗口

在研究ShellWindows对象的shell窗口列表时,我获得一个副产品:看起来Windows Explorer窗口也有共同的窗口类名。这样同样的机制在把窗口类从"IEFrame"改成"ExploreWClass"之后对Windows Explorer窗口也适用。因为没有DHTML DOM可供操作,我通知Windows Explorer 窗口打开一个现存路径,来标志我接管了这个窗口。

//show the folder bar COleVariant clsIDFolderBar(_T("{EFA24E64-B078-11d0-89E4-00C04FC9E26E}")); COleVariant FolderBarShow(VARIANT_TRUE,VT_BOOL); COleVariant dummy; if(m_pWebBrowser2) m_pWebBrowser2->ShowBrowserBar(&clsIDFolderBar,&FolderBarShow,&dummy); //browse to a given folder CComQIPtr psp(m_pWebBrowser2); CComPtr psb; if(psp) psp->QueryService(SID_STopLevelBrowser,IID_IShellBrowser,(LPVOID*)&psb); if(psb) { USES_CONVERSION; LPITEMIDLIST pidl=NULL; SFGAOF sfgao; SHParseDisplayName (T2OLE(m_strFileToFind),NULL,&pidl,0, &sfgao); if(pidl==NULL) ::SHGetSpecialFolderLocation(m_hWnd,CSIDL_DRIVES,&pidl); m_pidlToNavigate=NULL; if(pidl) { //if the start address is a folder, then browse it. //otherwise browse to its parent folder, and select it in the folder view. LPCITEMIDLIST pidlChild=NULL; CComPtr psf; HRESULT hr = SHBindToParent(pidl, IID_IShellFolder, (LPVOID*)&psf, &pidlChild); if (SUCCEEDED(hr)){ SFGAOF rgfInOut=SFGAO_FOLDER; hr=psf->GetAttributesOf(1,&pidlChild,&rgfInOut); if (SUCCEEDED(hr)){ m_pidlToNavigate=ILClone(pidl); if(rgfInOut&SFGAO_FOLDER){//this is a folder psb->BrowseObject(pidl,SBSP_SAMEBROWSER); } else { //this is a file, browse to the parent folder LPITEMIDLIST pidlParent=ILClone(pidl); ::ILRemoveLastID(pidlParent); psb->BrowseObject( pidlParent, SBSP_SAMEBROWSER); ILFree(pidlParent); } } } //clean up ILFree(pidl); } }:
这代码有点长,因为我想区别对待文件和文件夹。如果你调用IShellBrowser::BrowseObject并且给这个方法传递一个文件pidl,那么Windows Explorer会提示你是否打开这个文件,就像在资源管理器的地址栏中输入路径之后按回车一样。我想模拟"Explorer.exe /select"的行为,在文件夹视图中选择指定的文件,所以我在DocumentComplete事件处理函数中加入了一些代码:
if(m_pidlToNavigate) { //If the start address is a file, browse to the parent folder //and then select it CComQIPtr psp(m_pWebBrowser2); CComPtr psb; CComPtr psv; if(psp) psp->QueryService(SID_STopLevelBrowser,IID_IShellBrowser,(LPVOID*)&psb); if(psb) psb->QueryActiveShellView(&psv); if(psv) { LPCITEMIDLIST pidlChild=NULL; CComPtr psf; SFGAOF rgfInOut=SHCIDS_ALLFIELDS; HRESULT hr = SHBindToParent(m_pidlToNavigate, IID_IShellFolder, (LPVOID*)&psf, &pidlChild); if (SUCCEEDED(hr)){ hr=psf->GetAttributesOf(1,&pidlChild,&rgfInOut); if (SUCCEEDED(hr)){ if((rgfInOut&SFGAO_FOLDER)==0){ //a file, select it hr=psv->SelectItem(ILFindLastID(m_pidlToNavigate) ,SVSI_SELECT|SVSI_ENSUREVISIBLE|SVSI_FOCUSED| SVSI_POSITIONITEM); } } } } //clean up ILFree(m_pidlToNavigate); m_pidlToNavigate=NULL; }
创建Explorer窗口

解决了这么多问题,可以衣锦还乡了。既然我可以以和当前的Internet Explorer窗口基本相同的方式连接到当前的Windows Explorer窗口,那么我是否可以以和创建Internet Explorer窗口基本相同的方式创建Windows Explorer窗口?遗憾的是,这不可行。不存在Windows Explorer对应的类ID来创建一个COM对象。虽然我仍旧可以创建IE窗口,浏览到文件夹,显示文件夹侧边栏,使得它看起来就像一个Windows Explorer窗口,但是我不能改变窗口类"IEFrame",因此较难把它和其他的显示HTML网页和活动文档的IE窗口区分开来。

好吧,既然我不能以COM的方式来创建它,我还可以尝试用传统的方式。我可以创建一个explorer.exe进程之后查找其主窗口,就像Paul DiLascia 在他的文章"Get the Main Window, Get EXE Name"中演示的那样,并且发送未文档化的消息WM_GETISHELLBROWSER来获得窗口的IShellBrowser接口:

//start the new process STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); // Start the child process. if( !CreateProcess( NULL, // No module name (use command line). _T("explorer.exe"), // Command line. NULL, // Process handle not inheritable. NULL, // Thread handle not inheritable. FALSE, // Set handle inheritance to FALSE. 0, // No creation flags. NULL, // Use parent's environment block. NULL, // Use parent's starting directory. &si, // Pointer to STARTUPINFO structure. &pi ) // Pointer to PROCESS_INFORMATION structure. ) //wait a graceful time //so the window is created and is ready to answer messages. ::WaitForInputIdle(pi.hProcess,1000); //m_hExplorerProcess=(DWORD)pi.hProcess; EnumWindows(EnumWindowsProc,(LPARAM)this); BOOL CALLBACK CAutomationDlg::EnumWindowsProc(HWND hwnd,LPARAM lParam) { CAutomationDlg* pdlg=(CAutomationDlg*)lParam; DWORD pidwin; GetWindowThreadProcessId(hwnd, &pidwin); if (pidwin==pdlg->m_hExplorerProcess) { IShellBrowser* psb=(IShellBrowser*)::SendMessage(hwnd,WM_USER+7,0,0); CComQIPtr pwb(psb); return FALSE; } return TRUE; }
啊喔,这在我的计算机上也没有效果。怎么回事?在我的资源管理器的文件夹选项中,“在同一窗口中打开每一个文件夹”被选中,所以新的Windows Explorer窗口被创建在现有的Windows Explorer进程中。看起来这是条死胡同。

等一下,我手头还有另一个ShellWindows对象,它可以给我一个Shell窗口的列表,包含每一个Windows Explorer窗口和每个窗口对应的IWebBrowser2接口,这是到IShellBrowser接口的入口。.现在我需要获得两份shell窗口列表,创建explorer.exe进程之前和之后各一份,之后要比较它们来找到新的shell窗口:

m_pShellWindows.CoCreateInstance(CLSID_ShellWindows); if(m_pShellWindows) { //get the list of running IE windows //using the ShellWindows collection //For more information, please visit //http://support.microsoft.com/kb/176792 long lCount=0; m_pShellWindows->get_Count(&lCount); for(long i=0;i pdispShellWindow; m_pShellWindows->Item(COleVariant(i),&pdispShellWindow); if(pdispShellWindow) { m_listShellWindows.AddTail(new CComQIPtrIDispatch(pdispShellWindow)); } } } //enumerate through the new shell window list long lCount=0; m_pShellWindows->get_Count(&lCount); for(long i=0;i//search the new window //using the ShellWindows collection //For more information, please visit //http://support.microsoft.com/kb/176792 BOOL bFound=FALSE; CComPtr pdispShellWindow; m_pShellWindows->Item(COleVariant(i),&pdispShellWindow); //search it in the old shell window list POSITION pos=m_listShellWindows.GetHeadPosition(); while(pos) { CComQIPtrIDispatch* pDispatch=m_listShellWindows.GetNext(pos); if(pDispatch&&pdispShellWindow.p==pDispatch->p) { bFound=TRUE;break; } } if(!bFound)//new window found { //attach to it m_pWebBrowser2=pdispShellWindow; m_bOwnIE=TRUE; //sink for the Quit and DocumentComplete events AdviseSinkIE(); NavigateToSamplePage(FALSE); } }
等一下,你的"创建explorer.exe进程之后"是什么意思?一秒钟之后?还是两秒钟?实际上,一个WindowRegistered事件会被ShellWindows 对象触发,所以我在事件处理中加入一些代码:.
//sink DShellWindowsEvents events LPUNKNOWN pUnkSink = GetIDispatch(FALSE); m_pShellWindows.CoCreateInstance(CLSID_ShellWindows); AfxConnectionAdvise((LPUNKNOWN)m_pShellWindows, DIID_DShellWindowsEvents,pUnkSink,FALSE,&m_dwCookieShellWindows); void CAutomationDlg::WindowRegistered(long lCookie) { //ok, a new shell window is created if(m_pShellWindows) { //enumerate through the new shell window list long lCount=0; m_pShellWindows->get_Count(&lCount); for(long i=0;i//search the new window //using the ShellWindows collection //For more information, please visit //http://support.microsoft.com/kb/176792 BOOL bFound=FALSE; CComPtr pdispShellWindow; m_pShellWindows->Item(COleVariant(i),&pdispShellWindow); //search it in the old shell window list POSITION pos=m_listShellWindows.GetHeadPosition(); while(pos) { CComQIPtrIDispatch* pDispatch=m_listShellWindows.GetNext(pos); if(pDispatch&&pdispShellWindow.p==pDispatch->p) { bFound=TRUE;break; } } if(!bFound)//new window { //attach to it m_pWebBrowser2=pdispShellWindow; m_bOwnIE=TRUE; //sink for the Quit and DocumentComplete events AdviseSinkIE(); NavigateToSamplePage(FALSE); } } //clean up if(m_dwCookieShellWindows!= 0) { LPUNKNOWN pUnkSink = GetIDispatch(FALSE); AfxConnectionUnadvise((LPUNKNOWN)m_pShellWindows, DIID_DShellWindowsEvents, pUnkSink, FALSE, m_dwCookieShellWindows); m_dwCookieShellWindows= 0; } POSITION pos=m_listShellWindows.GetHeadPosition(); while(pos) { CComQIPtrIDispatch* pDispatch=m_listShellWindows.GetNext(pos); delete pDispatch; } m_listShellWindows.RemoveAll(); m_pShellWindows=(LPUNKNOWN)NULL; } }
为什么不用Browser Helper Objects?

因为新的窗口在进程外,所以跨进程列集COM调用很慢。如果你的自动化操作包含很多的COM调用,那么你可能要把代码本地化,例如编写一个浏览器辅助对象(BHO)。但是,BHO会被每一个Windows Explorer和Internet Explorer的实例加载,而且我不想拖慢整个系统来让它们扫瓦上霜。一些人倒是使用了这个技术连接到当前的Internet Explorer窗口.

已知问题

ShellWindows对象在explorer.exe process被终止或者尚未启动时不可访问。BHO在这种情况下可以作为替代方案。

结论

这里有一大堆让人迷糊的代码,而且可能还有你不熟悉的COM和Windows API函数混合调用。希望你会觉得本文有用,并且不会被我的代码搞得头昏脑胀。自动化Internet Explorer和Windows Explorers窗口可以节省你模拟系统默认行为的时间,并且给用户提供一个熟悉的界面。

参考

历史

  • October 20, 2005 初版发布

posted on 2005-10-20 23:32:00 by jiangsheng  评论(10) 阅读(15396)

MSN Messenger and Yahoo Messenger Collaborate

According to Sina, MSN Messenger and Yahoo Messenger are expected to be able to communicate with each other. (http://tech.sina.com.cn/i/2005-10-13/1629739192.shtml) .

It looks similar to the "Instant Messenger Interoperability" section in the 2003 Microsoft-AOL agreement(http://www.microsoft.com/presspass/press/2003/may03/05-29msaolsettlementpr.mspx). 

 

posted on 2005-10-13 17:33:00 by jiangsheng  评论(0) 阅读(4054)

托管C++中函数调用的双重转换(Double Thunking)

 在VC.Net中使用默认设置/clr编译时,一个托管函数会产生两个入口点,一个是托管的,供托管代码调用,另外一个是非托管的,供非托管代码调用。但是函数地址,特别是虚函数指针只能有一个值,所以需要有一个默认的入口。

非托管入口点可能是所有调用的默认入口(在 Visual Studio .NET2003 中,编译器总是会选择非托管入口,但是在Visual Studio 2005中,如果参数或者返回值中包含托管类型,那么编译器会选择托管入口),而另外一个只是使用托管C++中的互操作功能对默认入口的调用。在一个托管函数被另一个托管函数调用的时候,这可能会造成不必要的托管/非托管上下文切换和参数/返回值的复制。如果函数不会被非托管代码使用指针调用,那么可以在声明函数时用VC2005新增的__clrcall修饰符阻止编译器生成两个入口。
现在用简单的冒泡排序算法来比较一下使用__clrcall之后的性能改善程度。

using namespace System; #define ARRAY_SIZE 1000 struct bubbleBase { int value; }; class bubble1:public bubbleBase { public: virtual int getvalue(){return value;} virtual void setvalue(int newvalue){value=newvalue;} }; class bubble2:public bubbleBase { public: virtual int __clrcall getvalue(){return value;} virtual void __clrcall setvalue(int newvalue){value=newvalue;} }; template<class T> void bubbleSort(int length) { TimeSpan ts; T* array1=new T[ARRAY_SIZE]; for (int i=0;i<ARRAY_SIZE ;i++) { array1[i].setvalue(ARRAY_SIZE-i-1); } Int64 ticks=DateTime::Now.Ticks; int i, j,temp, test; for(i = length - 1; i > 0; i--) { test=0; for(j = 0; j < i; j++) { if(array1[j].getvalue() > array1[j+1].getvalue()) { temp = array1[j].getvalue(); array1[j].setvalue(array1[j+1].getvalue()); array1[j+1] .setvalue(temp); test=1; } } if(test==0) break; } ts=TimeSpan::FromTicks(DateTime::Now.Ticks-ticks); Console::WriteLine("BubbleSort {0} Items: {1} Ticks", ARRAY_SIZE, ts.Ticks ); delete array1; } int main(array<System::String ^> ^args) { bubbleSort<bubble1>(ARRAY_SIZE); bubbleSort<bubble2>(ARRAY_SIZE); return 0; }

运行结果是
BubbleSort 1000 Items: 3281250 Ticks
BubbleSort 1000 Items: 312500 Ticks
可以看到,__clrcall会大大加快在托管代码中调用托管函数的速度。

顺便说一下,在随VC8.0发布的STL中增加了很多安全特性,但是这也会造成程序的运行速度减慢。如果你确认程序不会有缓冲区溢出或者内存越界访问的问题,那么可以把_SECURE_SCL定义成0来关掉这个特性。
参考

posted on 2005-10-07 00:12:00 by jiangsheng  评论(1) 阅读(4535)

西雅图MVP峰会见闻

个人觉得这次MVP峰会最大的进步就是技术相关的Session数量大大增加,按照MVP专长来分类;而不像上次那样按主题分类。我只需要在VC产品组的日程里面选择就可以了,而不是像上回那样不得不去听移动开发。当然这回也有MVP不去参加VC的Session,跑去听IE和移动开发。内容方面也比上次活泼很多,Don Box还是那么幽默,比尔·盖茨也有搞笑的演出,不过他看起来比去年七月份在北京的时候老多了。

一些可能有人会感兴趣的技术信息

一些建议

  • 停止开发新的面向Win9x的程序和静态链接MFC的程序。使用新的MFC版本编译旧的程序来增加应用程序的安全性。
  • 在新的程序中使用Unicode编码,同时尽可能将现有程序移植到Unicode。
  • 移植到Visual C++ 2005来使用强大的编译器和调试器。

尽管限于保密协议我不能说得更多,但是微软在11月7号就会正式发布Visual Studio 2005、SQL Server 2005和BizTalk Server 2006了。新的Visual Studio版本(代号Orca和Hawaii)也正在规划中。

posted on 2005-10-03 14:42:00 by jiangsheng  评论(8) 阅读(4685)

Powered by: Joycode.MVC引擎 0.5.2.0