1

我想访问另一个进程控制台的缓冲区(通过 AttachConsole),以调用 ReadConsoleOutput 等。

是一个 DOS 16 位应用程序。我不能使用管道,因为它不会连续写入输出(它模拟“windows”......如果你知道我的意思的话,就像 FAR 指挥官一样)。

所以我应该:

1) 启动应用程序 2) 获取进程 ID 3) 调用 AttachConsole(ProcId) 4) 调用 GetConsoleScreenBufferInfo 以获取大小 5) 调用 ReadConsoleOutput

问题出在 3:当我调用 AttachConsole 时,ir 返回 0,在调用 GetLastError 后它报告 ERROR_INVALID_PARAMETER 87 (0x57)。

AttachConsole 的唯一参数是 ProcessId,我用 ProcessExplorer 检查了它是正确的(它实际上是模拟应用程序的 ntvdm.exe 的 PID)。

德尔福代码:

function AttachConsole(dwProcessId: DWORD): Cardinal; external kernel32 name 'AttachConsole';

var
  Handle: HWND;

function EnumWindowsProc(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
var
  s: string;
  IsVisible, IsOwned, IsAppWindow: Boolean;
begin
  Result := True;//carry on enumerating

  IsVisible := IsWindowVisible(hwnd);
  if not IsVisible then
    exit;

  IsOwned := GetWindow(hwnd, GW_OWNER)<>0;
  if IsOwned then
    exit;

  IsAppWindow := GetWindowLongPtr(hwnd, GWL_STYLE) and WS_EX_APPWINDOW<>0;
  if not IsAppWindow then
    exit;

  SetLength(s, GetWindowTextLength(hwnd));
  GetWindowText(hwnd, PChar(s), Length(s)+1);
  if AnsiContainsText(s, '????.EXE') then // set windows name to search
    Handle := hwnd;
end;

procedure Test(Strings: TStrings);
var
  ProcessID: Cardinal;
begin
  Handle := 0;
  EnumWindows(@EnumWindowsProc, 0);
  Strings.Add('Handle: ' + IntToStr(Handle));
  if Handle <> 0 then
    SetForegroundWindow(Handle);
  Sleep(100);

  GetWindowThreadProcessId(Handle, @ProcessID);
  Strings.Add('ProcessId: ' + IntToStr(ProcessID));

  if AttachConsole(ProcessId) <> 0 then
    Strings.Add('Ok Attached')
  else
    Strings.Add('Error: ' + IntToStr(GetLastError));
end;

在表单中放置备忘录和按钮。在 OnClick 调用 Test(Memo1.Lines)。

===== 编辑完整的解决方案 =====

function AttachAndGetConsoleHandle(ProcessId: Cardinal): Cardinal;
begin
  if not AttachConsole(ProcessId) then
    raise Exception.Create('AttachConsole error: ' + IntToStr(GetLastError));

  Result := GetStdHandle(STD_OUTPUT_HANDLE);

  if Result = INVALID_HANDLE_VALUE then
    raise Exception.Create('GetStdHandle(STD_OUTPUT_HANDLE) error: ' + IntToStr(GetLastError));
end;

procedure DettachConsole;
begin
  if not FreeConsole then
    raise Exception.Create('FreeConsole error: ' + IntToStr(GetLastError));
end;

function ReadConsole(ConsoleHandle: Cardinal): TStringList;
var
  BufferInfo: _CONSOLE_SCREEN_BUFFER_INFO;
  BufferSize, BufferCoord: _COORD;
  ReadRegion: _SMALL_RECT;
  Buffer: Array of _CHAR_INFO;
  I, J: Integer;
  Line: AnsiString;
begin
  Result := TStringList.Create;

  ZeroMemory(@BufferInfo, SizeOf(BufferInfo));
  if not GetConsoleScreenBufferInfo(ConsoleHandle, BufferInfo) then
    raise Exception.Create('GetConsoleScreenBufferInfo error: ' + IntToStr(GetLastError));

  SetLength(Buffer, BufferInfo.dwSize.X * BufferInfo.dwSize.Y);

  BufferSize.X := BufferInfo.dwSize.X;
  BufferSize.Y := BufferInfo.dwSize.Y;
  BufferCoord.X := 0;
  BufferCoord.Y := 0;
  ReadRegion.Left := 0;
  ReadRegion.Top := 0;
  ReadRegion.Right := BufferInfo.dwSize.X;
  ReadRegion.Bottom := BufferInfo.dwSize.Y;

  if ReadConsoleOutput(ConsoleHandle, Pointer(Buffer), BufferSize, BufferCoord, ReadRegion) then
  begin
    for I := 0 to BufferInfo.dwSize.Y - 1 do
    begin
      Line := '';
      for J := 0 to BufferInfo.dwSize.X - 1 do
        Line := Line + Buffer[I * BufferInfo.dwSize.X + J].AsciiChar;
      Result.Add(Line)
    end
  end
  else
    raise Exception.Create('ReadConsoleOutput error: ' + IntToStr(GetLastError));
end;
4

1 回答 1

1

定义应该是:

function AttachConsole(dwProcessId: DWORD): BOOL; stdcall; external
kernel32 name 'AttachConsole';

所以它后面的代码应该是:

if AttachConsole(ProcessId) then

不能再帮你了。

于 2012-05-09T06:55:20.877 回答