The code is below and it is part of a thread. pFileChange->m_hDirectory is of type HANDLE and pFileChange->m_eventFileChange if of type CEvent. CreateFile and ReadDirectoryChangesW return with a success. I am not able to figure out why i am getting an invalid handle status, please help!
UINT CFileChange::FileMontiorThread(LPVOID pArgs)
{
CFileChange* pFileChange = NULL;
pFileChange = (CFileChange*)pArgs;
pFileChange = (CFileChange*)pArgs;
CString str = pFileChange->m_strDirectory;
LPSTR strDirectory;
strDirectory = str.GetBuffer(str.GetLength());
PathRemoveFileSpec(strDirectory);
DWORD dwBytes = 0;
vector<BYTE> m_Buffer;
BOOL m_bChildren;
OVERLAPPED m_Overlapped;
HANDLE arrHandles[2] = { pFileChange->m_hDirectory, pFileChange->m_eventFileChange };
::ZeroMemory(&m_Overlapped, sizeof(OVERLAPPED));
DWORD dwBufferSize = 16384;
m_Buffer.resize(dwBufferSize);
m_bChildren = false;
pFileChange->m_hDirectory = ::CreateFile(
(LPCSTR)(LPCTSTR)strDirectory,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS
| FILE_FLAG_OVERLAPPED,
NULL);
if (pFileChange->m_hDirectory == INVALID_HANDLE_VALUE)
{
return false;
}
BOOL success = ::ReadDirectoryChangesW(
pFileChange->m_hDirectory, // handle to directory
&m_Buffer[0], // read results buffer
m_Buffer.size(), // length of buffer
m_bChildren, // monitoring option
FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME, // filter conditions
&dwBytes, // bytes returned
&m_Overlapped, // overlapped buffer
NULL); // no completion routine
DWORD dwWaitStatus;
while (!pFileChange->m_bKillThread)
{
dwWaitStatus = WaitForMultipleObjects(2, arrHandles, FALSE, INFINITE);
Switch(dwWaitStatus)
{
case WAIT_FAILED:
{
ULONG rc = 0;
rc = ::GetLastError();
LPVOID lpMsgBuf = NULL;
::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
rc,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR)&lpMsgBuf,
0,
NULL);
CString strErrMsg;
strErrMsg.Format(_T("%s, %s, Reason:%s"), "", "", (LPTSTR)lpMsgBuf);
break;
}
}
}
return 0;
}
Note that, as it specifies in the documentation, you can't wait on any type of handle.
Waiting on a directory handle isn't going to do what you think it should. Read this related question and its answers for more information and background reading.
As it seems you're trying to create a folder monitor, perhaps read this blog post for the correct way to use ReadDirectoryChangesW.
The answer was provided by Hans Passant in a comment:
You copy the handles into arrHandles too soon, before they are created. That cannot work of course.
Related
Code:
void CChristianLifeMinistryEditorDlg::OnSize(UINT nType, int cx, int cy)
{
CResizingDialog::OnSize(nType, cx, cy);
const CWnd* pFocus = GetFocus();
CComboBox* pFocusCombo = nullptr;
if (pFocus != nullptr)
{
if (pFocus->GetParent()->IsKindOf(RUNTIME_CLASS(CComboBox)))
{
pFocusCombo = dynamic_cast<CComboBox*>(GetFocus()->GetParent());
}
}
for (CWnd* pWnd = GetWindow(GW_CHILD); pWnd != nullptr; pWnd = pWnd->GetNextWindow(GW_HWNDNEXT))
{
if (pWnd == pFocusCombo)
{
// TODO: Sadly, by now, the control has already got all the text selected.
//pFocusCombo->SetEditSel(LOWORD(dwEditSel), HIWORD(dwEditSel));
}
else if (pWnd->IsKindOf(RUNTIME_CLASS(CComboBox)))
{
// This only works for combo boxes that are bound to controls
auto* pCombo = dynamic_cast<CComboBox*>(pWnd);
pCombo->SetEditSel(-1, -1);
}
else
{
CString strClassName;
if (::GetClassName(pWnd->GetSafeHwnd(), strClassName.GetBuffer(_MAX_PATH), _MAX_PATH))
{
if (strClassName == _T("ComboBox"))
{
auto* pCombo = (CComboBox*)pWnd;
//auto* pCombo = dynamic_cast<CComboBox*>(pWnd);
pCombo->SetEditSel(-1, -1);
}
}
strClassName.ReleaseBuffer();
}
}
if (m_pHtmlPreview != nullptr)
{
m_lblHtmlPreview.GetWindowRect(m_rctHtmlPreview);
ScreenToClient(m_rctHtmlPreview);
m_pHtmlPreview->MoveWindow(m_rctHtmlPreview);
}
}
I display the whole function for context. But I am specifically interested in this bit:
CString strClassName;
if (::GetClassName(pWnd->GetSafeHwnd(), strClassName.GetBuffer(_MAX_PATH), _MAX_PATH))
{
if (strClassName == _T("ComboBox"))
{
auto* pCombo = (CComboBox*)pWnd;
//auto* pCombo = dynamic_cast<CComboBox*>(pWnd);
pCombo->SetEditSel(-1, -1);
}
}
strClassName.ReleaseBuffer();
During code analysis updates I had many situations where I had to update C-Style casts. A lot of the time I was able to use static_cast, but, in some instances the compiler would then tell me I should use dynamic_cast.
I then found that my application was not working correctly and in debug mode isolated it to this bit:
//auto* pCombo = (CComboBox*)pWnd;
auto* pCombo = dynamic_cast<CComboBox*>(pWnd);
It turned out that the cast pointer pCombo was null. Yet, this never happens when I use the C-Style cast. As a result I have reverted to the C-Style cast. I saw this discussion (MFC Classes and C++ style casts) but I can't see that this is the reason (temporary pointers).
What cast should I be using that I can rely on in this situation?
C-style casting will try different c++ casting, it may choose reinterpret_cast static_cast. This is converting CWnd* to CComboBox*, for example:
CWnd* wnd = GetDlgItem(IDC_COMBO1);
CComboBox* combo = (CComboBox*)wnd;
In general, parent class can't "always" be converted to child. It depends if CComboBox m_combobox; for that ID was created.
A) m_combobox does exist:
In this case our wnd can be a reference to m_combobox.
CWnd::GetDlgItem etc. cast &m_combobox to CWnd*, they pass it around.
dynamic_cast checks it and converts back to CComboBox*.
MFC's IsKindOf will confirm if m_combobox was created.
B) m_combobox doesn't exist:
In this case our wnd is CWnd* object. MFC never created CComboBox for that control.
dynamic_cast tests it, can't convert to CComboBox*
static_cast works if wnd's classname is "ComboBox"
The code below should be okay. In this case you can skip dynamic_cast if you want, rely on static_cast. But it's better to use dynamic_cast if possible.
CComboBox* ptr = nullptr;
if (wnd->IsKindOf(RUNTIME_CLASS(CComboBox)))
ptr = dynamic_cast<CComboBox*>(wnd);
if(!ptr)
{
CString classname;
::GetClassName(wnd->m_hWnd, classname.GetBuffer(_MAX_PATH), _MAX_PATH);
classname.ReleaseBuffer();
if(classname == L"ComboBox")
ptr = static_cast<CComboBox*>(wnd);
}
I have come up with the following function which works as it should:
bool CChristianLifeMinistryStudentMaterialDlg::EncodeText(HWND hWnd, CString strCode)
{
bool bHandled = false;
map<HWND, CComboBox*> mapControls;
map<HWND, CString*> mapControlsText;
mapControls.emplace(m_cbMaterialAssignment1.GetSafeHwnd(), &m_cbMaterialAssignment1);
mapControls.emplace(m_cbMaterialAssignment2.GetSafeHwnd(), &m_cbMaterialAssignment2);
mapControls.emplace(m_cbMaterialAssignment3.GetSafeHwnd(), &m_cbMaterialAssignment3);
mapControls.emplace(m_cbMaterialAssignment4.GetSafeHwnd(), &m_cbMaterialAssignment4);
mapControlsText.emplace(m_cbMaterialAssignment1.GetSafeHwnd(), &m_strMaterialAssignment1);
mapControlsText.emplace(m_cbMaterialAssignment2.GetSafeHwnd(), &m_strMaterialAssignment2);
mapControlsText.emplace(m_cbMaterialAssignment3.GetSafeHwnd(), &m_strMaterialAssignment3);
mapControlsText.emplace(m_cbMaterialAssignment4.GetSafeHwnd(), &m_strMaterialAssignment4);
if (mapControls.find(::GetParent(hWnd)) != mapControls.end())
{
UpdateData(TRUE);
DWORD dwSel = mapControls[::GetParent(hWnd)]->GetEditSel();
CMeetingScheduleAssistantApp::EncodeText(*mapControlsText[::GetParent(hWnd)],
strCode, LOWORD(dwSel), HIWORD(dwSel));
UpdateData(FALSE);
bHandled = true;
}
else
{
map<HWND, CEdit*> mapControls;
map<HWND, CString*> mapControlsText;
mapControls.emplace(m_editBibleReading.GetSafeHwnd(), &m_editBibleReading);
mapControls.emplace(m_editDiscussionVideoTheme.GetSafeHwnd(), &m_editDiscussionVideoTheme);
mapControls.emplace(m_editDiscussionVideoMaterial.GetSafeHwnd(), &m_editDiscussionVideoMaterial);
mapControlsText.emplace(m_editBibleReading.GetSafeHwnd(), &m_strBibleReading);
mapControlsText.emplace(m_editDiscussionVideoTheme.GetSafeHwnd(), &m_strDiscussionVideoTheme);
mapControlsText.emplace(m_editDiscussionVideoMaterial.GetSafeHwnd(), &m_strDiscussionVideoMaterial);
if (mapControls.find(hWnd) != mapControls.end())
{
UpdateData(TRUE);
DWORD dwSel = mapControls[hWnd]->GetSel();
CMeetingScheduleAssistantApp::EncodeText(*mapControlsText[hWnd],
strCode, LOWORD(dwSel), HIWORD(dwSel));
UpdateData(FALSE);
bHandled = true;
}
}
return bHandled;
}
The code is straight forward to follow. But as you can see I have to potentially deal with either a comb box edit control or regular edit control. As a result, I have two sets of similar code.
Is it possible to consolidate some of this code without overcomplicating it too much? My project is set to the ISO C++ 17 Standard if that helps.
Update
Initially I thought I would try a single map of CWnd* pointers. But then I had the two problems of CComboBox verses CEdit.
CComboBox uses:
::GetParent(hWnd)
GetEditSel()
CEdit uses:
hWnd
GetSel()
By using a single list of CWnd* I no longer know which is a combo or a edit control.
—-
Update
The core problem I would like to solve is to have a single loop rather than the two.
One possibility would be to define an interface to the functionality you need, then a couple of implementations of that functionality. Add in a map to get from an HWND to the object you need, and you're off to the races:
class Writer {
virtual DWORD getSel() = 0;
CString* data;
public:
Writer(CString *data) : data(data) {}
void write(CString text) {
UpdateData(true);
DWORD selection = getSel();
CMeetingScheduleAssistantApp::EncodeText(*data, text, LOWORD(dwSel), HIWORD(dwSel));
UpdateData(false);
}
virtual ~Writer() = default;
};
class ComboBoxWriter : public writer {
CWnd *parent;
DWORD getSel() override { return parent->GetEditSel(); }
public:
ComboBoxWriter(CComboBox &dest, CString &data) : Writer(&data), parent(dest.GetParent()) {}
};
class EditCtrlWriter : public Writer {
CEdit *ctrl;
DWORD getSel() override { return ctrl->GetSel(); }
public:
EditCtrlWriter(CEdit &ctrl, CString &data) : Writer(&data), ctrl(&ctrl) {}
};
bool CChristianLifeMinistryStudentMaterialDlg::EncodeText(HWND hWnd, CString strCode) {
static std::map<HWND, Writer*> controls {
{ m_cbMaterialAssignment1.GetSafeHwnd(), new ComboBoxWriter(&m_cbMaterialAssignment1, &m_strMaterialAssignment1) },
{ m_cbMaterialAssignment2.GetSafeHwnd(), new ComboBoxWriter(&m_cbMaterialAssignment2, &m_strMaterialAssignment2) },
{ m_cbMaterialAssignment3.GetSafeHwnd(), new ComboBoxWriter(&m_cbMaterialAssignment3, &m_strMaterialAssignment3) },
{ m_cbMaterialAssignment4.GetSafeHwnd(), new ComboBoxWriter(&m_cbMaterialAssignment4, &m_strMaterialAssignment4) },
{ m_editBibleReading.GetSafeHwnd(), new EditCtrlWriter(&m_editBibleReading, &m_strBibleReading) },
{ m_editDiscussionVideoTheme.GetSafeHwnd(), new EditCtrlWriter(&m_editDiscussionVideoTheme, &m_strDiscussionVideoTheme) },
{ m_editDiscussionVideoMaterial.GetSafeHwnd(), new EditCtrlWriter(&m_editDiscussionVideoMaterial, &m_strDiscussionVideoMaterial) }
};
auto ctrl = controls.find(hwnd);
if (ctrl == controls.end())
return false;
ctrl->second->write(strCode);
return true;
}
This isn't a lot shorter overall (in fact, it's almost the same length), but quite a bit more of that length is boilerplate that's pretty easy to ignore.
Your building maps of HWND to MFC controls looks very strange.
Surely MFC already has that map. See if this helps: https://learn.microsoft.com/en-us/cpp/mfc/accessing-run-time-class-information?view=msvc-160
Based on the comments to one of the answers (#vlad-feinstein):
all HWND handles are unique system-wide
I decided that I could take a much simpler approach to simplifying my code:
bool CChristianLifeMinistryStudentMaterialDlg::EncodeText(HWND hWnd, CString strCode)
{
map<HWND, CWnd*> mapControls; // Use generic CWnd pointers
map<HWND, CString*> mapControlsText;
// Lookup map of controls
mapControls.emplace(m_cbMaterialAssignment1.GetSafeHwnd(), &m_cbMaterialAssignment1);
mapControls.emplace(m_cbMaterialAssignment2.GetSafeHwnd(), &m_cbMaterialAssignment2);
mapControls.emplace(m_cbMaterialAssignment3.GetSafeHwnd(), &m_cbMaterialAssignment3);
mapControls.emplace(m_cbMaterialAssignment4.GetSafeHwnd(), &m_cbMaterialAssignment4);
mapControls.emplace(m_editBibleReading.GetSafeHwnd(), &m_editBibleReading);
mapControls.emplace(m_editDiscussionVideoTheme.GetSafeHwnd(), &m_editDiscussionVideoTheme);
mapControls.emplace(m_editDiscussionVideoMaterial.GetSafeHwnd(), &m_editDiscussionVideoMaterial);
// Lookup map of text values
mapControlsText.emplace(m_cbMaterialAssignment1.GetSafeHwnd(), &m_strMaterialAssignment1);
mapControlsText.emplace(m_cbMaterialAssignment2.GetSafeHwnd(), &m_strMaterialAssignment2);
mapControlsText.emplace(m_cbMaterialAssignment3.GetSafeHwnd(), &m_strMaterialAssignment3);
mapControlsText.emplace(m_cbMaterialAssignment4.GetSafeHwnd(), &m_strMaterialAssignment4);
mapControlsText.emplace(m_editBibleReading.GetSafeHwnd(), &m_strBibleReading);
mapControlsText.emplace(m_editDiscussionVideoTheme.GetSafeHwnd(), &m_strDiscussionVideoTheme);
mapControlsText.emplace(m_editDiscussionVideoMaterial.GetSafeHwnd(), &m_strDiscussionVideoMaterial);
// Determine if the use clicked on a combo or edit control
bool bIsComboControl = false;
HWND hWndToUse = nullptr;
if (mapControls.find(::GetParent(hWnd)) != mapControls.end())
{
bIsComboControl = true;
hWndToUse = ::GetParent(hWnd);
}
else if (mapControls.find(hWnd) != mapControls.end())
hWndToUse = hWnd;
if (hWndToUse == nullptr)
return false;
// Process
UpdateData(TRUE);
// Get the correct selection from the control
DWORD dwSel = (bIsComboControl) ?
((CComboBox*)mapControls[hWndToUse])->GetEditSel() :
((CEdit*)mapControls[hWndToUse])->GetSel();
// Encode the text
CMeetingScheduleAssistantApp::EncodeText(*mapControlsText[hWndToUse],
strCode, LOWORD(dwSel), HIWORD(dwSel));
UpdateData(FALSE);
return true;
}
I have defined a base class using std::thread. For the child class, I perform some initialization of member variables and then start the thread using m_thread.reset(new std::thread(&MyClass::ThreadMain, this)); where m_thread is a member of MyClass. The purpose of the class is to read data from a serial port and report to a parent. The posix message queue handle of the parent is passed to MyClass during initialization before the thread is created. On running I get exceptions and I see that member variables that were initialized before the thread started appear to be no longer valid using the watch in GDB.
It appears as if the first message on the serial port is received and passed validation in order to get to the SendToParent call. At this call, it appears that I lose the stack. I tried running cppcheck to see if I have any memory leaks or buffer overflows and found nothing.
void MyClass::ThreadMain(void)
{
ssize_t bytesRead = 0;
UINT8 buffer[256];
UINT8 message[256];
BOOL partialMessage = FALSE;
UINT8 messageIndex = 0;
UINT8 payloadLength = 0;
// read data from the UART
while(1)
{
// the UART is setup to pend until data is available
bytesRead = read(m_radioFileDescriptor, buffer, sizeof(buffer));
if (FAIL == bytesRead)
{
LOG_SYSTEM_INFO("UART Read interrupted by a system call");
}
else if (bytesRead > 0)
{
// build the message
for(ssize_t i = 0 ; i < bytesRead ; i++)
{
if (FALSE == partialMessage)
{
// have we found the start of the message?
if(START_BYTE == buffer[i])
{
// start of new message
messageIndex = 0;
message[messageIndex] = buffer[i];
partialMessage = TRUE;
messageIndex++;
}
}
else
{
// keep building the message until the expected length is reached
if(LENGTH_POSITION == messageIndex)
{
// capture the expected message length
message[messageIndex] = buffer[i];
messageIndex++;
payloadLength = buffer[i];
}
else
{
message[messageIndex] = buffer[i];
messageIndex++;
// check for expected length and end byte
if((messageIndex == payloadLength) && (END_BYTE == buffer[i]))
{
// this should be a valid message but need to confirm by checking for a valid checksum
UINT8 messageChecksum = message[messageIndex - CHKSUM_POS_FROM_END];
UINT8 calculatedChecksum = RadioProtocol::Instance().GenerateRadioChecksum(message, (payloadLength - CHKSUM_POS_FROM_END));
if (messageChecksum == calculatedChecksum)
{
SendToParent(message, payloadLength);
}
else
{
LOG_SYSTEM_ERROR("Checksum FAILURE");
}
// reset for the next message
partialMessage = FALSE;
messageIndex = 0;
}
else if((messageIndex == payloadLength) && (END_BYTE != buffer[i]))
{
// malformed message - throw out and look for start of next message
LOG_SYSTEM_ERROR("Bytes read exceeded expected message length");
partialMessage = FALSE;
messageIndex = 0;
}
}
}
} // end for loop of bytes read on the port
}
else
{
LOG_SYSTEM_INFO("Read returned 0 bytes which is unexpected");
}
}
}
void MyClass::SendToParent(UINT8* pMsg, UINT8 size)
{
if ((pMsg != NULL) && (m_parentQueueHandle > 0))
{
// message is valid - pass up for processing
MsgQueueMessage msgToSend;
msgToSend.m_msgHeader = UART_MESSASGE;
bzero(msgToSend.m_msgData, sizeof(msgToSend.m_msgData));
for (UINT8 i = 0; i < size; i++)
{
msgToSend.m_msgData[i] = pMsg[i];
}
if (FAIL == msgsnd(m_parentQueueHandle, &msgToSend, sizeof(msgToSend), IPC_NOWAIT))
{
LOG_SYSTEM_ERROR("FAILED to send message on queue");
}
}
}
This acts like I am performing a buffer overflow but I just can't see it. When I set a breakpoint at the line UINT8 messageChecksum = message[messageIndex - CHKSUM_POS_FROM_END]; all data in the watch window appear valid. If I step over to the next line then the data, m_parentQueueHandle as an example, gets blown away.
This is my first time working with c++11 threads and particularly with c++. Any help or insights would be appreciated.
I think I found the issue. I added a bunch of printfs and found that the destructor for the class was being called. Much further upstreamI had the parent object being created as a local variable and it was going out of scope. This caused the child to go out of scope but the threads were still running. I certainly need to clean up the threads in the destructor.
When i am trying to add resources to another file at run time, some of the earlier resources are getting deleted. Please find the source code below:
void CResourceIncludeSampleDlg::OnBnClickedButton1()
{
CString strInputFile = _T("C:\\SampleData\\FileToInsert.zip"); // This File is 100 MB
HANDLE hFile = CreateFile(strInputFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD FileSize = GetFileSize(hFile, NULL);
BYTE *pBuffer = new BYTE[FileSize];
DWORD dwBytesRead;
ReadFile(hFile, pBuffer, FileSize, &dwBytesRead, NULL);
for (int iIndex = 1; iIndex <= 4; iIndex++)
{
InsertResource(FileSize, iIndex, pBuffer);
}
CloseHandle(hFile);
}
void CResourceIncludeSampleDlg::InsertResource(DWORD FileSize, int iIndex, BYTE *pBuffer)
{
CString strOutputFile = _T("C:\\SampleData\\ResourceIncludeSample_Source.exe");
int iResourceID = 300 + iIndex;
HANDLE hResource = BeginUpdateResource(strOutputFile, FALSE);
if (INVALID_HANDLE_VALUE != hResource)
{
if (UpdateResource(hResource, _T("VIDEOBIN"), MAKEINTRESOURCE(iResourceID), MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
(LPVOID)pBuffer, FileSize) == TRUE)
{
EndUpdateResource(hResource, FALSE);
}
}
}
After completion of the execution, i am expecting output as 301, 302, 303 and 304 added under "VIDEOBIN" category. But only 2 (sometimes 3) resources are present. One resource is always deleted.
Could you please let me know what could be wrong or any fix for the same ?
Any help or sample source code is greatly appreciated.
Thanks and Regards,
YKK Reddy
You need delete[] pBuffer after closing the file. It should be RT_RCDATA instead of _T("VIDEOBIN") although custom resource name may not be the cause of this particular problem.
Unlike the WriteProfileString method, the GetProfileString method does not offer a way to check if a section or an option even exists in the profile. All I can get is a default string. I am trying to implement a class representing my profile and I would like to have a
BOOL HasSection(CString sSection)
and a
BOOL HasOption(CString sSection, CString sOption)
method in it.
It is very easy to do. Please read the MSDN Article carefully. Notice that when you pass NULL to first or a second parameter, the function returns a list of all section names (in the former case) or keys (in the latter case).
So for you to implement HaseOption() method you would have a code similar to this one:
BOOL HasSection(CString sSection)
{
DWORD dwSize = 100, dwRequired;
LPTSTR lpBuffer = new TCHAR[dwSize];
BOOL bExists = FALSE;
dwRequired = ::GetProfileString(NULL, NULL, _T(""), lpBuffer, dwSize);
while(dwRequired == dwSize - 2)
{
// buffer is too small
delete [] lpBuffer;
dwSize = dwRequired + 100;
lpBuffer = new TCHAR[dwSize];
dwRequired = ::GetProfileString(NULL, NULL, _T(""), lpBuffer, dwSize);
}
if(dwRequired)
{
LPTSTR lpszFound = lpBuffer;
do
{
if(sSection.CompareNoCase(lpszFound) == 0)
{
bExists = TRUE; break;
}
} while(*(lpszFound = _tcsninc(lpszFound, _tcsnbcnt(lpszFound, dwRequired)+1)));
}
delete [] lpBuffer;
return bExists;
}
I have not tested the code, just cut-n-pasted from my program and modified a bit. Please test the code yourself