Related
This problem occurs only on Windows 10. Works fine on other versions such as Windows 7.
On user action, I have following code to find out another open application window as:
void zcTarget::LocateSecondAppWindow( void )
{
ghwndAppWindow = NULL;
CString csQuickenTitleSearch = "MySecondApp";
::EnumDesktopWindows( hDesktop, MyCallback, (LPARAM)(LPCTSTR)csTitleSearch );
}
With callback functions as:
BOOL CALLBACK MyCallback( HWND hwnd, LPARAM lParam)
{
if ( ::GetWindowTextLength( hwnd ) == 0 )
{
return TRUE;
}
CString strText;
GetWindowText( hwnd, strText.GetBuffer( 256 ), 256 );
strText.ReleaseBuffer();
if ( strText.Find( (LPCTSTR)lParam ) == 0 )
{
// We found the desired app HWND, so save it off, and return FALSE to
// tell EnumDesktopWindows to stopping iterating desktop HWNDs.
ghwndAppWindow = hwnd;
return FALSE;
}
return TRUE;
} // This is the line after which call is not returned for about 30 mins
This callback function mentioned above gets called for about 7 times, each time returning True. At this stage it finds own app window through which EnumDesktopWindows was invoked.
It returns True as expected, but then nothing happens for about 30 minutes. No debug points hit. The original running application is unresponsive at this point.
How to resolve this problem?
Found another path. Instead of going by Window name, looking for Process helps. Get process using process name, extract process id and get window handle.
void zcTarget::LocateSecondAppWindow( void )
{
PROCESSENTRY32 entry;
entry.dwSize = sizeof(PROCESSENTRY32);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (Process32First(snapshot, &entry) == TRUE)
{
while (Process32Next(snapshot, &entry) == TRUE)
{
if (_stricmp(entry.szExeFile, "myApp.exe") == 0)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);
EnumData ed = { GetProcessId( hProcess ) };
if ( !EnumWindows( EnumProc, (LPARAM)&ed ) &&
( GetLastError() == ERROR_SUCCESS ) ) {
ghwndQuickenWindow = ed.hWnd;
}
CloseHandle(hProcess);
break;
}
}
}
CloseHandle(snapshot);
}
BOOL CALLBACK EnumProc( HWND hWnd, LPARAM lParam ) {
// Retrieve storage location for communication data
zcmTaxLinkProTarget::EnumData& ed = *(zcmTaxLinkProTarget::EnumData*)lParam;
DWORD dwProcessId = 0x0;
// Query process ID for hWnd
GetWindowThreadProcessId( hWnd, &dwProcessId );
// Apply filter - if you want to implement additional restrictions,
// this is the place to do so.
if ( ed.dwProcessId == dwProcessId ) {
// Found a window matching the process ID
ed.hWnd = hWnd;
// Report success
SetLastError( ERROR_SUCCESS );
// Stop enumeration
return FALSE;
}
// Continue enumeration
return TRUE;
}
Anyone know why returns 183 in call EnumDesktopWindows
This process is an service running in System LocalService
I'm trying to put the window in the top, because the process starts minimized.
Thank for Help
My Code:
BOOL CALLBACK EnumWindowsProc( HWND hwnd, LPARAM lParam )
{
DWORD dwPID;
GetWindowThreadProcessId( hwnd, &dwPID );
if( dwPID == lParam ) {
SetWindowPos( hwnd, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW|SWP_NOSIZE|SWP_NOMOVE );
SwitchToThisWindow(hwnd, true);
SetFocus( hwnd );
return FALSE;
}
return TRUE;
}
BOOL CALLBACK EnumDesktopProc(LPTSTR lpszDesktop, LPARAM lParam) {
HDESK hDesk = OpenDesktop(lpszDesktop, NULL, FALSE, GENERIC_ALL);
if(hDesk != NULL) {
if(!EnumDesktopWindows(hDesk,&EnumWindowsProc, lParam)) {
//This call returns (183) Cannot create a file when that file already exists
}
CloseDesktop(hDesk);
}
return TRUE;
}
BOOL CALLBACK EnumWindowStationProc(LPTSTR lpszWindowStation, LPARAM lParam)
{
HWINSTA hWinStat = OpenWindowStation(lpszWindowStation,FALSE,WINSTA_ENUMDESKTOPS|WINSTA_ENUMERATE);
if(hWinStat) {
SetProcessWindowStation(hWinStat);
EnumDesktops(hWinStat,&EnumDesktopProc,lParam);
CloseWindowStation(hWinStat);
}
return TRUE;
}
bool Utils::execIntoDifferentSession(const std::wstring &aPath, const std::wstring &aParams, const std::wstring &aMode)
{
PROCESS_INFORMATION pi;
STARTUPINFO si;
BOOL bResult = FALSE;
DWORD dwSessionId,winlogonPid;
HANDLE hUserToken,hUserTokenDup,hPToken,hProcess;
DWORD dwCreationFlags;
// Log the client on to the local computer.
dwSessionId = WTSGetActiveConsoleSessionId();
//////////////////////////////////////////
// Find the winlogon process
////////////////////////////////////////
PROCESSENTRY32 procEntry;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap == INVALID_HANDLE_VALUE)
return false;
procEntry.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hSnap, &procEntry))
return false;
do {
if (_wcsicmp(procEntry.szExeFile, L"winlogon.exe") == 0) {
// We found a winlogon process...make sure it's running in the console session
DWORD winlogonSessId = 0;
if (ProcessIdToSessionId(procEntry.th32ProcessID, &winlogonSessId) && winlogonSessId == dwSessionId) {
winlogonPid = procEntry.th32ProcessID;
break;
}
}
} while (Process32Next(hSnap, &procEntry));
WTSQueryUserToken(dwSessionId,&hUserToken);
dwCreationFlags = NORMAL_PRIORITY_CLASS|CREATE_NEW_CONSOLE;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb= sizeof(STARTUPINFO);
si.lpDesktop = L"winsta0\\default";
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOWNORMAL|SW_RESTORE;
ZeroMemory(&pi, sizeof(pi));
TOKEN_PRIVILEGES tp;
LUID luid;
hProcess = OpenProcess(MAXIMUM_ALLOWED,FALSE,winlogonPid);
if(!::OpenProcessToken(hProcess,TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY
|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY|TOKEN_ADJUST_SESSIONID
|TOKEN_READ|TOKEN_WRITE,&hPToken))
{
return false;
}
if (!LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid))
return false;
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
DuplicateTokenEx(hPToken,MAXIMUM_ALLOWED,NULL,SecurityIdentification,TokenPrimary,&hUserTokenDup);
//Adjust Token privilege
SetTokenInformation(hUserTokenDup,TokenSessionId,(void*)dwSessionId,sizeof(DWORD));
if (!AdjustTokenPrivileges(hUserTokenDup,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),(PTOKEN_PRIVILEGES)NULL,NULL))
return false;
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
return false;
LPVOID pEnv = NULL;
if(CreateEnvironmentBlock(&pEnv,hUserTokenDup,TRUE))
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
else
pEnv = NULL;
// Launch the process in the client's logon session.
std::wstring params = aParams;
std::wstring path = aPath;
if(aMode == L"select") {
TCHAR infoBuffer[MAX_PATH];
GetSystemWindowsDirectory(infoBuffer, MAX_PATH);
std::wstring windowsDir(infoBuffer);
path = windowsDir+L"\\explorer.exe";
params = L" /n, /select,"+replaceString(aPath, L"\\\\", L"\\");
}
bResult = CreateProcessAsUser(
hUserTokenDup, // client's access token
path.c_str(), // file to execute
params.length() > 0 ? stringToLPWSTR(wideToUtf8(params)) : NULL, // command line
NULL, // pointer to process SECURITY_ATTRIBUTES
NULL, // pointer to thread SECURITY_ATTRIBUTES
FALSE, // handles are not inheritable
dwCreationFlags, // creation flags
pEnv, // pointer to new environment block
NULL, // name of current directory
&si, // pointer to STARTUPINFO structure
&pi // receives information about new process
);
EnumWindowStations(&EnumWindowStationProc, (LPARAM)(pi.dwProcessId));
// End impersonation of client.
//GetLastError Shud be 0
int rv = GetLastError();
//Perform All the Close Handles task
CloseHandle(hProcess);
CloseHandle(hUserToken);
CloseHandle(hUserTokenDup);
CloseHandle(hPToken);
return !rv;
}
Error 183 is ERROR_ALREADY_EXISTS. EnumDesktopWindows() does not set that error, so it must be a carry-over from an earlier API call. If you read the documentation says this:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms682615.aspx
You must ensure that the callback function sets SetLastError if it fails.
http://msdn.microsoft.com/en-us/library/windows/desktop/ms682614.aspx
If the callback function fails, the return value is zero. The callback function can call SetLastError to set an error code for the caller to retrieve by calling GetLastError.
So try something more like this:
struct WndInfo
{
DWORD dwProcessID;
HWND hWnd;
};
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
WndInfo *pInfo = (WndInfo*) lParam;
DWORD dwPID;
GetWindowThreadProcessId(hwnd, &dwPID);
if (dwPID == pInfo->dwProcessID)
{
pInfo->hWnd = hwnd;
SetLastError(0);
return FALSE;
}
return TRUE;
}
BOOL CALLBACK EnumDesktopProc(LPTSTR lpszDesktop, LPARAM lParam)
{
HDESK hDesk = OpenDesktop(lpszDesktop, NULL, FALSE, GENERIC_ALL);
if (hDesk != NULL)
{
if (!EnumDesktopWindows(hDesk, &EnumWindowsProc, lParam))
{
if (GetLastError() != 0)
{
// handle error as needed...
}
}
CloseDesktop(hDesk);
WndInfo *pInfo = (WndInfo*) lParam;
if (pInfo->hWnd != NULL)
{
SetLastError(0);
return FALSE;
}
}
return TRUE;
}
BOOL CALLBACK EnumWindowStationProc(LPTSTR lpszWindowStation, LPARAM lParam)
{
HWINSTA hWinStat = OpenWindowStation(lpszWindowStation, FALSE, WINSTA_ENUMDESKTOPS|WINSTA_ENUMERATE);
if (hWinStat != NULL)
{
SetProcessWindowStation(hWinStat);
if (!EnumDesktops(hWinStat, &EnumDesktopProc, lParam))
{
if (GetLastError() != 0)
{
// handle error as needed...
}
}
CloseWindowStation(hWinStat);
WndInfo *pInfo = (WndInfo*) lParam;
if (pInfo->hWnd != NULL)
{
SetLastError(0);
return FALSE;
}
}
return TRUE;
}
HWND findWindowForProcess(DWORD PID)
{
WndInfo info;
info.dwProcessID = PID;
info.hWnd = NULL;
if (!EnumWindowStations(&EnumWindowStationProc, (LPARAM)&info))
{
if (GetLastError() != 0)
{
// handle error as needed...
}
}
return info.hWnd;
}
bool Utils::execIntoDifferentSession(const std::wstring &aPath, const std::wstring &aParams, const std::wstring &aMode)
{
...
bResult = CreateProcessAsUser(...);
if (bResult)
{
HWND hWnd = findWindowForProcess(pi.dwProcessId);
if (hWnd != NULL)
{
SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW|SWP_NOSIZE|SWP_NOMOVE);
SwitchToThisWindow(hWnd, TRUE);
SetFocus(hWnd);
}
}
...
}
With that said, since all you are really trying to do is execute a new process in a specific user session, you don't need to bother with all that enumeration logic. You don't need to find the WinLogon process at all, you already have the user's token from WTSQueryUserToken() so just duplicate+adjust that token as needed. And you are not doing anything useful in your window enumeration that the new process would not already do by default when it is started, so just get rid of that logic, too.
And then finally fix your error handling (or lack of) so you can close any handles that are open and not leak them.
After I close the main window of my app, process remains listed in windows task manager's processes list.
Here's the code below, anyone has an idea what to modify to successfully exit process on app exit (or main window close).
int WINAPI WinMain(HINSTANCE inst,HINSTANCE prev,LPSTR cmd,int show)
{
HRESULT hr = CoInitialize(0); MSG msg={0}; DWORD no;
IGraphBuilder* graph= 0; hr = CoCreateInstance( CLSID_FilterGraph, 0, CLSCTX_INPROC,IID_IGraphBuilder, (void **)&graph );
IMediaControl* ctrl = 0; hr = graph->QueryInterface( IID_IMediaControl, (void **)&ctrl );
ICreateDevEnum* devs = 0; hr = CoCreateInstance (CLSID_SystemDeviceEnum, 0, CLSCTX_INPROC, IID_ICreateDevEnum, (void **) &devs);
IEnumMoniker* cams = 0; hr = devs?devs->CreateClassEnumerator (CLSID_VideoInputDeviceCategory, &cams, 0):0;
IMoniker* mon = 0; hr = cams->Next (1,&mon,0); // get first found capture device (webcam?)
IBaseFilter* cam = 0; hr = mon->BindToObject(0,0,IID_IBaseFilter, (void**)&cam);
hr = graph->AddFilter(cam, L"Capture Source"); // add web cam to graph as source
IEnumPins* pins = 0; hr = cam?cam->EnumPins(&pins):0; // we need output pin to autogenerate rest of the graph
IPin* pin = 0; hr = pins?pins->Next(1,&pin, 0):0; // via graph->Render
hr = graph->Render(pin); // graph builder now builds whole filter chain including MJPG decompression on some webcams
IEnumFilters* fil = 0; hr = graph->EnumFilters(&fil); // from all newly added filters
IBaseFilter* rnd = 0; hr = fil->Next(1,&rnd,0); // we find last one (renderer)
hr = rnd->EnumPins(&pins); // because data we are intersted in are pumped to renderers input pin
hr = pins->Next(1,&pin, 0); // via Receive member of IMemInputPin interface
IMemInputPin* mem = 0; hr = pin->QueryInterface(IID_IMemInputPin,(void**)&mem);
DsHook(mem,6,Receive); // so we redirect it to our own proc to grab image data
hr = ctrl->Run();
while ( GetMessage( &msg, 0, 0, 0 ) ) {
TranslateMessage( &msg );
DispatchMessage( &msg );
}
};
Disclaimer: I made no attempt to make this look pretty or do error checking. It works as far as I can tell (when I close the window, the application ends), but it's not exemplary code at all.
The window does not post a WM_QUIT message on its own; you have to do that yourself. You can do it as follows:
Create a window for messages:
WNDCLASSEX wx = {};
wx.cbSize = sizeof(WNDCLASSEX);
wx.lpfnWndProc = proc; // function which will handle messages
wx.hInstance = GetModuleHandle(0);
wx.lpszClassName = "some class";
RegisterClassEx(&wx);
HWND hwnd = CreateWindowEx(0, "some class", "some name", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
Make a IMediaEventEx * and use it to direct notifications to the window:
I assume a global IMediaEventEx *event;. Please don't do it the quick dirty way.
graph->QueryInterface(IID_IMediaEventEx, (void **) &event);
event->SetNotifyWindow((OAHWND) hwnd, WM_APP + 1, 0);
Make the window procedure handle the case of the user aborting:
LRESULT CALLBACK proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
if (msg == WM_APP + 1) {
long evt;
LONG_PTR param1, param2;
while (SUCCEEDED(event->GetEvent(&evt, ¶m1, ¶m2, 0))) {
event->FreeEventParams(evt, param1, param2);
if (evt == EC_USERABORT) {
PostQuitMessage(0);
}
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
My C++ application has a windowless timer to periodically cleanup latent communications data that was never (and will never be) fully processed. The problem is that the callback function is never called. My class constructor executes the following code, just before it returns:
if ( (this->m_hMsgsThread = ::CreateThread(
NULL, // no security attributes
0, // use default initial stack size
reinterpret_cast<LPTHREAD_START_ROUTINE>(MessagesThreadFn), // function to execute in new thread
this, // thread parameters
0, // use default creation settings
NULL // thread ID is not needed
)) == NULL )
{
dwError = ::GetLastError();
TRACE(_T("%s : Failed to create thread.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), _T(__FUNCTION__), dwError, _T(__FILE__), __LINE__);
continue;
}
if ( (s_pCleanupTimerId = ::SetTimer( NULL, 0, MSGS_CLEANUP_PERIOD, CleanupTimerProc )) == NULL )
{
dwError = ::GetLastError();
TRACE(_T("%s : Failed to create timer.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), _T(__FUNCTION__), dwError, _T(__FILE__), __LINE__);
continue;
}
This is my definition for CleanupTimerProc:
static void CALLBACK CleanupTimerProc( HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime )
{
CMsgDesc * pobjMsgDesc = NULL;
DWORD dwError = ERROR_SUCCESS;
DWORD dwItem;
DWORD dwList;
DWORD dwListItems;
DWORD dwThen, dwNow;
const DWORD cMAX_LISTS = MSGS_MAX_EVENTS;
do
{
// Kill off the old timer.
TRACE(_T("%s : Killing cleanup timer.\r\n"), _T(__FUNCTION__));
ASSERT(s_pCleanupTimerId == idEvent);
::KillTimer( hwnd, idEvent );
// Start a new timer using the same ID.
TRACE(_T("%s : Restarting cleanup timer.\r\n"), _T(__FUNCTION__));
if ( (s_pCleanupTimerId = ::SetTimer( NULL, 0, MSGS_CLEANUP_PERIOD, CleanupTimerProc )) == NULL )
{
dwError = ::GetLastError();
TRACE(_T("%s : Failed to create timer.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), _T(__FUNCTION__), dwError, _T(__FILE__), __LINE__);
continue;
}
// Get the current time.
dwNow = ::GetTickCount();
// Search through the message descriptor lists
// looking for descriptors that haven't been touched in a while.
TRACE(_T("%s : Deleting old message descriptors.\r\n"), _T(__FUNCTION__));
ASSERT(s_pInterface != NULL);
ASSERT(s_pInterface->pobjMessages != NULL);
::EnterCriticalSection( &s_pInterface->pobjMessages->m_csMsgDescriptors );
for ( dwList = 0; dwList < cMAX_LISTS; dwList++ )
{
dwListItems = s_pInterface->pobjMessages->m_pobjMsgDescriptors[dwList]->GetItemCount();
for ( dwItem = 0; dwItem < dwListItems; dwItem++ )
{
if ( (pobjMsgDesc = (CMsgDesc *)s_pInterface->pobjMessages->m_pobjMsgDescriptors[dwList]->Peek( NULL, dwItem )) == NULL )
{
dwError = ::GetLastError();
TRACE(_T("%s : Failed to peek item from list.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), _T(__FUNCTION__), dwError, _T(__FILE__), __LINE__);
continue;
}
// Get the last touched time from the descriptor.
dwThen = pobjMsgDesc->m_dwLastTouched;
// If the specified time has elapsed, delete the descriptor.
if ( (dwNow - dwThen) > MSGS_DESC_SHELFLIFE )
{
if ( s_pInterface->pobjMessages->m_pobjMsgDescriptors[dwList]->Remove( NULL, dwItem ) == NULL )
{
dwError = ::GetLastError();
TRACE(_T("%s : Failed to remove item from list.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), _T(__FUNCTION__), dwError, _T(__FILE__), __LINE__);
continue;
}
delete pobjMsgDesc;
TRACE(_T("%s : Deleted old message descriptor.\r\n"), _T(__FUNCTION__));
}
}
}
::LeaveCriticalSection( &s_pInterface->pobjMessages->m_csMsgDescriptors );
}
while ( 0 );
}
Any thoughts as to why this function is not getting called? Do I need to create the timer from within the thread?
I may be remembering this incorrectly ....
However a timer still requires a windows message pump. If you want that timer to fire in a given thread then that thread need to pump messages or it will never get called.
Equally it seems to me that you are creating a timer callback that enters an infinite loop. The function needs to exit or the next timer can never get called.
Use CreateWaitableTimer() and SetWaitableTimer() instead of SetTimer(). Waitable timers work with the WaitFor...() family of functions, like MsgWaitForMultipleObjects(), eg:
HANDLE s_pCleanupTimer;
s_pCleanupTimer = CreateWaitableTimer(NULL, FALSE, NULL);
if( !s_pCleanupTimer )
{
dwError = ::GetLastError();
TRACE(_T("%s : Failed to create timer.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), _T(__FUNCTION__), dwError, _T(__FILE__), __LINE__);
continue;
}
LARGE_INTEGER DueTime;
DueTime.LowPart = -(MSGS_CLEANUP_PERIOD * 10000);
DueTime.HighPart = 0;
if( !SetWaitableTimer(s_pCleanupTimer, &DueTime, MSGS_CLEANUP_PERIOD, NULL, NULL, FALSE) )
{
dwError = ::GetLastError();
TRACE(_T("%s : Failed to start timer.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), _T(__FUNCTION__), dwError, _T(__FILE__), __LINE__);
CloseHandle(s_pCleanupTimer);
s_pCleanupTimer = NULL;
continue;
}
Then in your message loop (or any other kind of status poll):
do
{
DWORD dwRet = MsgWaitForMultipleObjects(1, &hTimer, FALSE, INFINITE, QS_ALLINPUT);
if( dwRet == WAIT_FAILED )
break;
if( dwRet == WAIT_OBJECT_0 ) // timer elapsed
CleanupTimerProc();
else if( dwRet == (WAIT_OBJECT_0+1) ) // pending message
ProcessPendingMessages();
}
while( true );
In your function I see that you're killing off the old timer and starting the new one and it looks rather redundant. I believe Goz is right when he says that you're creating an infinite loop.
I wrote a short program using the same non-window-based unidentified timers that you're using and it works fine for me.
#include <windows.h>
VOID CALLBACK TimerProc(HWND hWnd, UINT uMessage, UINT_PTR uEventId, DWORD dwTime)
{
UNREFERENCED_PARAMETER(hWnd);
UNREFERENCED_PARAMETER(uMessage);
UNREFERENCED_PARAMETER(uEventId);
UNREFERENCED_PARAMETER(dwTime);
MessageBox(NULL, "lol", "lol", MB_OK);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
switch (uMessage)
{
case WM_CREATE:
SetTimer(NULL, 0, 1000, TimerProc);
break;
case WM_CLOSE:
DestroyWindow(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, uMessage, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCommandLine, int nShowCommand)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpszCommandLine);
UNREFERENCED_PARAMETER(nShowCommand);
WNDCLASSEX wce;
ZeroMemory(&wce, sizeof(wce));
wce.cbSize = sizeof(wce);
wce.hInstance = hInstance;
wce.lpfnWndProc = WndProc;
wce.lpszClassName = "test";
if (RegisterClassEx(&wce) > 0)
{
if (CreateWindow("test", "test", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, hInstance, NULL) != NULL)
{
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return static_cast<int>(msg.wParam);
}
}
return EXIT_FAILURE;
}
My application uses serial I/O with overlapped events. For some reason, ::WaitCommEvent fails consistently on the second pass through the loop with ERROR_INVALID_PARAMETER. If anyone can explain what I need to do differently, it would be greatly appreciated. My serial port initialization/open code and the thread function follows. It should be noted that the init/open code is executed after the thread function is started, which is what the call to ::WaitForSingleObject for.
Additionally, I was wondering if something like ::WaitForSingleObject( pobjSerialPort->m_hSerialPort, INFINITE ); would be valid as a non-blocking means of determining when the serial port is open.
Serial Port Initialization:
DWORD CSerialPort::Open( const wchar_t * portName )
{
DCB dcb = {0};
DWORD dwError = ERROR_SUCCESS;
do
{
if ( this->IsOpen() != FALSE )
{
TRACE(_T("CSerialPort::Open : Warning: Attempted to re-open serial port that is already open.\r\n"));
continue;
}
// Overwrite port name if specified.
if ( portName != NULL )
{
this->m_pwcPortName.clear();
this->m_pwcPortName.append( SP_NAME_PREFIX );
this->m_pwcPortName.append( portName );
}
ASSERT(this->m_pwcPortName.length() > 0);
// Open the serial port.
if ( (this->m_hSerialPort = ::CreateFile(
m_pwcPortName.c_str(), // Formatted serial port name
GENERIC_READ | GENERIC_WRITE, // Access: Read and write
0, // Share: No sharing
NULL, // Security: None
OPEN_EXISTING, // COM port already exists
FILE_FLAG_OVERLAPPED, // Asynchronous I/O
NULL // No template file for COM port
)) == INVALID_HANDLE_VALUE )
{
dwError = ::GetLastError();
TRACE(_T("CSerialPort::Open : Failed to get the handle to the serial port.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
// Initialize the DCB structure with COM port parameters.
if ( !::BuildCommDCB( _T("baud=38400 parity=N data=8 stop=1"), &dcb ) )
{
dwError = ::GetLastError();
TRACE(_T("CSerialPort::Open : Failed to build the DCB structure.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
// Set the serial port communications events mask.
if ( !::SetCommMask( this->m_hSerialPort, SP_COMM_MASK ) )
{
dwError = ::GetLastError();
TRACE(_T("CSerialPort::Open : Failed to set comm. events to be monitored.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
// Set serial port parameters.
if ( !::SetCommState( this->m_hSerialPort, &dcb ) )
{
dwError = ::GetLastError();
TRACE(_T("CSerialPort::Open : Failed to set the comm state.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
// Set the serial port communications timeouts.
this->m_ct.ReadIntervalTimeout = MAXDWORD;
this->m_ct.ReadTotalTimeoutMultiplier = 0;
this->m_ct.ReadTotalTimeoutConstant = 0;
this->m_ct.WriteTotalTimeoutMultiplier = 0;
this->m_ct.WriteTotalTimeoutConstant = 0;
if ( !::SetCommTimeouts( this->m_hSerialPort, &(this->m_ct) ) )
{
dwError = ::GetLastError();
TRACE(_T("CSerialPort::Open : Failed to set the comm timeout values.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
// Create thread to receive data.
if ( (this->m_hSpRxThread = CreateThread(
NULL, // No security attributes.
0, // Use default initial stack size.
reinterpret_cast<LPTHREAD_START_ROUTINE>(SerialPortRxThreadFn), // Function to execute in new thread.
this, // Thread parameters.
0, // Use default creation settings.
NULL // Thread ID is not needed.
)) == NULL )
{
dwError = ::GetLastError();
TRACE(_T("CSerialPort::Open : Failed to create serial port receive thread.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
// Create thread to transmit data.
if ( (this->m_hSpTxThread = CreateThread(
NULL, // No security attributes.
0, // Use default initial stack size.
reinterpret_cast<LPTHREAD_START_ROUTINE>(SerialPortTxThreadFn), // Function to execute in new thread.
this, // Thread parameters.
0, // Use default creation settings.
NULL // Thread ID is not needed.
)) == NULL )
{
dwError = ::GetLastError();
TRACE(_T("CSerialPort::Open : Failed to create serial port transmit thread.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
if ( !::SetEvent( this->m_hSpPortOpenEvent ) )
{
dwError = ::GetLastError();
TRACE(_T("CSerialPort::Open : Failed to set event object signal state.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
}
while ( 0 );
return dwError;
}
Serial Port Communications Events Thread:
static DWORD SerialPortCommEvtsThreadFn( void * pParam )
{
CSerialPort * pobjSerialPort = NULL;
BOOL blContinue = TRUE;
DWORD dwError = ERROR_SUCCESS;
DWORD dwEventMask = 0;
DWORD dwObjectWaitState;
OVERLAPPED ovComm = { 0 };
int i = 0;
static HANDLE pHandles[SPCM_MAX_EVENTS + SP_ONE_ITEM]; // +1 for overlapped event
// Validate parameters.
if ( pParam == NULL )
{
dwError = ERROR_INVALID_PARAMETER;
TRACE(_T("SerialPortTxThreadFn : Invalid parameter.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
return dwError;
}
pobjSerialPort = (CSerialPort *)pParam;
// Load event handles.
pHandles[i++] = pobjSerialPort->GetCommHandle( SPCM_THREAD_EXIT_EVT_ID );
pHandles[i++] = pobjSerialPort->GetCommHandle( SPCM_PORT_OPEN_EVT_ID );
pHandles[i++] = pobjSerialPort->GetCommHandle( SPCM_PORT_CLOSED_EVT_ID );
while ( (blContinue != FALSE) && (dwError == ERROR_SUCCESS) )
{
// Wait for serial port to open.
if ( (dwObjectWaitState = ::WaitForSingleObject( pobjSerialPort->GetCommHandle( SPCM_PORT_OPEN_EVT_ID ), INFINITE )) != WAIT_OBJECT_0 )
{
dwError = ( dwObjectWaitState == WAIT_FAILED ) ? ::GetLastError() : dwObjectWaitState;
TRACE(_T("SerialPortCommEvtsThreadFn : Failed waiting for event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
if ( ovComm.hEvent == NULL )
{
// Create event object for serial port communications events OVERLAPPED structure.
if ( (ovComm.hEvent = ::CreateEvent(
NULL, // No security
TRUE, // Create a manual-reset event object
FALSE, // Initial state is non-signaled
NULL // No name specified
)) == NULL )
{
dwError = ::GetLastError();
TRACE(_T("SerialPortCommEvtsThreadFn : Failed to create event object.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
pHandles[i++] = ovComm.hEvent;
}
else
{
i++;
}
// Wait for a communications event.
if ( !::WaitCommEvent( pobjSerialPort->m_hSerialPort, &dwEventMask, &ovComm ) )
{
if ( (dwError = ::GetLastError()) != ERROR_IO_PENDING )
{
TRACE(_T("SerialPortCommEvtsThreadFn : Failed waiting for communications event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
else
{
dwError = ERROR_SUCCESS;
}
}
else
{
if ( (dwError = HandleCommOvEvent( pobjSerialPort, dwEventMask )) != ERROR_SUCCESS )
{
TRACE(_T("SerialPortCommEvtsThreadFn : Failed handling communications event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
continue;
}
dwObjectWaitState = ::WaitForMultipleObjects( i--, pHandles, FALSE, INFINITE );
switch ( dwObjectWaitState )
{
case WAIT_OBJECT_0 + SPCM_THREAD_EXIT_EVT_ID:
blContinue = FALSE;
break;
case WAIT_OBJECT_0 + SPCM_PORT_OPEN_EVT_ID:
if ( !::ResetEvent( pobjSerialPort->GetCommHandle( SPCM_PORT_CLOSED_EVT_ID ) ) )
{
dwError = ::GetLastError();
TRACE(_T("SerialPortCommEvtsThreadFn : Failed to reset event object signal state.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
break;
case WAIT_OBJECT_0 + SPCM_PORT_CLOSED_EVT_ID:
if ( !::ResetEvent( pobjSerialPort->GetCommHandle( SPCM_PORT_OPEN_EVT_ID ) ) )
{
dwError = ::GetLastError();
TRACE(_T("SerialPortCommEvtsThreadFn : Failed to reset event object signal state.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
break;
case WAIT_OBJECT_0 + SPCM_MAX_EVENTS:
if ( (dwError = HandleCommOvEvent( pobjSerialPort, dwEventMask )) != ERROR_SUCCESS )
{
TRACE(_T("SerialPortCommEvtsThreadFn : Failed handling communications event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
::CloseHandle( ovComm.hEvent );
::memset( &ovComm, 0, sizeof(OVERLAPPED) );
break;
default:
dwError = ( dwObjectWaitState == WAIT_FAILED ) ? ::GetLastError() : dwObjectWaitState;
TRACE(_T("SerialPortCommEvtsThreadFn : Failed waiting for event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
break;
}
}
return dwError;
}
Handle Comm Events Function
static DWORD HandleCommOvEvent( CSerialPort * pobjSerialPort, DWORD dwEvtMask )
{
DWORD dwError = ERROR_SUCCESS;
do
{
// Validate parameters.
if ( pobjSerialPort == NULL )
{
dwError = ERROR_INVALID_PARAMETER;
TRACE(_T("HandleCommEvent : Invalid parameter.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
// Handle the transmit complete event.
if ( dwEvtMask & EV_TXEMPTY )
{
if ( (dwError = HandleTxDoneCommEvent( pobjSerialPort )) != ERROR_SUCCESS )
{
TRACE(_T("HandleCommEvent : Failed handling transmit complete event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
}
// Handle the received data event.
if ( dwEvtMask & EV_RXCHAR )
{
if ( (dwError = HandleRxDataCommEvent( pobjSerialPort )) != ERROR_SUCCESS )
{
TRACE(_T("HandleCommEvent : Failed handling received data event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
}
}
while ( 0 );
return dwError;
}
Handle Received Data Comm Event Function
static DWORD HandleRxDataCommEvent( CSerialPort * pobjSerialPort )
{
DWORD dwError = ERROR_SUCCESS;
do
{
// Validate parameters.
if ( pobjSerialPort == NULL )
{
dwError = ERROR_INVALID_PARAMETER;
TRACE(_T("HandleRxDataCommEvent : Invalid parameter.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
if ( !::SetEvent( pobjSerialPort->GetRxHandle( SPRX_RECEIVED_DATA_EVT_ID ) ) )
{
dwError = ::GetLastError();
TRACE(_T("HandleRxDataCommEvent : Failed setting event object signal state.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
}
while ( 0 );
return dwError;
}
Receive Thread Function
static DWORD SerialPortRxThreadFn( void * pParam )
{
CSerialPort * pobjSerialPort = NULL;
BOOL blContinue = TRUE;
DWORD dwError = ERROR_SUCCESS;
DWORD dwEventMask = 0;
DWORD dwObjectWaitState;
OVERLAPPED ovComm = { 0 };
int i = 0;
static BYTE pBuf[SP_RX_BUF_SIZE];
static HANDLE pHandles[SPRX_MAX_EVENTS + SP_ONE_ITEM]; // +1 for overlapped event
ASSERT(s_pobjRxBuffer != NULL);
// Validate parameters.
if ( pParam == NULL )
{
dwError = ERROR_INVALID_PARAMETER;
TRACE(_T("SerialPortRxThreadFn : Invalid parameter.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
return dwError;
}
pobjSerialPort = (CSerialPort *)pParam;
// Load event handles.
pHandles[i++] = pobjSerialPort->GetRxHandle( SPRX_THREAD_EXIT_EVT_ID );
pHandles[i++] = pobjSerialPort->GetRxHandle( SPRX_RECEIVED_DATA_EVT_ID );
while ( (blContinue != FALSE) && (dwError == ERROR_SUCCESS) )
{
if ( ovComm.hEvent == NULL )
{
// Create event object for serial port communications events OVERLAPPED structure.
if ( (ovComm.hEvent = ::CreateEvent(
NULL, // No security
TRUE, // Create a manual-reset event object
FALSE, // Initial state is non-signaled
NULL // No name specified
)) == NULL )
{
dwError = ::GetLastError();
TRACE(_T("SerialPortRxThreadFn : Failed to create event object.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
pHandles[i++] = ovComm.hEvent;
}
else
{
i++;
}
dwObjectWaitState = ::WaitForMultipleObjects( i--, pHandles, FALSE, INFINITE );
switch ( dwObjectWaitState )
{
case WAIT_OBJECT_0 + SPRX_THREAD_EXIT_EVT_ID:
blContinue = FALSE;
break;
case WAIT_OBJECT_0 + SPRX_RECEIVED_DATA_EVT_ID:
if ( !::ReadFile( pobjSerialPort->m_hSerialPort, pBuf, SP_RX_BUF_SIZE, NULL, &ovComm ) )
{
if ( (dwError = ::GetLastError()) != ERROR_IO_PENDING )
{
TRACE(_T("SerialPortRxThreadFn : Failed reading data from serial port.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
}
else
{
if ( (dwError = HandleReceivedDataOvEvent( pobjSerialPort, &ovComm, pBuf )) != ERROR_SUCCESS )
{
TRACE(_T("SerialPortRxThreadFn : Failed handling serial port received data event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
}
break;
case WAIT_OBJECT_0 + SPRX_MAX_EVENTS:
if ( (dwError = HandleReceivedDataOvEvent( pobjSerialPort, &ovComm, pBuf )) != ERROR_SUCCESS )
{
TRACE(_T("SerialPortRxThreadFn : Failed handling serial port received data event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
::CloseHandle( ovComm.hEvent );
::memset( &ovComm, 0, sizeof(OVERLAPPED) );
break;
default:
dwError = ( dwObjectWaitState == WAIT_FAILED ) ? ::GetLastError() : dwObjectWaitState;
TRACE(_T("SerialPortRxThreadFn : There is a problem with the OVERLAPPED structure's event handle.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
break;
}
}
return dwError;
}
Looks like you're trying to use the same OVERLAPPED structure for both WaitCommEvent and ReadFile. That should be causing you no end of problems.
Documentation citation: When performing multiple simultaneous overlapped operations on a single thread, the calling thread must specify an OVERLAPPED structure for each operation
An attempt to fix it (not tested, not even compiled). Stuff you need to fill in specific to your project is marked with // TODO.
static DWORD SerialPortCommEvtsThreadFn( void * pParam )
{
CSerialPort * pobjSerialPort = NULL;
BOOL blContinue = TRUE;
DWORD dwError = ERROR_SUCCESS;
DWORD dwEventMask = 0;
DWORD dwObjectWaitState;
OVERLAPPED ovWaitComm = { 0 };
OVERLAPPED ovRead = { 0 };
const DWORD numHandles = SPCM_MAX_EVENTS + 2;
HANDLE pHandles[numHandles];
// Validate parameters.
if ( pParam == NULL )
{
dwError = ERROR_INVALID_PARAMETER;
TRACE(_T("SerialPortTxThreadFn : Invalid parameter.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
return dwError;
}
pobjSerialPort = static_cast<CSerialPort *>(pParam);
ovWaitComm.hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
ovRead.hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
if (!ovWaitComm.hEvent || !ovRead.hEvent) {
dwError = ::GetLastError();
TRACE(_T("SerialPortCommEvtsThreadFn : Failed to create event objects.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
return dwError;
}
// Load event handles.
pHandles[SPCM_THREAD_EXIT_EVT_ID ] = pobjSerialPort->GetCommHandle( SPCM_THREAD_EXIT_EVT_ID );
pHandles[SPCM_PORT_OPEN_EVT_ID ] = pobjSerialPort->GetCommHandle( SPCM_PORT_OPEN_EVT_ID );
pHandles[SPCM_PORT_CLOSED_EVT_ID ] = pobjSerialPort->GetCommHandle( SPCM_PORT_CLOSED_EVT_ID );
pHandles[numHandles - 2] = ovWaitComm.hEvent;
pHandles[numHandles - 1] = ovRead.hEvent;
// Wait for serial port to open.
if ( (dwObjectWaitState = ::WaitForSingleObject( pobjSerialPort->GetCommHandle( SPCM_PORT_OPEN_EVT_ID ), INFINITE )) != WAIT_OBJECT_0 ) {
dwError = ( dwObjectWaitState == WAIT_FAILED ) ? ::GetLastError() : dwObjectWaitState;
TRACE(_T("SerialPortCommEvtsThreadFn : Failed waiting for event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
return dwError;
}
// TODO: SetCommTimeouts
// Wait for a communications event.
if ( !::WaitCommEvent( pobjSerialPort->m_hSerialPort, &dwEventMask, &ovWaitComm ) {
if ( (dwError = ::GetLastError()) != ERROR_IO_PENDING ) {
TRACE(_T("SerialPortCommEvtsThreadFn : Failed waiting for communications event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
return dwError;
}
}
// TODO: Here, call ReadFile, passing &ovRead
dwError = ERROR_SUCCESS;
while ( blContinue && (dwError == ERROR_SUCCESS) )
{
dwObjectWaitState = ::WaitForMultipleObjects( numHandles, pHandles, FALSE, INFINITE );
switch ( dwObjectWaitState )
{
case WAIT_OBJECT_0 + SPCM_THREAD_EXIT_EVT_ID:
blContinue = FALSE;
break;
case WAIT_OBJECT_0 + SPCM_PORT_OPEN_EVT_ID:
if ( !::ResetEvent( pobjSerialPort->GetCommHandle( SPCM_PORT_CLOSED_EVT_ID ) ) )
{
dwError = ::GetLastError();
TRACE(_T("SerialPortCommEvtsThreadFn : Failed to reset event object signal state.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
break;
case WAIT_OBJECT_0 + SPCM_PORT_CLOSED_EVT_ID:
if ( !::ResetEvent( pobjSerialPort->GetCommHandle( SPCM_PORT_OPEN_EVT_ID ) ) )
{
dwError = ::GetLastError();
TRACE(_T("SerialPortCommEvtsThreadFn : Failed to reset event object signal state.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
break;
case WAIT_OBJECT_0 + numHandles - 2:
if ( (dwError = HandleCommOvEvent( pobjSerialPort, dwEventMask )) != ERROR_SUCCESS )
{
TRACE(_T("SerialPortCommEvtsThreadFn : Failed handling communications event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
continue;
}
// TODO: call WaitCommEvent again, if HandleCommOvEvent didn't already
break;
case WAIT_OBJECT_0 + numHandles - 1:
// TODO: do something with the received data, it's now in the buffer supplied to ReadFile
// TODO: call ReadFile again
break;
default:
dwError = ( dwObjectWaitState == WAIT_FAILED ) ? ::GetLastError() : dwObjectWaitState;
TRACE(_T("SerialPortCommEvtsThreadFn : Failed waiting for event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwError, _T(__FILE__), __LINE__);
break;
}
}
return dwError;
}
It looks like you're calling GetLastError() when you haven't checked that there's been an error.
You are calling
WaitCommEvent(pobjSerialPort->m_hSerialPort, &dwEventMask, &(pobjSerialPort->m_ovEvents))
The 1st and 3rd parameters of WaitCommEvent are input parameters, However you did not supply their initialization code.
How do you initialize m_hSerialPort? Do you call CreateFile with valid params and verify there is no error?
How do you initialize m_ovEvents? Do you call CreateEvent with valid params and verify there is no error?
Since you're getting ERROR_INVALID_PARAMETER, I think your problem is in the initialization of one of these parameters.