如何改变当前选定日期的TDateTimePicker控制其HWND手柄另一个进程? [英] How to change currently selected date in TDateTimePicker control in another process by its HWND handle?
问题描述
我正在写一个自定义模块与专有软件工作。 (该软件已经停产了,我没有它的源$ 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类型$ C的当前值$ C>变量被更新时:
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:
-
的
的TDateTimePicker
最初创建的,其中的日期/时间设置为NOW()
。
the
TDateTimePicker
is initially created, where the date/time is set toNow()
.
及其日期
/ 时间
属性是由应用程序分配的,无论是在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屋!