您的位置:首页 > 其它

修改启动的进程的窗口标题

2013-10-13 18:16 127 查看
最近在改一个项目里的小功能:原先的情况是在网页上点击按钮,ocx控件写临时.rdp文件,根据这个文件启动mstsc.exe。现在要做的工作是把远程桌面连接窗口的标题改成能显示特定信息的标题。

感谢 http://blog.csdn.net/icedmilk/article/details/5278371,还有http://www.cctry.com/forum.php?mod=viewthread&tid=11857&page=1&authorid=51等。
程序调用mstsc,修改mstsc窗口名。

1、调用通过CreateProcess函数来实现;

/***************************************/
BOOL WINAPI CreateProcess
(
_In_opt_     LPCTSTR lpApplicationName,
_Inout_opt_  LPTSTR lpCommandLine,
_In_opt_     LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_     LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_         BOOL bInheritHandles,
_In_         DWORD dwCreationFlags,
_In_opt_     LPVOID lpEnvironment,
_In_opt_     LPCTSTR lpCurrentDirectory,
_In_         LPSTARTUPINFO lpStartupInfo,
_Out_        LPPROCESS_INFORMATION lpProcessInformation
);
//最后一个参数返回新进程的信息
typedef struct _PROCESS_INFORMATION
{
HANDLE hProcess;
HANDLE hThread;
DWORD  dwProcessId;
DWORD  dwThreadId;
} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
/***************************************/

DWORD CreateProcessMstsc(STARTUPINFO *si,PROCESS_INFORMATION *pi, CString strComLine)
{
memset(si,0,sizeof(STARTUPINFO));
si->cb=sizeof(STARTUPINFO);

LPSTR szCommandLine;
szCommandLine=strComLine.GetBuffer(strComLine.GetLength());
if(!CreateProcess(NULL,szCommandLine,NULL,NULL,FALSE,0,NULL,NULL,si,pi))
{
return 0;
}
return pi->dwProcessId;
}


2、得到mstsc进程的pid。XP下mstsc的pid就是pi.dwProcessId。Win7下CreateProcess打开mstsc时,会先起一个中间进程,再由中间进程起mstsc,所以不能简单地把pi.dwProcessId作为mstsc的pid,这里还要找mstsc的pid。

DWORD find_sub_pid(DWORD parentpid)
{
PROCESSENTRY32 pe32={0};
pe32.dwSize=sizeof(PROCESSENTRY32);

BOOL bRet;
DWORD found_pid=0;
HANDLE hProcessSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,NULL);
bRet=Process32First(hProcessSnap,&pe32);

while(bRet&&g_found_flag==0)
{
if(pe32.th32ParentProcessID==parentpid)
{
found_pid = pe32.th32ProcessID;
break;
}
bRet=Process32Next(hProcessSnap,&pe32);
}
CloseHandle(hProcessSnap);
return found_pid;
}


上面的代码不具有通用性,鉴于中间进程只会起mstsc这一个进程,所以在处理时,只要满足,就返回退出了。如果中间进程起了好几个进程,返回哪个进程还要做个判断。

3、通过mstsc的pid来找到正确的窗口。网上查阅资料,没有找到好的通过pid找HWND的方法,基本都是枚举窗口,对比窗口所属进程pid和目标pid。每个进程有多个窗口,必须要判断哪个窗口是目标窗口。

BOOL CALLBACK EnumWindowsCallBack(HWND hwnd, LPARAM lParam)
{
//printf("进入处理函数\n");
DWORD ProcID=0;
GetWindowThreadProcessId(hwnd,&ProcID);

if(ProcID==lParam)
{
//下面开始验证这个窗口的窗口类名是不是我想要的,以及其他的限制条件
char lpWinClass[256]={0};
GetClassNameA(hwnd,lpWinClass,sizeof(lpWinClass)-1);
if((strcmp(lpWinClass,"TSSHELLWND")==0)||(strcmp(lpWinClass,"TscShellContainerClass")==0))
{
g_hwnd_to_find=hwnd;
g_found_flag=1;
return FALSE;
}
}
return TRUE;
}

int find_window_through_pid(DWORD pid)
{
EnumWindows(EnumWindowsCallBack,pid);
return 0;
}


4、通过HWND设置窗口标题。由于mstsc窗口并不是立刻显示在屏幕上的,如果设置标题比窗口显示出来早,那窗口显示的时候会把已经设置好的标题给改回来(这个问题困扰了我好久),所以在这里要判断一下窗口的显示状态,结合Sleep等来使用。

while(!(::IsWindowVisible(g_hwnd_to_find)))
{
Sleep(100);
count++;
if((count%20)==0)
{
if(find_pid(pid_to_find)==0)
{
break;
}
}
}
::SetWindowTextA(g_hwnd_to_find,IPRemAddr);


5、总结:

我是个菜鸟,上面这段程序前前后后搞了一个多星期,在关键性问题上还是实验室的大牛崔哥给解决的,犯了很多错误,现指出如下,希望看到的人也能有所启发。

不能通过投机取巧的方式(比如枚举窗口,将含有“远程桌面连接”的标题的窗口改掉),这方法不可靠,而且还会带来其他不需要改的窗口被篡改的后果;

Process会有多个Thread,Thread会有多个HWND,在做查找时不能想当然地把他们之间的关系当做是一对一的;

单步调试正确,但是执行不正确的程序,多半是和时序有关系(比如第4点说的,没有加判断条件的时候,单步调试正确,直接执行就有问题了);

找准目标,简化问题,不要被中间过程搞乱。mstsc的启动过程,中间有好几个弹窗(可能有3个甚至更多,也可能1个也没有,看怎么起),只要知道最后的目标是mstsc的窗口,窗口类型是什么即可。

6、知识点:

6、1 关于Process Thread HANDLE HINSTANCE HMODULE HWND

进程
被当前操作系统加载到内存的、正在运行的应用程序的实例。每一个进程都是由内核对象和地址空间所组成的,内核对象可以让系统在其内存放有关进程的统计信息并使系统能够以此来管理进程,而内核空间则包括了所有程序模块的代码和数据以及线程堆栈、堆分配空间等动态分配的空间。

线程
进程仅仅是一个存在,是不能独自完任何操作的,必须拥有至少一个在其环境下运行的线程,并由其负责执行在进程地址空间内的代码。在进程启动的同时即同时启动了一个线程,该线程被称作主线程或是执行线程,游戏线程可以继续创建子线程。如果主线程退出,那么进程也就没有存在的可能了,系统将自动撤销该进程并完成对其地址空间的释放。

HANDLE

句柄是一种特殊的智能指针。当一个应用程序需要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。

主要是代表系统的内核对象,如文件句柄,线程句柄,进程句柄。

系统对内核对象以链表的形式进行管理,载入到内存中的每一个内核对象都有一个线性地址,同时相对系统来说,在串列中有一个索引位置,这个索引位置就是内核对象的handle。 句柄和普通指针的区别在于,指针包含的是引用对象的内存地址,而句柄是由系统所管理的引用标识,该标识可以被系统重新定位到一个内存地址上。这种间接访问对象的模式增强了系统对引用对象的控制。通俗的说,调用句柄就是调用句柄的服务,即句柄已经把他能做的操作都设定好了,我们只能在句柄所提供的操作范围内进行操作,但是普通指针的操作却不受限制。

HINSTANCE HMODULE

A handle to an instance. This is rhe base address of the module in memory. HMODULE and HINSTANCE are the same today, but represented different things in 16-bit Windows.

HWND

typedef HANDLE HWND;

6、2 关于进程如何管理资源

内核对象、GDI对象、和user对象
系统会维护一张进程列表,进程列表里记录着当前的进程,其中包含了pid,进程名,引用数(关于这个引用数比较直观的表现是在Windows资源管理器下面查看进程,勾选上句柄数、线程数、用户对象和GDI对象,可以看到某个进程引用的句柄数、线程数、用户对象数目和GDI对象数)等等。 内核对象的直接拥有者是操作系统内核,所有进程共享这些内核对象,因此要有一种机制保证内核对象的正确构造、销毁,Windows采用引用计数的计数;内核对象维护着一个引用计数成员。一个进程创建了一个内核对象,对象的引用计数为1,如果该对象又被另外的进程共享,每多一个进程引用数就会加1,当一个进程调用CloseHandle之后,引用计数会减1,如果引用计数变为0,操作系统会销毁该内核对象。内核对象使用完之后,要调用CloseHandle。这个函数的作用就是将内核对象的引用计数-1,当这个计数变为0时,改内核对象会变成Signal状态,并被操作系统销毁。

GDI对象和user对象的销毁不需要调用CloseHandle。每一个GDI对象和user对象的销毁都有其对应的Destory或Delete方法。

进程、线程、窗口

一个进程可以拥有多个线程,一个线程可以有多个窗口。

在Windows下遍历进程可以用获取进程快照,然后调用Process32First和Process32Next来完成对进程的逐个访问;
访问线程列表也同样可以获取线程快照,调用Thread32First和Thread32Next来完成对线程的逐个访问;

访问窗口可以通过Windows API函数EnumWindows来完成逐个访问;

要获得某个进程的所有线程可以通过如下方法来完成,获得父进程pid,遍历线程列表,如果线程的THREADENTRY32结构体中th32OwnerProcessID和父进程pid一致,则该线程就是属于父进程的;
要回的某个进程的所有窗口可以通过如下方法来完成,获得进程pid,遍历窗口,在EnumWindows的回调函数里,用GetWindowThreadProcessId得到当前窗口的进程pid,比对这个pid和父进程pid,如果一致,该窗口属于父进程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: