I have compiled a bunch of online resources that got me to here. Hopefully what I have is close. Unfortunately I have no Windows Programming experience. I come from a Linux background. I am also new to alien for Lua, but I know Lua well enough.
What I want to do is send a simple "Hello World" with sendMessage() from the Win32 API to a running Notepad.exe window.
I got the process ID from the command prompt with the following command:
tasklist /FI "IMAGENAME eq notepad.exe" /FI "USERNAME eq user"
And I got the message to send code of 0x000C from here.
So far this is what I have:
require "luarocks.require"
require "alien"
myTestMessage = 0x000C -- Notepad "Set text" id
notepadPID = 2316 -- Notepad Process ID
-- Prototype the SendMessage function from User32.dll
local SendMessage= alien.User32.SendMessageA
SendMessage:types{ret ='long', abi = 'stdcall','long','long','string','string'}
-- Prototype the FindWindowExA function from User32.dll
local FindWindowEx = alien.User32.FindWindowExA
FindWindowEx:types{ret = 'long', abi = 'stdcall', 'long', 'long', 'string', 'string'}
-- Prototype the GetWindowThreadProcessID function from User32.dll
local GetWindowThreadProcessId = alien.User32.GetWindowThreadProcessId
GetWindowThreadProcessId:types{ret = 'long', abi = 'stdcall', 'long', 'pointer'}
local buffer = alien.buffer(4) -- This creates a 4-byte buffer
local threadID = GetWindowThreadProcessId(notepadPID, buffer) -- this fills threadID and our 4-byte buffer
local longFromBuffer = buffer:get(1, 'long') -- this tells that I want x bytes forming a 'long' value and starting at the first byte of the
-- 'buffer' to be in 'longFromBuffer' variable and let its type be 'long'
local handle = FindWindowEx(threadID, "0", "Edit", nil); -- Get the handle to send the message to
local x = SendMessage(handle, myTestMessage, "0", "Hello World!") -- Actually send the message
A lot of this code was pieced together from the Lua alien documents, msdn, and some Google searches (namely this result).
Is there anyone out there who could be a hero and explain to me what i'm doing wrong, and how I should go about this. And, most importantly, why!
I eventually found a simpler way to do this using both FindWindow and FindWindowEX from the Windows API. This way you can find correct handles to the parent and children processes of Notepad.
-- Require luarocks and alien which are necessray for calling Windows functions
require "luarocks.require"
require "alien"
local SendMessage= alien.User32.SendMessageA
SendMessage:types{ret ='long', abi = 'stdcall','long','long','string','string'}
local FindWindowEx = alien.User32.FindWindowExA
FindWindowEx:types{ret = 'long', abi = 'stdcall', 'long', 'long', 'string', 'string'}
local FindWindow = alien.User32.FindWindowA
FindWindow:types{ret = 'long', abi = 'stdcall', 'string', 'string'}
local notepadHandle = FindWindow("Notepad", NULL )
local childHandle = FindWindowEx(notepadHandle, "0", "Edit", nil)
local x = SendMessage(childHandle, "0x000C", "0", "Hello World!") -- Actually send the message
You are misusing GetWindowThreadProcessId() and FindWindowEx(). The first parameter to both of them is an HWND handle to the desired window, but you are passing a process ID to GetWindowThreadProcessId() and a thread ID to FindWindowEx(), both of which are wrong.
There is no straightforward way to get an HWND from a process ID. You would have to use EnumWindows() to loop through the currently running windows, calling GetWindowThreadProcessId() on each one until you find a matching process ID to the one you already have.
Related
Here is the scenario:
I have 2 apps. One of them is my main app, and the second is a dialog based app, which is started from the first one. I'm trying to capture the main handle of the dialog based app from my main app. The problem is that I cannot find it with EnumWindows. The problem disappears if I put sleep for a second, just before start enumerating windows.
This is the code:
...
BOOL res = ::CreateProcess( NULL, _T("MyApp.exe"), NULL, NULL, FALSE, NULL, NULL, NULL, &siStartInfo, &piProcInfo );
ASSERT(res);
dwErr = WaitForInputIdle(piProcInfo.hProcess, iTimeout);
ASSERT(dwErr == 0);
//Sleep(1000); //<-- uncomment this will fix the problem
DWORD dwProcessId = piProcInfo.dwProcessId;
EnumWindows(EnumWindowsProc, (LPARAM)&dwProcessId);
....
BOOL IsMainWindow(HWND handle)
{
return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle);
}
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
DWORD* pParam = (DWORD*)lParam;
DWORD dwTargetProcessId = *pParam;
DWORD dwProcessId = 0;
::GetWindowThreadProcessId(hwnd, &dwProcessId);
if (dwProcessId == dwTargetProcessId )
{
TCHAR buffer[MAXTEXT];
::SendMessage(hwnd, WM_GETTEXT, (WPARAM)MAXTEXT,(LPARAM)buffer);
if( IsMainWindow(hwnd))
{
g_hDlg = hwnd;
return FALSE;
}
}
return TRUE;
}
There are exactly 2 windows which belongs to my process and tracing their text shows:
GDI+ Window
Default IME
I'm not quite sure what does this mean. These might be the default captions, assigned to the windows, before their initialization.... but I call EnumWindows after WaitForInputIdle ...
Any help will be appreciated.
CreateProcess returns, when the OS has created the process object including the object representing the primary thread. This does not imply, that the process has started execution.
If you need to query another process for information that is only available after that process has run to a certain point, you will need to install some sort of synchronization. An obvious option is a named event object (see CreateEvent), that is signaled, when the second process has finished its initialization, and the dialog is up and running. The first process would then simply WaitForSingleProcess, and only continue, once the event is signaled. (A more robust solution would call WaitForMultipleObjects on both the event and the process handle, to respond to unexpected process termination.)
Another option would be to have the second process send a user-defined message (WM_APP+x) to the first process, passing its HWND along.
WaitForInputIdle sounds like a viable solution. Except, it isn't. WaitForInputIdle was introduced to meet the requirements of DDE, and merely checks, if a thread in the target process can receive messages. And that really means any thread in that process. It is not strictly tied to a GUI being up and running.
Additional information on the topic can be found here:
WaitForInputIdle should really be called WaitForProcessStartupComplete
WaitForInputIdle waits for any thread, which might not be the thread you care about
Seemingly simple task: I want to open the standard Windows dialog for picking the application to be used for opening the file, and then wait for this application to finish. The internet tells that ShellExecuteEx is the way to go.
Ok, so here's the code:
SHELLEXECUTEINFO sei;
::ZeroMemory(&sei,sizeof(sei));
sei.cbSize = sizeof(sei);
sei.lpFile = L"path/to/document";
sei.lpVerb = L"openas";
sei.lpParameters = L"";
sei.nShow = SW_SHOW;
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_INVOKEIDLIST ;
BOOL ret = ::ShellExecuteEx(&sei);
DWORD waitResult = ::WaitForSingleObject(sei.hProcess, INFINITE);
But it doesn't work: specifying SEE_MASK_INVOKEIDLIST flag makes hProcess to always be NULL, even if a new process was indeed launched.
How can this be fixed? Thanks in advance!
The shell was never designed to do this and even if it was it would not work 100% of the time because not everything launches a new process (DDE, IShellExecuteHook, IDropTarget, IExecuteCommand etc).
If writing your own dialog is acceptable then you might want to take a look at IEnumAssocHandlers. Raymond Chen recently did a blog post about it.
I'm getting a error message in a VB6 .exe file running on Windows XP.
I compile and "make it" on Windows 7/8, but always get an Overflow error message when it executes this two lines on XP:
sUrl = "C:\Arquivos de Programas\Internet Explorer\IEXPLORE.EXE http://example.com/WebForms/send.aspx?id=" & intCodID & "&type=500&usr=" & intCodUser
openWeb = Shell(sUrl, vbMaximizedFocus)
sUrl is a String and OpenWeb is actually a Integer, but I already declared it as Double and as nothing (just Dim OpenWeb) and still get the overflow error.
UPDATE
Didn't managed to find out what was happening there, but another solution for calling IE:
Dim IE
sUrl = "http://www.google.com/"
Set IE = CreateObject("InternetExplorer.Application")
IE.Visible = True
IE.Navigate sUrl
While the VB6 documentation says Shell() returns a Variant Double... that appears to be obsolete information left over from manuals for earler versions of VB. Instead if you check the typelib info (i.e. look in the IDE's Object Browser) it actually returns a Double type result value.
As far as I can tell Shell() is a wrapper around a call to the WinExec() function.
The returned values are:
0 The system is out of memory or resources.
ERROR_BAD_FORMAT = 11 The .exe file is invalid.
ERROR_FILE_NOT_FOUND = 2 The specified file was not found.
ERROR_PATH_NOT_FOUND = 3 The specified path was not found.
or a Process ID
Also contrary to the documentation, Shell() turns those error values into exceptions ("File not found", "Invalid procedure call or argument," etc.). So if the call succeeds you always get back a PID value.
In all cases this is a DWORD. So it always fits in a Double without the possibility of an overflow. If you are seeing an overflow there is something else going wrong in your code.
Sadly a Double isn't particularly useful here, though it can at least hold the entire range of values. But you'd normally want to carefully convert it to a Long value:
Option Explicit
Function DDoubleToDLong(ByVal DDouble As Double) As Long
'Some functions like the intrinsic Shell() return a Double
'to get around the lack of a UI4 type (DWORD, i.e. unsigned
'Long) in VB. Of course this isn't clean to pass to API
'calls, making it sort of worthless so we need to do a type
'conversion such as this:
If DDouble > 2147483647# Then
DDoubleToDLong = CLng(DDouble - 2147483648#) Or &H80000000
Else
DDoubleToDLong = CLng(DDouble)
End If
End Function
Private Sub Form_Load()
Dim DD As Double
Dim DL As Long
AutoRedraw = True
Font.Name = "Courier New" 'Or other handy monospaced font.
Font.Size = 12#
DD = 0#: DL = DDoubleToDLong(DD): Print DD, DL, Hex$(DL)
DD = 1#: DL = DDoubleToDLong(DD): Print DD, DL, Hex$(DL)
DD = 2147483647#: DL = DDoubleToDLong(DD): Print DD, DL, Hex$(DL)
DD = 2147483648#: DL = DDoubleToDLong(DD): Print DD, DL, Hex$(DL)
DD = 4294967295#: DL = DDoubleToDLong(DD): Print DD, DL, Hex$(DL)
End Sub
Integer is worthless since overflows will be common. Long without the conversion could cause overflows now and then. String is just silly.
You also need to quote the values for the EXE and its arguments property, as in:
Option Explicit
Function DDoubleToDLong(ByVal DDouble As Double) As Long
If DDouble > 2147483647# Then
DDoubleToDLong = CLng(DDouble - 2147483648#) Or &H80000000
Else
DDoubleToDLong = CLng(DDouble)
End If
End Function
Private Sub Form_Load()
Dim sUrl As String
Dim PID As Long
sUrl = """C:\Arquivos de Programas\Internet Explorer\IEXPLORE.EXE"" " _
& """http://example.com/WebForms/send.aspx?id=" _
& intCodID _
& "&type=500&usr=" _
& intCodUser _
& """"
PID = DDoubleToDLong(Shell(sUrl, vbMaximizedFocus))
End Sub
Even this isn't quite "right" since exception handling should be added. And both intCodID and intCodUser may require "cleansing" (UrlEncoding) depending on what types they are and what values they really have. These might be Integers based on the names, with you relying on implicit String coercion? If so they might be Ok.
BTW, as we see above special folder names get localized. For that matter the system drive might not even be C:\ at all! So such paths should never be hard-coded but instead be built up based on values returned from calls to Shell32 to look up the special folder.
An integer can only be a whole number. No decimals.
You say it's declared as an integer therefore you cannot assign 1. anything, and you certainly can't assign anything like that to a number variable as it's not a valid number anyway as it has two decimal points.
You need to declare it as string.
I'm in the middle of converting some older code to talk with a custom SCSI device. The original code was written for WinXP and ASPI, and the newer code needs to work on Win7 and SPTI. My problem is that the newer code fails on a call to do a SCSI "Mode Select" operation with an status code of 2, which is a SCSI "Check Condition" error - but this doesn't happen with the older code under WinXP.
Normally, when you get a "Check Condition" code, you can issue a "Request Sense" command to the device to find out what happened. Unfortunately, this device is (in my opinion) buggy, and always returns "everything is okay" when you do a Request Sense. So I'm working in the dark here.
So I'm hoping for some suggestions on what I could be doing wrong with the SPTI code, and would be grateful for any feedback.
Here are a few things I've thought of that may be affecting this:
The sequence the device expects is "Reserve Unit", "Rezero Unit", "Mode Select", then some other operations, then "Release Unit". It appears "Reserve Unit", "Rezero Unit", and "Release Unit are all working fine, but the other operations fail because "Mode Select" failed.
For each operation, the SPTI code opens and closes a handle to the SCSI host adapter. Should I open a handle in "Reserve Unit" and leave it open for the entire sequence?
The ioctl sent to DeviceIoControl() is IOCTL_SCSI_PASS_THROUGH. Should I be using IOCTL_SCSI_PASS_THROUGH_DIRECT for the "Mode Select" operation? It's a simple operation, so I figured the simpler API would be adequate for this, but maybe I'm wrong about that.
The code in question is:
void MSSModeSelect(const ModeSelectRequestPacket& inRequest, StatusResponsePacket& outResponse)
{
IPC_LOG("MSSModeSelect(): PathID=%d, TargetID=%d, LUN=%d", inRequest.m_Device.m_PathId,
inRequest.m_Device.m_TargetId, inRequest.m_Device.m_Lun);
int adapterIndex = inRequest.m_Device.m_PathId;
HANDLE adapterHandle = prvOpenScsiAdapter(inRequest.m_Device.m_PathId);
if (adapterHandle == INVALID_HANDLE_VALUE)
{
outResponse.m_Status = eScsiAdapterErr;
return;
}
SCSI_PASS_THROUGH_WITH_BUFFERS sptwb;
memset(&sptwb, 0, sizeof(sptwb));
#define MODESELECT_BUF_SIZE 32
sptwb.spt.Length = sizeof(SCSI_PASS_THROUGH);
sptwb.spt.PathId = inRequest.m_Device.m_PathId;
sptwb.spt.TargetId = inRequest.m_Device.m_TargetId;
sptwb.spt.Lun = inRequest.m_Device.m_Lun;
sptwb.spt.CdbLength = CDB6GENERIC_LENGTH;
sptwb.spt.SenseInfoLength = 0;
sptwb.spt.DataIn = SCSI_IOCTL_DATA_IN;
sptwb.spt.DataTransferLength = MODESELECT_BUF_SIZE;
sptwb.spt.TimeOutValue = 2;
sptwb.spt.DataBufferOffset =
offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucDataBuf);
sptwb.spt.Cdb[0] = SCSIOP_MODE_SELECT;
sptwb.spt.Cdb[4] = MODESELECT_BUF_SIZE;
DWORD length = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucDataBuf) +
sptwb.spt.DataTransferLength;
memset(sptwb.ucDataBuf, 0, sizeof(sptwb.ucDataBuf));
sptwb.ucDataBuf[2] = 0x10;
sptwb.ucDataBuf[4] = 0x01;
sptwb.ucDataBuf[5] = 0x04;
ULONG bytesReturned = 0;
BOOL okay = DeviceIoControl(adapterHandle,
IOCTL_SCSI_PASS_THROUGH,
&sptwb,
sizeof(SCSI_PASS_THROUGH),
&sptwb,
length,
&bytesReturned,
FALSE);
DWORD gle = GetLastError();
IPC_LOG(" DeviceIoControl() %s", okay ? "worked" : "failed");
if (okay)
{
outResponse.m_Status = (sptwb.spt.ScsiStatus == 0) ? eOk : ePrinterStatusErr;
}
else
{
outResponse.m_Status = eScsiPermissionsErr;
}
CloseHandle(adapterHandle);
}
The solution proved to have two parts.
First, sptwb.spt.DataIn needed to be SCSI_IOCTL_DATA_OUT rather than SCSI_IOCTL_DATA_IN - because, of course, "Mode Select" is telling the device what to do, rather than asking it for information. This changed the result of DeviceIoControl() from TRUE to FALSE, and GetLastError() then returned a value of 87, indicating an invalid parameter.
Second, as I'd speculated, the ioctl transaction needs to be done using IOCTL_SCSI_PASS_THROUGH_DIRECT rather than IOCTL_SCSI_PASS_THROUGH.
Once everything was set up right with those two changes, the "Mode Select" command succeeded.
I'm reading some code that uses fopen to open files for writing. The code needs to be able to close and rename these files from time to time (it's a rotating file logger). The author says that for this to happen the child processes must not inherit these FILE handles. (On Windows, that is; on Unix it's OK.) So the author writes a special subroutine that duplicates the handle as non-inheritable and closes the original handle:
if (!(log->file = fopen(log->path, mode)))
return ERROR;
#ifdef _WIN32
sf = _fileno(log->file);
sh = (HANDLE)_get_osfhandle(sf);
if (!DuplicateHandle(GetCurrentProcess(), sh, GetCurrentProcess(),
&th, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
fclose(log->file);
return ERROR;
}
fclose(log->file);
flags = (*mode == 'a') ? _O_APPEND : 0;
tf = _open_osfhandle((intptr_t)th, _O_TEXT | flags);
if (!(log->file = _fdopen(tf, "at"))) {
_close(tf);
return ERROR;
}
#endif
Now, I'm also reading MSDN docs on fopen and see that their version of fopen has a Microsoft-specific flag that seems to do the same: the N flag:
N: Specifies that the file is not inherited by child processes.
Question: do I understand it correctly that I can get rid of that piece above and replace it (on Windows) with an additional N in the mode parameter?
Yes, you can.
fopen("myfile", "rbN") creates a non-inheritable file handle.
The N flag is not mentioned anywhere in Linux documentation for fopen, so the solution will be most probably not portable, but for MS VC it works fine.