如何改变当前选定日期的TDateTimePicker控制其HWND手柄另一个进程? [英] How to change currently selected date in TDateTimePicker control in another process by its HWND handle?

查看:388
本文介绍了如何改变当前选定日期的TDateTimePicker控制其HWND手柄另一个进程?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在写一个自定义模块与专有软件工作。 (该软件已经停产了,我没有它的源$ C ​​$ C。)我模块将运行作为一个单独的进程。它的目标是通过这个专有软件来自动操作。要做到这一点,我需要能够选择在的TDateTimePicker 控制的具体日期。我知道这是一个Delphi控制,但是这是据我的德尔福/帕斯卡尔的知识去。我可以找到 HWND 处理此控件虽然。

I'm writing a custom module to work with a proprietary software. (That software has been discontinued and I do not have its source code.) My module will run as a separate process. Its goal is to automate an operation via this proprietary software. To do that I need to be able to select a specific date in the TDateTimePicker control. I know it's a Delphi control, but that's as far as my knowledge of Delphi/Pascal goes. I can find the HWND handle for this control though.

所以我的问题 - 是有办法只有用把手从外部程序来设置该控件的日期的(使用WinAPIs)

So my question -- is there a way to set a date in that control only by its handle from an outside process (using WinAPIs)?

推荐答案

您可以发送的 DTM_SETSYSTEMTIME 消息DTP的 HWND 。不过,该消息需要一个指针 SYSTEMTIME 记录作为参数,并且指针必须在拥有DTP控制进程的地址空间中有效。 DTM_SETSYSTEMTIME 终止的的由Windows时跨进程边界发送自动编组,因此,如果你把一个指针 SYSTEMTIME 由发送进程所拥有,并把它作为-是进入DTP过程中,这是行不通的。您必须在 SYSTEMTIME 数据手动元帅的DTP处理,例如:

You can send a DTM_SETSYSTEMTIME message to the DTP's HWND. However, that message takes a pointer to a SYSTEMTIME record as a parameter, and that pointer MUST be valid in the address space of the process that owns the DTP control. DTM_SETSYSTEMTIME is NOT auto-marshaled by Windows when sent across process boundaries, so if you take a pointer to a SYSTEMTIME owned by the sending process and send it as-is into the DTP process, that will not work. You MUST manually marshal the SYSTEMTIME data to the DTP process, for example:

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;

现在,随着中说,有一个专门为的TDateTimePicker 相关问题(不DTP控制一般)。 的TDateTimePicker 使用的 DTM_GETSYSTEMTIME 信息检索当​​前选定的日期/时间。它的日期 / 时间属性简单地返回内部 TDateTime类型变量被更新时:

Now, with that said, there is problem related specifically to TDateTimePicker (not to DTP controls in general). TDateTimePicker does not use the DTM_GETSYSTEMTIME message to retrieve the currently selected date/time. Its Date/Time properties simply return the current value of an internal TDateTime variable that gets updated when:


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

  1. the TDateTimePicker is initially created, where the date/time is set to Now().

及其日期 / 时间属性是由应用程序分配的,无论是在code或DFM流。

its Date/Time property is assigned by the app, either in code or DFM streaming.

接收到 DTN_DATETIMECHANGE 的通知用新的日期/时间值。

it receives a DTN_DATETIMECHANGE notification with a new date/time value.

在这种情况下,要#3的情况发生。然而, DTN_DATETIMECHANGE (这是基于的 WM_NOTIFY )不受 DTM_SETSYSTEMTIME ,所以你要假它,但 WM_NOTIFY 不能跨进程边界(Windows将不允许其发送 - 雷蒙德陈的解释了一下为什么)。这是记录在MSDN上:

In this situation, you want #3 to happen. However, DTN_DATETIMECHANGE (which is based on WM_NOTIFY) is not generated automatically by DTM_SETSYSTEMTIME, so you have to fake it, but WM_NOTIFY cannot be sent across process boundaries (Windows will not allow it - Raymond Chen explains a bit why). This is documented on MSDN:

有关Windows 2000及更高版本的系统,无法进程之间发送的WM_NOTIFY消息。

For Windows 2000 and later systems, the WM_NOTIFY message cannot be sent between processes.

那么,你就必须注入一些自定义的code到DTP的拥有过程相同的过程,因为DTP内发送 DTN_DATETIMECHANGE 。和注射code到另一个进程的是不平凡的实施。然而,在这种特殊情况下,有一个相当简单的解决方案,大卫晴礼貌:

So, you would have to inject some custom code into the DTP's owning process to send the DTN_DATETIMECHANGE within the same process as the DTP. And injecting code into another process is not trivial to implement. However, in this particular case, there is a fairly simply solution, courtesy of David Ching:

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

正如其他人所指出的那样,在LPARAM指针需要驻留在
  相同的过程,创建HWND线程...我
  已经创建了使用VirtualAlloc的一个SendMessageRemote()API,
  ReadProcessMemory,WriteProcessMemory的和远程线程做
  繁重...

As others have pointed out, the pointer in LPARAM needs to reside in the same process as the thread that created hwnd ... I have created a SendMessageRemote() API which uses VirtualAlloc, ReadProcessMemory, WriteProcessMemory, and CreateRemoteThread to do the heavy lifting ...

http://www.dcsoft.com/private/sendmessageremote.h < BR>
   http://www.dcsoft.com/private/sendmessageremote.cpp

这是基于一个伟大的$ C $的CProject文章:结果
  的http://www.$c$cproject.com/threads/winspy.asp

It is based on a great CodeProject article:
http://www.codeproject.com/threads/winspy.asp.

下面是他的code的德尔福翻译。请注意,我在32位的测试,它的工作原理,但我没有在64位进行了测试。您可能必须从32位进程发送时消息给64位进程或反之亦然,或者如果目标DTP使用,而不是一个统一code窗口一个ANSI窗口调整它:

Here is a Delphi translation of his code. Note, I have tested it in 32-bit and it works, but I have not tested it in 64-bit. You may have to tweak it when sending a message from a 32bit process to a 64bit process or vice versa, or if the target DTP is using an Ansi window instead of a Unicode window:

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;

现在的code操纵DTP变得简单多了:

Now the code to manipulate the DTP becomes much simpler:

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;

如果一切顺利,在的TDateTimePicker 现在将更新其内部 TDateTime类型变量,以匹配 SYSTEMTIME ,你给它。

If all goes well, the TDateTimePicker will now update its internal TDateTime variable to match the SYSTEMTIME that you send to it.

这篇关于如何改变当前选定日期的TDateTimePicker控制其HWND手柄另一个进程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆