如何通过其HWND句柄更改另一个进程中TDateTimePicker控件中当前选中的日期?

c00*_*0fd 0 c c++ delphi winapi

我正在编写一个自定义模块来使用专有软件.(该软件已停产,我没有其源代码.)我的模块将作为一个单独的进程运行.其目标是通过该专有软件实现操作自动化.为此,我需要能够在TDateTimePicker控件中选择特定日期.我知道这是一个Delphi控件,但就我对Delphi/Pascal的了解而言.我可以找到HWND这个控件的句柄.

所以我的问题 - 有没有办法在该控件中设置一个日期只能通过外部进程的句柄(使用WinAPI)

Rem*_*eau 8

您可以向DTM_SETSYSTEMTIMEDTP 发送消息HWND.但是,该消息将指向SYSTEMTIME记录的指针作为参数,并且该指针必须在拥有DTP控件的进程的地址空间中有效.

DTM_SETSYSTEMTIME不是当跨进程边界发送的,所以如果你用一个指针指向一个由Windows自动编组SYSTEMTIME由发送进程所拥有并把它作为,是为DTP过程中,这是行不通的.您必须手动将SYSTEMTIME数据封送到DTP进程,例如:

uses
  ..., CommCtrl;

var
  Wnd: HWND;
  Pid: DWORD;
  hProcess: THandle;
  ST: TSystemTime;
  PST: PSystemTime;
  Written: SIZE_T;
begin
  Wnd := ...; // the HWND of the DateTimePicker control
  DateTimeToSystemTime(..., ST); // the desired date/time value

  // open a handle to the DTP's owning process...
  GetWindowThreadProcessId(Wnd, Pid);
  hProcess := OpenProcess(PROCESS_VM_WRITE or PROCESS_VM_OPERATION, FALSE, Pid);
  if hProcess = 0 then RaiseLastOSError;
  try
    // allocate a SYSTEMTIME record within the address space of the DTP process...
    PST := PSystemTime(VirtualAllocEx(hProcess, nil, SizeOf(ST), MEM_COMMIT, PAGE_READWRITE));
    if PST = nil then RaiseLastOSError;
    try
      // copy the SYSTEMTIME data into the DTP process...
      if not WriteProcessMemory(hProcess, PST, @ST, SizeOf(ST), Written) then RaiseLastOSError;
      // now send the DTP message, specifying the memory address that belongs to the DTP process...
      SendMessage(Wnd, DTM_SETSYSTEMTIME, GDT_VALID, LPARAM(PST));
    finally
      // free the SYSTEMTIME memory...
      VirtualFreeEx(hProcess, PST, SizeOf(ST), MEM_DECOMMIT);
    end;
  finally
    // close the process handle...
    CloseHandle(hProcess);
  end;
end;
Run Code Online (Sandbox Code Playgroud)

现在,有了这个说法,还有另一个问题TDateTimePicker(通常不是DTP控制). TDateTimePicker 使用该DTM_GETSYSTEMTIME消息来检索当前选择的日期/时间.它的Date/ Time属性只返回在以下情况下TDateTime更新的内部变量的当前值:

  1. TDateTimePicker最初创建,其中的日期/时间被设定为Now().

  2. Date/ Time属性由应用程序分配,可以是代码或DFM流.

  3. 它会收到一个DTN_DATETIMECHANGE带有新日期/时间值的通知.

在这种情况下,你想要#3发生.但是,DTN_DATETIMECHANGE(基于WM_NOTIFY)不会自动生成DTM_SETSYSTEMTIME,因此您必须伪造它,但WM_NOTIFY 不能跨进程边界发送(Windows不允许它 - Raymond Chen 解释了一些原因).这在MSDN上有记录:

对于Windows 2000及更高版本的系统,无法在进程之间发送WM_NOTIFY消息.

因此,您必须在DTP拥有的进程中注入一些自定义代码,DTN_DATETIMECHANGE以便在与DTP相同的进程内发送.将代码注入另一个进程并不容易实现.然而,在这个特殊情况下,有一个相当简单的解决方案,礼貌David Ching:

https://groups.google.com/d/msg/microsoft.public.vc.mfc/QMAHlPpEQyM/Nu9iQycmEykJ

正如其他人指出的那样,LPARAM中的指针需要与创建hwnd的线程驻留在同一个进程中......我创建了一个SendMessageRemote()API,它使用VirtualAlloc,ReadProcessMemory,WriteProcessMemory和CreateRemoteThread来完成繁重的工作. ..

http://www.dcsoft.com/private/sendmessageremote.h
http://www.dcsoft.com/private/sendmessageremote.cpp

它基于一篇很棒的CodeProject文章:
http://www.codeproject.com/threads/winspy.asp.

这是他的代码的Delphi翻译.注意,我已经在32位测试它,它的工作原理,但我没有在64位测试它.将消息从32位进程发送到64位进程或反之亦然,或者如果目标DTP使用的是Ansi窗口而不是Unicode窗口时,您可能需要调整它:

const
  MAX_BUF_SIZE = 512;

type
  LPFN_SENDMESSAGE = function(Wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;

  PINJDATA = ^INJDATA;
  INJDATA = record
    fnSendMessage: LPFN_SENDMESSAGE;    // pointer to user32!SendMessage
    hwnd: HWND;
    msg: UINT;
    wParam: WPARAM;
    arrLPARAM: array[0..MAX_BUF_SIZE-1] of Byte;
  end;

function ThreadFunc(pData: PINJDATA): DWORD; stdcall;
begin
  Result := pData.fnSendMessage(pData.hwnd, pData.msg, pData.wParam, LPARAM(@pData.arrLPARAM));
end;

procedure AfterThreadFunc;
begin
end;

function SendMessageRemote(dwProcessId: DWORD; hwnd: HWND; msg: UINT; wParam: WPARAM; pLPARAM: Pointer; sizeLParam: size_t): LRESULT;
var
  hProcess: THandle;    // the handle of the remote process
  hUser32: THandle;
  DataLocal: INJDATA;
  pDataRemote: PINJDATA;    // the address (in the remote process) where INJDATA will be copied to;
  pCodeRemote: Pointer; // the address (in the remote process) where ThreadFunc will be copied to;
  hThread: THandle; // the handle to the thread executing the remote copy of ThreadFunc;
  dwThreadId: DWORD;
  dwNumBytesXferred: SIZE_T; // number of bytes written/read to/from the remote process;
  cbCodeSize: Integer;
  lSendMessageResult: DWORD;
begin
  Result := $FFFFFFFF;

  hUser32 := GetModuleHandle('user32');
  if hUser32 = 0 then RaiseLastOSError;

  // Initialize INJDATA
  @DataLocal.fnSendMessage := GetProcAddress(hUser32, 'SendMessageW');
  if not Assigned(DataLocal.fnSendMessage) then RaiseLastOSError;

  DataLocal.hwnd := hwnd;
  DataLocal.msg := msg;
  DataLocal.wParam := wParam;

  Assert(sizeLParam <= MAX_BUF_SIZE);
  Move(pLPARAM^, DataLocal.arrLPARAM, sizeLParam);

  // Copy INJDATA to Remote Process
  hProcess := OpenProcess(PROCESS_CREATE_THREAD or PROCESS_QUERY_INFORMATION or PROCESS_VM_OPERATION or PROCESS_VM_WRITE or PROCESS_VM_READ, FALSE, dwProcessId);
  if hProcess = 0 then RaiseLastOSError;
  try
    // 1. Allocate memory in the remote process for INJDATA
    // 2. Write a copy of DataLocal to the allocated memory
    pDataRemote := PINJDATA(VirtualAllocEx(hProcess, nil, sizeof(INJDATA), MEM_COMMIT, PAGE_READWRITE));
    if pDataRemote = nil then RaiseLastOSError;
    try
      if not WriteProcessMemory(hProcess, pDataRemote, @DataLocal, sizeof(INJDATA), dwNumBytesXferred) then RaiseLastOSError;

      // Calculate the number of bytes that ThreadFunc occupies
      cbCodeSize := Integer(LPBYTE(@AfterThreadFunc) - LPBYTE(@ThreadFunc));

      // 1. Allocate memory in the remote process for the injected ThreadFunc
      // 2. Write a copy of ThreadFunc to the allocated memory
      pCodeRemote := VirtualAllocEx(hProcess, nil, cbCodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
      if pCodeRemote = nil then RaiseLastOSError;
      try
        if not WriteProcessMemory(hProcess, pCodeRemote, @ThreadFunc, cbCodeSize, dwNumBytesXferred) then RaiseLastOSError;

        // Start execution of remote ThreadFunc
        hThread := CreateRemoteThread(hProcess, nil, 0, pCodeRemote, pDataRemote, 0, dwThreadId);
        if hThread = 0 then RaiseLastOSError;
        try
          WaitForSingleObject(hThread, INFINITE);

          // Copy LPARAM back (result is in it)
          if not ReadProcessMemory(hProcess, @pDataRemote.arrLPARAM, pLPARAM, sizeLParam, dwNumBytesXferred) then RaiseLastOSError;
        finally
          GetExitCodeThread(hThread, lSendMessageResult);
          CloseHandle(hThread);
          Result := lSendMessageResult;
        end;
      finally
        VirtualFreeEx(hProcess, pCodeRemote, 0, MEM_RELEASE);
      end;
    finally
      VirtualFreeEx(hProcess, pDataRemote, 0, MEM_RELEASE);
    end;
  finally
    CloseHandle(hProcess);
  end;
end;
Run Code Online (Sandbox Code Playgroud)

现在,操作DTP的代码变得更加简单:

uses
  ..., CommCtrl;

var
  Wnd: HWND;
  Pid: DWORD;
  nm: TNMDateTimeChange;
begin
  Wnd := ...; // the HWND of the DateTimePicker control

  // get PID of DTP's owning process
  GetWindowThreadProcessId(Wnd, Pid);

  // prepare DTP message data
  nm.nmhdr.hwndFrom := Wnd;
  nm.nmhdr.idFrom := GetDlgCtrlID(Wnd); // VCL does not use CtrlIDs, but just in case
  nm.nmhdr.code := DTN_DATETIMECHANGE;
  nm.dwFlags := GDT_VALID;
  DateTimeToSystemTime(..., nm.st); // the desired date/time value

  // now send the DTP messages from within the DTP process...
  if SendMessageRemote(Pid, Wnd, DTM_SETSYSTEMTIME, GDT_VALID, @nm.st, SizeOf(nm.st)) <> 0 then
    SendMessageRemote(Pid, GetParent(Wnd), WM_NOTIFY, nm.nmhdr.idFrom, @nm, sizeof(nm));
end;
Run Code Online (Sandbox Code Playgroud)

如果一切顺利,TDateTimePicker现在将更新其内部TDateTime变量以匹配SYSTEMTIME您发送给它的内部变量.