如何获得字符串的价值?

时间:2022-01-06 20:53:52
{ *
  * AControl: Control handle determined by Spy++ (e.g. 0037064A)
  * ANewText: Text to assign to control
  * AWinTitle: Window Title/Caption
  * }
function ControlSetText(const AControl, ANewText, AWinTitle: string): boolean;
  function EnumChildren(AWindowHandle: HWND; ALParam: lParam): bool; stdcall;
  begin
    ShowMessage(AControl); // if commented out - code works fine
    TStrings(ALParam).Add(IntToStr(GetDlgCtrlID(AWindowHandle)));
    Result := true;
  end;

var
  _MainWindowHandle: HWND;
  _WindowControlList: TStringlist;
  i: integer;
  _ControlHandle: integer;
begin
  Result := false;
  _MainWindowHandle := FindWindow(nil, PWideChar(AWinTitle));
  if _MainWindowHandle <> 0 then
  begin
    _WindowControlList := TStringlist.Create;
    try
      if TryStrToInt('$' + Trim(AControl), _ControlHandle) then
        try
          EnumChildWindows(_MainWindowHandle, @EnumChildren,
            UINT_PTR(_WindowControlList));
          for i := 0 to _WindowControlList.Count - 1 do
          begin
            if (StrToInt(_WindowControlList[i]) = _ControlHandle)
            then
            begin
              SendMessage(StrToInt(_WindowControlList[i]), WM_SETTEXT, 0,
                integer(PCHAR(ANewText)));
              Result := true;
            end;
          end;
        except
          on E: Exception do
            MessageDlg(E.Message, TMsgDlgType.mtError, [TMsgD*n.mbOK], 0)
        end;
    finally
      FreeAndNil(_WindowControlList);
    end;
  end;
end;

The debugger raises an exception with the message

调试器会使用该消息引发异常

--------------------------- Debugger Exception Notification ---------------------------

---------------------------调试器异常通知-------------------- -------

Project Default_project.exe raised exception class $C0000005 with message 'access violation at 0x00406fae: write of address 0x00408dbb'.

项目Default_project.exe引发异常类$ C0000005,消息'访问冲突位于0x00406fae:写入地址0x00408dbb'。

It breaks at:

它打破了:

for i := 0 to _WindowControlList.Count - 1 do

I call it like this:

我称之为:

ControlSetText('00070828', 'New TEdit text', 'Delphi_test_app');

I am planning an update, so, not only control handle could be passed, but also control type+identifier e.g. 'Edit1'.

我正在计划更新,因此,不仅可以传递控制句柄,还可以控制类型+标识符,例如'EDIT1'。

EDIT:

What I am trying is to do is to implement http://www.autohotkey.com/docs/commands/ControlSetText.htm

我想要做的是实现http://www.autohotkey.com/docs/commands/ControlSetText.htm

2 个解决方案

#1


1  

The root cause of your crash is that your are using an inner function as the EnumChildWindows() callback and it is referencing a parameter from its outer function, which will not work (and why it does work when you comment out the access of that parameter). The call stack frame is not what EnumChildWindows() is expecting. You need to make the inner function be a standalone function instead.

崩溃的根本原因是您正在使用内部函数作为EnumChildWindows()回调,并且它正在引用来自其外部函数的参数,这将无法工作(以及当您注释掉该参数的访问权限时它为什么会起作用)。调用堆栈帧不是EnumChildWindows()所期望的。您需要使内部函数成为独立函数。

With that said, there is another bug in your code. Even if the above worked, your code would still fail because you are storing child Control IDs in your TStringList but then using them as if they were HWND values instead. They are not!

话虽如此,您的代码中还有另一个错误。即使上述工作正常,您的代码仍然会失败,因为您将子控件ID存储在TStringList中,然后使用它们就好像它们是HWND值一样。他们不是!

Try something more like this:

尝试更像这样的东西:

uses
  ..., System.Generics.Collections;

{ *
  * AControl: Control handle determined by Spy++ (e.g. 0037064A)
  * ANewText: Text to assign to control
  * AWinTitle: Window Title/Caption
  * }

type
  THWndList = TList<HWND>;

function EnumChildren(AWindowHandle: HWND; AParam: LPARAM): BOOL; stdcall;
begin
  THWndList(AParam).Add(AWindowHandle);
  Result := TRUE;
end;

function TryStrToHWnd(const AStr: String; var Wnd: HWND): Boolean;
begin
  {$IFDEF WIN64}
  Result := TryStrToInt64(AStr, Int64(Wnd));
  {$ELSE}
  Result := TryStrToInt(AStr, Integer(Wnd));
  {$ENDIF}
end;

function ControlSetText(const AControl, ANewText, AWinTitle: String): Boolean;
var
  _MainWindowHandle: HWND;
  _WindowControlList: THWndList;
  i: integer;
  _ControlHandle: HWND;
  EnumInfo: TEnumInfo;
begin
  Result := False;
  _MainWindowHandle := FindWindow(nil, PChar(AWinTitle));
  if _MainWindowHandle <> 0 then
  begin
    _WindowControlList := THWndList;
    try
      if TryStrToHWnd('$' + Trim(AControl), _ControlHandle) then
      try
        EnumChildWindows(_MainWindowHandle, @EnumChildren, LPARAM(_WindowControlList));
        for i := 0 to _WindowControlList.Count - 1 do
        begin
          if (_WindowControlList[i] = _ControlHandle) then
          begin
            Result := SendMessage(_WindowControlList[i], WM_SETTEXT, 0, LPARAM(PChar(ANewText))) = 1;
            Break;
          end;
        end;
      except
        on E: Exception do
          MessageDlg(E.Message, TMsgDlgType.mtError, [TMsgD*n.mbOK], 0);
      end;
    finally
      FreeAndNil(_WindowControlList);
    end;
  end;
end;

Alternatively:

{ *
  * AControl: Control handle determined by Spy++ (e.g. 0037064A)
  * ANewText: Text to assign to control
  * AWinTitle: Window Title/Caption
  * }

type
  PEnumInfo = ^TEnumInfo;
  TEnumInfo = record
    Control: HWND;
    Found: Boolean;
   end;

function EnumChildren(AWindowHandle: HWND; AParam: LPARAM): BOOL; stdcall;
begin
  PEnumInfo(AParam).Found := (AWindowHandle = PEnumInfo(AParam).Control);
  Result := not PEnumInfo(AParam).Found;
end;

function TryStrToHWnd(const AStr: String; var Wnd: HWND): Boolean;
begin
  {$IFDEF WIN64}
  Result := TryStrToInt64(AStr, Int64(Wnd));
  {$ELSE}
  Result := TryStrToInt(AStr, Integer(Wnd));
  {$ENDIF}
end;

function ControlSetText(const AControl, ANewText, AWinTitle: String): Boolean;
var
  _MainWindowHandle: HWND;
  _ControlHandle: HWND;
  EnumInfo: TEnumInfo;
begin
  Result := False;
  _MainWindowHandle := FindWindow(nil, PChar(AWinTitle));
  if _MainWindowHandle <> 0 then
  begin
    if TryStrToHWnd('$' + Trim(AControl), _ControlHandle) then
    try
      EnumInfo.Control := _ControlHandle;
      EnumInfo.Found := False;
      EnumChildWindows(_MainWindowHandle, @EnumChildren, LPARAM(@EnumInfo));
      if EnumInfo.Found then
      begin
        Result := SendMessage(_ControlHandle, WM_SETTEXT, 0, LPARAM(PChar(ANewText))) = 1;
      end;
    except
      on E: Exception do
        MessageDlg(E.Message, TMsgDlgType.mtError, [TMsgD*n.mbOK], 0);
    end;
  end;
end;

Or just get rid of EnumChilWindows() and let Windows validate the HWND you try to send to:

或者只是摆脱EnumChilWindows()并让Windows验证您尝试发送到的HWND:

{ *
  * AControl: Control handle determined by Spy++ (e.g. 0037064A)
  * ANewText: Text to assign to control
  * }

function TryStrToHWnd(const AStr: String; var Wnd: HWND): Boolean;
begin
  {$IFDEF WIN64}
  Result := TryStrToInt64(AStr, Int64(Wnd));
  {$ELSE}
  Result := TryStrToInt(AStr, Integer(Wnd));
  {$ENDIF}
end;

function ControlSetText(const AControl, ANewText: String): Boolean;
var
  _ControlHandle: HWND;
begin
  Result := TryStrToHWnd('$' + Trim(AControl), _ControlHandle) and
    (SendMessage(_ControlHandle, WM_SETTEXT, 0, LPARAM(PChar(ANewText))) = 1);
end;

#2


4  

The problem is that your callback is a local nested function. That is it is nested inside ControlSetText. It must be declared at global scope.

问题是你的回调是一个本地嵌套函数。那就是它嵌套在ControlSetText中。它必须在全球范围内声明。

Any extra state information must be passed in through the lParam parameter.

必须通过lParam参数传递任何额外的状态信息。

I also find it odd that you store integers and pointers in strings. Store them as integers or pointers.

我也觉得奇怪的是你在字符串中存储整数和指针。将它们存储为整数或指针。

In fact it is more than odd. You put control ids in the list, as strings, but then use them as window handles. So once you get past the crash the code won't work. I don't want to get into debugging that in this question.

事实上,这不仅仅是奇怪的。您将控件ID放在列表中,作为字符串,但然后将它们用作窗口句柄。因此,一旦您通过崩溃,代码将无法正常工作。我不想在这个问题中进行调试。

#1


1  

The root cause of your crash is that your are using an inner function as the EnumChildWindows() callback and it is referencing a parameter from its outer function, which will not work (and why it does work when you comment out the access of that parameter). The call stack frame is not what EnumChildWindows() is expecting. You need to make the inner function be a standalone function instead.

崩溃的根本原因是您正在使用内部函数作为EnumChildWindows()回调,并且它正在引用来自其外部函数的参数,这将无法工作(以及当您注释掉该参数的访问权限时它为什么会起作用)。调用堆栈帧不是EnumChildWindows()所期望的。您需要使内部函数成为独立函数。

With that said, there is another bug in your code. Even if the above worked, your code would still fail because you are storing child Control IDs in your TStringList but then using them as if they were HWND values instead. They are not!

话虽如此,您的代码中还有另一个错误。即使上述工作正常,您的代码仍然会失败,因为您将子控件ID存储在TStringList中,然后使用它们就好像它们是HWND值一样。他们不是!

Try something more like this:

尝试更像这样的东西:

uses
  ..., System.Generics.Collections;

{ *
  * AControl: Control handle determined by Spy++ (e.g. 0037064A)
  * ANewText: Text to assign to control
  * AWinTitle: Window Title/Caption
  * }

type
  THWndList = TList<HWND>;

function EnumChildren(AWindowHandle: HWND; AParam: LPARAM): BOOL; stdcall;
begin
  THWndList(AParam).Add(AWindowHandle);
  Result := TRUE;
end;

function TryStrToHWnd(const AStr: String; var Wnd: HWND): Boolean;
begin
  {$IFDEF WIN64}
  Result := TryStrToInt64(AStr, Int64(Wnd));
  {$ELSE}
  Result := TryStrToInt(AStr, Integer(Wnd));
  {$ENDIF}
end;

function ControlSetText(const AControl, ANewText, AWinTitle: String): Boolean;
var
  _MainWindowHandle: HWND;
  _WindowControlList: THWndList;
  i: integer;
  _ControlHandle: HWND;
  EnumInfo: TEnumInfo;
begin
  Result := False;
  _MainWindowHandle := FindWindow(nil, PChar(AWinTitle));
  if _MainWindowHandle <> 0 then
  begin
    _WindowControlList := THWndList;
    try
      if TryStrToHWnd('$' + Trim(AControl), _ControlHandle) then
      try
        EnumChildWindows(_MainWindowHandle, @EnumChildren, LPARAM(_WindowControlList));
        for i := 0 to _WindowControlList.Count - 1 do
        begin
          if (_WindowControlList[i] = _ControlHandle) then
          begin
            Result := SendMessage(_WindowControlList[i], WM_SETTEXT, 0, LPARAM(PChar(ANewText))) = 1;
            Break;
          end;
        end;
      except
        on E: Exception do
          MessageDlg(E.Message, TMsgDlgType.mtError, [TMsgD*n.mbOK], 0);
      end;
    finally
      FreeAndNil(_WindowControlList);
    end;
  end;
end;

Alternatively:

{ *
  * AControl: Control handle determined by Spy++ (e.g. 0037064A)
  * ANewText: Text to assign to control
  * AWinTitle: Window Title/Caption
  * }

type
  PEnumInfo = ^TEnumInfo;
  TEnumInfo = record
    Control: HWND;
    Found: Boolean;
   end;

function EnumChildren(AWindowHandle: HWND; AParam: LPARAM): BOOL; stdcall;
begin
  PEnumInfo(AParam).Found := (AWindowHandle = PEnumInfo(AParam).Control);
  Result := not PEnumInfo(AParam).Found;
end;

function TryStrToHWnd(const AStr: String; var Wnd: HWND): Boolean;
begin
  {$IFDEF WIN64}
  Result := TryStrToInt64(AStr, Int64(Wnd));
  {$ELSE}
  Result := TryStrToInt(AStr, Integer(Wnd));
  {$ENDIF}
end;

function ControlSetText(const AControl, ANewText, AWinTitle: String): Boolean;
var
  _MainWindowHandle: HWND;
  _ControlHandle: HWND;
  EnumInfo: TEnumInfo;
begin
  Result := False;
  _MainWindowHandle := FindWindow(nil, PChar(AWinTitle));
  if _MainWindowHandle <> 0 then
  begin
    if TryStrToHWnd('$' + Trim(AControl), _ControlHandle) then
    try
      EnumInfo.Control := _ControlHandle;
      EnumInfo.Found := False;
      EnumChildWindows(_MainWindowHandle, @EnumChildren, LPARAM(@EnumInfo));
      if EnumInfo.Found then
      begin
        Result := SendMessage(_ControlHandle, WM_SETTEXT, 0, LPARAM(PChar(ANewText))) = 1;
      end;
    except
      on E: Exception do
        MessageDlg(E.Message, TMsgDlgType.mtError, [TMsgD*n.mbOK], 0);
    end;
  end;
end;

Or just get rid of EnumChilWindows() and let Windows validate the HWND you try to send to:

或者只是摆脱EnumChilWindows()并让Windows验证您尝试发送到的HWND:

{ *
  * AControl: Control handle determined by Spy++ (e.g. 0037064A)
  * ANewText: Text to assign to control
  * }

function TryStrToHWnd(const AStr: String; var Wnd: HWND): Boolean;
begin
  {$IFDEF WIN64}
  Result := TryStrToInt64(AStr, Int64(Wnd));
  {$ELSE}
  Result := TryStrToInt(AStr, Integer(Wnd));
  {$ENDIF}
end;

function ControlSetText(const AControl, ANewText: String): Boolean;
var
  _ControlHandle: HWND;
begin
  Result := TryStrToHWnd('$' + Trim(AControl), _ControlHandle) and
    (SendMessage(_ControlHandle, WM_SETTEXT, 0, LPARAM(PChar(ANewText))) = 1);
end;

#2


4  

The problem is that your callback is a local nested function. That is it is nested inside ControlSetText. It must be declared at global scope.

问题是你的回调是一个本地嵌套函数。那就是它嵌套在ControlSetText中。它必须在全球范围内声明。

Any extra state information must be passed in through the lParam parameter.

必须通过lParam参数传递任何额外的状态信息。

I also find it odd that you store integers and pointers in strings. Store them as integers or pointers.

我也觉得奇怪的是你在字符串中存储整数和指针。将它们存储为整数或指针。

In fact it is more than odd. You put control ids in the list, as strings, but then use them as window handles. So once you get past the crash the code won't work. I don't want to get into debugging that in this question.

事实上,这不仅仅是奇怪的。您将控件ID放在列表中,作为字符串,但然后将它们用作窗口句柄。因此,一旦您通过崩溃,代码将无法正常工作。我不想在这个问题中进行调试。