这个标题不太严谨,却也没想到更适合的表述,将就一下吧。

捣鼓桌面程序的人,或者对windows消息机制有所了解的,大概都知道其实windows桌面编程有一个贯穿了绝大多数消息机制的概念,就是容器。

比如一个按钮可以放在panel里,可以放在group里,也可以直接放在窗体上,这时候呢,这些panel、group和窗体就是这个按钮的容器。简而言之就是这个控件的“父容器”。

那么进程或者说sdi窗体的容器是谁呢?是桌面。桌面是一个较特殊的容器。

 

最近遇到个挺难受的问题,putty的新版自然是支持新的openssh交换协议,但是新版本全都没法记忆密码(当然带参数运行是可以的),而我用的0.57那个修改版太老了,每有新服务器就需要对openssh-server进行设置,次数多了也有点烦,于是最近试了一下最新的0.77。

并不意外的,新版仍然无法记忆密码。然后我又尝试了cnputty,当然,一样不行。

而且那个老版本有个我很喜欢的细节,就是当我复制了多行命令的时候,粘贴进去是可以直接运行的。而新版粘贴后显示为文本,需要手工敲一下回车。烦!

 

于是求助于各种管理工具,先后尝试了mremoteng、superputty,各有各的问题,相对而言superputty是比较符合我习惯的,然而这俩货都是.net整出来的,我个人比较反感在服务器上多装任何非必须程序。mremoteng的rdp协议做的很好,我一直在用,但是这货跟putty配合有些问题,那个0.57的版本无法最大化,始终是一个小窗口,这肯定不行。而superputty的rdp感觉太脑残了,基本上就是给mstsc传了个参数,至于用户名密码什么的都还要一次一次手工输入,肯定放弃了。

所以,我又试着自己干了。

 

我需要的功能都实现了,记录一些重点代码:

var 
  hWin: HWND = 0;



// 窗体枚举函数
 
function EnumWindowsProc(Wnd: HWND; ProcWndInfo: PProcessWindow): BOOL; stdcall;
var
  WndProcessID: Cardinal;
begin
  GetWindowThreadProcessId(Wnd, @WndProcessID);
  if WndProcessID = ProcWndInfo^.ProcessID then begin
    ProcWndInfo^.FoundWindow := Wnd;
    Result := False;                                    // 已找到,故停止 EnumWindows
  end
  else
    Result := True;                                     // 继续查找
end;
 
// 由 ProcessID 查找窗体 Handle
 
function GetProcessWindow(ProcessID: Cardinal): HWND;
var
  ProcWndInfo: TProcessWindow;
begin
  ProcWndInfo.ProcessID := ProcessID;
  ProcWndInfo.FoundWindow := 0;
  EnumWindows(@EnumWindowsProc, Integer(@ProcWndInfo)); // 查找窗体
  Result := ProcWndInfo.FoundWindow;
end;
 
// 在 Panel 上内嵌运行程序
 
function RunAppInPanel(const AppFileName: string; ParentHandle: HWND; var WinHandle: HWND): Boolean;
var
  si: STARTUPINFO;
  pi: TProcessInformation;
begin
  Result := False;
 
  // 启动进程
  FillChar(si, SizeOf(si), 0);
  si.cb := SizeOf(si);
  si.wShowWindow := SW_SHOW;
  if not CreateProcess(nil, PChar(AppFileName), nil, nil, true,
    CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil, nil, si, pi) then Exit;
 
  // 等待进程启动
  WaitForInputIdle(pi.hProcess, 10000);
 
  // 取得进程的 Handle
  WinHandle := GetProcessWindow(pi.dwProcessID);
  if WinHandle > 0 then begin
    // 设定父窗体
    Windows.SetParent(WinHandle, ParentHandle);
    ShowWindow( WinHandle, SW_MAXIMIZE);

    // 设定窗体位置
   // SetWindowPos(WinHandle, 0, 0, 0, 0, 0, SWP_NOSIZE or SWP_NOZORDER);
 
    // 去掉标题栏
    SetWindowLong(WinHandle, GWL_STYLE, GetWindowLong(WinHandle, GWL_STYLE) and (not WS_CAPTION) and (not WS_BORDER) and (not WS_THICKFRAME));

    UpdateWindow(WinHandle);

    Result := True;
  end;
 
  // 释放 Handle
  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  // 保持内嵌程序充满 pnlApp
//  if hWin <> 0 then MoveWindow(hWin, 0, 0, Panel1.ClientWidth, Panel1.ClientHeight, True);
if hwin<>0 then
SetForegroundWindow(hwin);
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
 // 退出时向内嵌程序发关闭消息
  if hWin > 0 then PostMessage(hWin, WM_CLOSE, 0, 0);

end;

procedure TForm1.Button2Click(Sender: TObject);
begin
 // Panel1.Align := alClient;
 
  // 启动内嵌程序
  if not RunAppInPanel('D:\Tools\0 putty\0 putty 修改版.exe -ssh -l root -pw www.tingtao.org -C -load zdw -P 22 服务器IP', Panel1.Handle, hWin) then ShowMessage('App not found');
end;

 

其中重点就是CreateProcess和SetParent这两个步骤,或者说是RunAppInPanel这个函数。里面大部分都是对api的调用,所以用什么语言都差不多的。

SetParent,顾名思义,把目标进程的父容器设置为自己的panel,运行结果就是截图。

但是这些代码还并不完善,因为windows桌面程序有一个“当前进程”和“当前窗体”的概念,而把其他进程的容器设置为自己的panel以后呢,还并不能解决“焦点”的事,还需要后续步骤。

做到这一步,我忽然意识到后面要处理的细节太多了,恰好在搜索的过程中找到了个能同时对rdp和ssh都支持很好的软件,测试一天下来各种细节我都觉得满意了。明天分享。

 

 

作者 听涛

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注