gpt4 book ai didi

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

转载 作者:太空宇宙 更新时间:2023-11-04 05:17:59 25 4
gpt4 key购买 nike

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

所以我的问题是 - 是否有一种方法可以仅通过来自外部进程的句柄在该控件中设置日期(使用 WinAPI)

最佳答案

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

DTM_SETSYSTEMTIME NOT 在跨进程边界发送时由 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;

现在,话虽如此,还有另一个问题专门与 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 explains a bit why)。这记录在 MSDN 上:

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

因此,您必须将一些自定义代码注入(inject) DTP 的所属进程才能发送 DTN_DATETIMECHANGE在与 DTP 相同的过程中。并将代码注入(inject)另一个进程 is not trivial to implement .然而,在这种特殊情况下,有一个相当简单的解决方案,由 David Ching 提供:

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

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
http://www.dcsoft.com/private/sendmessageremote.cpp

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

这是他的代码的 Delphi 翻译。请注意,我已经在 32 位中测试过它并且它可以工作,但我没有在 64 位中测试过它。当从 32 位进程向 64 位进程发送消息或从 64 位进程向 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;

现在操作 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;

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

关于c++ - 如何通过其 HWND 句柄更改另一个进程中 TDateTimePicker 控件中当前选定的日期?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28907337/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com