Using buffer or pszText member of LVITEM structure upon LVM_GETITEM - c++

If I want to read a value from a certain item and sub-item in a list-view I do the following:
const int MAX_SIZE = 256;
char szBuffer[MAX_SIZE];
LVITEM lvItem = {0};
lvItem.iItem = rowIndex;
lvItem.iSubItem = subItemIndex;
lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
lvItem.cchTextMax = MAX_SIZE;
lvItem.pszText = szBuffer;
// Get item
SendMessage(hListView, LVM_GETITEM, 0, (LPARAM)&lvItem);
// Print value, which one always guaranteed to work?
std::cout << lvItem.pszText << std::endl;
std::cout << lvItem.szBuffer << std::endl;
Now, reading the MSDN Documentation on LVM_GETITEM we see that it says
Applications should not assume that the text will necessarily be placed in the specified buffer. The control may instead change the pszText member of the structure to point to the new text, rather than place it in the buffer.
How do we know when it is safe to use our buffer (szBuffer in this example) rather than using
lvItem.pszText
directly?
Maybe we always have to do (or similar):
if(lvItem.pszText != szBuffer)
snprintf(szBuffer, MAX_SIZE, "%s", lvItem.pszText);
to copy the value from the pszText of the LVITEM structure into
our buffer (after we have sent the LVM_GETITEM message of course).

Related

Printing different documents silently in C++

I have folder of different documents like: pdf, txt, rtf, images.
My case is to send all documents to the printer (print it). Used framework is MFC and WinAPI. Current implementation has dialog box for choose documents and another dialog for choose printer.
Then question appears, how to print it all? Do I need to convert every documents to PDF, then merge it and print one pdf document? I will appreciate any advice in that field.
void CMultipleDocsPrintTestDlg::OnBnClickedButton1()
{
TCHAR strFilter[] = { _T("Rule Profile (*.pdf)||") };
// Create buffer for file names.
const DWORD numberOfFileNames = 100;
const DWORD fileNameMaxLength = MAX_PATH + 1;
const DWORD bufferSize = (numberOfFileNames * fileNameMaxLength) + 1;
CFileDialog fileDlg(TRUE, _T("pdf"), NULL, OFN_ALLOWMULTISELECT, strFilter);
TCHAR* filenamesBuffer = new TCHAR[bufferSize];
// Initialize beginning and end of buffer.
filenamesBuffer[0] = NULL;
filenamesBuffer[bufferSize - 1] = NULL;
// Attach buffer to OPENFILENAME member.
fileDlg.GetOFN().lpstrFile = filenamesBuffer;
fileDlg.GetOFN().nMaxFile = bufferSize;
// Create array for file names.
CString fileNameArray[numberOfFileNames];
if (fileDlg.DoModal() == IDOK)
{
// Retrieve file name(s).
POSITION fileNamesPosition = fileDlg.GetStartPosition();
int iCtr = 0;
while (fileNamesPosition != NULL)
{
fileNameArray[iCtr++] = fileDlg.GetNextPathName(fileNamesPosition);
}
}
// Release file names buffer.
delete[] filenamesBuffer;
CPrintDialog dlg(FALSE);
dlg.m_pd.Flags |= PD_PRINTSETUP;
CString printerName;
if (dlg.DoModal() == IDOK)
{
printerName = dlg.GetDeviceName();
}
// What next ???
}
You could make use of ShellExecute to do this. The parameter lpOperation can be set to print. To quote:
Prints the file specified by lpFile. If lpFile is not a document file, the function fails.
As mentioned in a similar discussion here on StackOverflow (ShellExecute, "Print") you should keep in mind:
You need to make sure that the machine's associations are configured to handle the print verb.
You referred to pdf, txt, rtf, images which should all be supported I would think by this mechanism.
ShellExecute(NULL, "print", fileNameArray[0], nullptr, nullptr, SW_SHOWNORMAL);
The last parameter might have to be changed (SW_SHOWNORMAL). This code would be put in a loop so you could call it for each file. And note that the above code snippet has not been tested.

c++ WINAPI Shared Memory array of structs

I'm trying to share an array of structs through shared named memory using the WINAPI. I'm able to create and manage the shared memory but when trying to share an array of structs the size of the array is always 0 upon reading.
Below is test code i have written which should write/read an array of 10 entries, but even this is failing. My goal is however to write/read a dynamic array of structs containing 2 dynamic arrays and the info they already contain at the moment.
I'm aware i shouldn't share pointers between processes as they could point to a random value. Therefor i'm allocating memory for the arrays using new.
This is what i have so far:
Shared in both processes:
#define MEMSIZE 90024
typedef struct {
int id;
int type;
int count;
} Entry;
Process 1:
extern HANDLE hMapObject;
extern void* vMapData;
std::vector<Entry> entries;//collection of entries
BOOL DumpEntries(TCHAR* memName) {//Returns true, writing 10 entries
int size = min(10, entries.size());
Entry* eArray = new Entry[size];
for (int i = 0; i < size; i++) {
eArray[i] = entries.at(i);
}
::hMapObject = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, MEMSIZE, memName);
if (::hMapObject == NULL) {
return FALSE;
}
::vMapData = MapViewOfFile(::hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, MEMSIZE);
if (::vMapData == NULL) {
CloseHandle(::hMapObject);
return FALSE;
}
CopyMemory(::vMapData, eArray, (size * sizeof(Entry)));
UnmapViewOfFile(::vMapData);
//delete[] eArray;
return TRUE;
}
Process 2:
BOOL ReadEntries(TCHAR* memName, Entry* entries) {//Returns true reading 0 entries
HANDLE hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, memName);
if (hMapFile == NULL) {
return FALSE;
}
Entry* tmpEntries = (Entry*)(MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 10 * sizeof(Entry)));
if (tmpEntries == NULL) {
CloseHandle(hMapFile);
return FALSE;
}
entries = new Entry[10];
for (int i = 0; i < 10; i++) {
entries[i] = tmpEntries[i];
}
UnmapViewOfFile(tmpEntries);
CloseHandle(hMapFile);
return TRUE;
}
Writing the 10 entries seems to be working but when trying to read the memory it returns successfully and the size
of the array is 0, like so:
Entry* entries = NULL;
if (ReadEntries(TEXT("Global\Entries"), entries)) {
int size = _ARRAYSIZE(entries);
out = "Succesfully read: " + to_string(size);// Is always 0
}
So my question is, what am I doing wrong? I'm sharing the same struct between 2 processes, i'm allocating new memory for the entries to be written to and copying the memory with a size of 10 * sizeof(Entry);. When trying to read I also try to read 10 * sizeof(Entry); bytes and cast the data to a Entry*. Is there something I'm missing? All help is welcome.
Based on cursory examination, this code appears to attempt to map structures containing std::strings into shared memory, to be used by another process.
Unfortunately, this adventure is doomed, before it even gets started. Even if you get the array length to pass along correctly, I expect the other process to crash immediately, as soon as it even smells the std::string that the other process attempted to map into shared memory segments.
std::strings are non-trivial classes. A std::string maintains internal pointers to a buffer where the actual string data is kept; with the buffer getting allocated on the heap.
You do understand that sizeof(std::string) doesn't change, whether the string contains five characters, or the entire contents of "War And Peace", right? Stop and think for a moment, how that's possible, in just a few bytes that it takes to store a std::string?
Once you think about it for a moment, it should become crystal clear why mapping one process's std::strings into a shared memory segment, and then attempting to grab them by another process, is not going to work.
The only thing that can be practically mapped to/from shared memory is plain old data; although you could get away with aggregates, in some cases, too.
I'm afraid the problem only lies in the _ARRAYSIZE macro. I could not really find it in MSDN, but I found references for _countof or ARRAYSIZE in other pages. All are defined as sizeof(array)/sizeof(array[0]). The problem is that it only make sense for true arrays defined as Entry entries[10], but not for a pointer to such an array. Technically when you declare:
Entry* entries;
sizeof(entries) is sizeof(Entry *) that is the size of a pointer. It is smaller than the size of the struct so the result of the integer division is... 0!
Anyway, there are other problems in current code. The correct way to exchange a variable size array through shared memory is to use an ancillary structure containing a size and the array itself declared as incomplete:
struct EntryArray {
size_t size;
Entry entries[];
};
You could dump it that way:
BOOL DumpEntries(TCHAR* memName) {//Returns true, writing 10 entries
int size = min(10, entries.size());
EntryArray* eArray = (EntryArray *) malloc(sizeof(EntryArray) + size * sizeof(Entry));
for (int i = 0; i < size; i++) {
eArray->entries[i] = entries.at(i);
}
eArray->size = size;
::hMapObject = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, MEMSIZE, memName);
if (::hMapObject == NULL) {
return FALSE;
}
::vMapData = MapViewOfFile(::hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, MEMSIZE);
if (::vMapData == NULL) {
CloseHandle(::hMapObject);
return FALSE;
}
CopyMemory(::vMapData, eArray, (sizeof(EntryArray) + size * sizeof(Entry)));
UnmapViewOfFile(::vMapData);
free(eArray);
return TRUE;
}
You can note that as the last member of the struct is an incomplete array, it is allocated 0 size, so you must allocate the size of the struct + the size of the array.
You can then read it from memory that way:
size_t ReadEntries(TCHAR* memName, Entry*& entries) {//Returns the number of entries or -1 if error
HANDLE hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, memName);
if (hMapFile == NULL) {
return -1;
}
EntryArray* eArray = (EntryArray*)(MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 10 * sizeof(Entry)));
if (eArray == NULL) {
CloseHandle(hMapFile);
return -1;
}
entries = new Entry[10]; // or even entries = new Entry[eArray->size];
for (int i = 0; i < 10; i++) { // same: i<eArray->size ...
entries[i] = eArray->entries[i];
}
UnmapViewOfFile(eArray);
CloseHandle(hMapFile);
return eArray.size;
}
But here again you should note some differences. As the number of entries is lost when eArray vanishes, it is passed as the return value from the function. And and you want to modify the pointer passed as second parameter, you must pass it by reference (if you pass it by value, you will only change a local copy and still have NULL in original variable after function returns).
There are still some possible improvement in your code, because the vector entries is global when it could be passed as a parameter to DumpEntries, and hMapObject is also global when it could be returned by the function. And in DumpObject you could avoid a copy by building directly the EntryArray in shared memory:
HANDLE DumpEntries(TCHAR* memName, const std::vector<Entry>& entries) {
//Returns HANDLE to mapped file (or NULL), writing 10 entries
int size = min(10, entries.size());
HANDLE hMapObject = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, MEMSIZE, memName);
if (hMapObject == NULL) {
return NULL;
}
void * vMapData = MapViewOfFile(hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, MEMSIZE);
if (vMapData == NULL) {
CloseHandle(hMapObject);
return NULL;
}
EntryArray* eArray = (EntryArray*) vMapData;
for (int i = 0; i < size; i++) {
eArray->entries[i] = entries.at(i);
}
eArray->size = size;
UnmapViewOfFile(vMapData);
return hMapObject;
}
And last but not least, the backslash \ is a special quoting character in a string litteral, and it must quote itself. So you should write .TEXT("Global\\Entries")
I did it some changes to your code:
PROCESS 1:
BOOL DumpEntries(TCHAR* memName)
{
int size = entries.size() * sizeof(Entry) + sizeof(DWORD);
::hMapObject = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, memName);
if (::hMapObject == NULL) {
return FALSE;
}
::vMapData = MapViewOfFile(::hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, size);
if (::vMapData == NULL) {
CloseHandle(::hMapObject);
return FALSE;
}
(*(DWORD*)::vMapData) = entries.size();
Entry* eArray = (Entry*)(((DWORD*)::vMapData) + 1);
for(int i = entries.size() - 1; i >= 0; i--) eArray[i] = entries.at(i);
UnmapViewOfFile(::vMapData);
return TRUE;
}
PROCESS 2:
BOOL ReadEntries(TCHAR* memName, Entry** entries, DWORD &number_of_entries) {
HANDLE hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, memName);
if (hMapFile == NULL) {
return FALSE;
}
DWORD *num_entries = (DWORD*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (num_entries == NULL) {
CloseHandle(hMapFile);
return FALSE;
}
number_of_entries = *num_entries;
if(number_of_entries == 0)
{
// special case: when no entries was found in buffer
*entries = NULL;
return true;
}
Entry* tmpEntries = (Entry*)(num_entries + 1);
*entries = new Entry[*num_entries];
for (UINT i = 0; i < *num_entries; i++) {
(*entries)[i] = tmpEntries[i];
}
UnmapViewOfFile(num_entries);
CloseHandle(hMapFile);
return TRUE;
}
PROCESS 2 (usage example):
void main()
{
Entry* entries;
DWORD number_of_entries;
if(ReadEntries(TEXT("Global\\Entries", &entries, number_of_entries) && number_of_entries > 0)
{
// do something
}
delete entries;
}
CHANGES:
I am not using a static size (MEMSIZE) when i map memory, i am calculating exactly memory requiered
I put a "header" to memory mapped, a DWORD for send to process 2 number of entries in buffer
your ReadEntries definition is wrong, i fix it changing Entry* to Entry**
NOTES:
you need to close ::hMapObject handle in process 1 before process 2 calls ReadEntries
you need to delete entries memory returned for ReadEntries in process 2, before you use it
this code works only under same windows user, if you want to communicate a services with user process (for example), you need to handle SECURITY_ATTRIBUTES member in CreateFileMapping procedure

how to change Text to Speech voice and how to insert characters into char array

I need to change the voice of the Text To Speech engine. When a menu is selected (ID_SPEAK_PLAY), I get the text of an edit box and simply read it.
My situation can be solved in two ways :
insert the XML code at the begining of ptrData without using strncat or other functions that involve creating other wchar_t* buffers (memory issues ). StringCchPrintf is not working.
change the voice in some other way that i don't know.
Here is my code :
case ID_SPEAK_PLAY:
text_size = SendMessage(h_edit, WM_GETTEXTLENGTH, 0, 0);
text_size += 100;
ptrData = new wchar_t[text_size];
SendMessage(h_edit, WM_GETTEXT, text_size, (LPARAM)ptrData);
StringCchPrintf(ptrData, text_size, L"<voice required = \"Gender=Female;Age=Teen\"> %s", ptrData);
pVoice->Speak(ptrData, SPF_ASYNC | SPF_IS_XML, NULL);
delete [] ptrData;
break;
StringCchPrintf is not working.
That is because you ignored the warning in the documentation:
Behavior is undefined if the strings pointed to by pszDest, pszFormat, or any argument strings overlap.
You are specifying ptrData as both pszDest and an argument string, so your code has undefined behavior. You must use separate buffers when using StringCchPrintf():
case ID_SPEAK_PLAY:
text_size = SendMessage(h_edit, WM_GETTEXTLENGTHW, 0, 0) + 1;
ptrData = new wchar_t[text_size];
SendMessage(h_edit, WM_GETTEXTW, text_size, (LPARAM)ptrData);
speak_size = text_size + 100;
speakData = new wchar_t[speak_size];
StringCchPrintf(speakData, speak_size, L"<voice required = \"Gender=Female;Age=Teen\"> %s", ptrData);
pVoice->Speak(speakData, SPF_ASYNC | SPF_IS_XML, NULL);
delete [] speakData;
delete [] ptrData;
break;
Alternatively, just skip StringCchPrintf() and let WM_GETTEXT populate your single buffer directly:
case ID_SPEAK_PLAY:
{
const wchar_t *xml = L"<voice required = \"Gender=Female;Age=Teen\"> ";
const int xml_size = lstrlenW(xml);
text_size = SendMessage(h_edit, WM_GETTEXTLENGTHW, 0, 0);
ptrData = new wchar_t[text_size + xml_size + 1];
lstrcpyW(ptrData, xml);
SendMessage(h_edit, WM_GETTEXTW, text_size+1, (LPARAM)(ptrData+xml_size));
pVoice->Speak(ptrData, SPF_ASYNC | SPF_IS_XML, NULL);
delete [] ptrData;
break;
}
change the voice in some other way that i don't know.
Instead of inserting XML in front of your text, you can call the ISpVoice::SetVoice() method before calling ISpVoice::Speak(). Use SpEnumTokens() to know which voices are installed, or use SpFindBestToken() to search for a voice that matches the criteria you need.

Win32 ListView - handle invalid after LVM_INSERTITEM

I have a problem a window handle (window class = WC_LISTVIEW) after calling
SendMessage(hListView_, LVM_INSERTITEM , 0, (LPARAM)&lvItem);
where
hListView_
is a handle to a list view window and
lvItem
is an LVITEM structure. The following code
std::cout << "Last error: " << GetLastError() << std::endl;
SendMessage(hListView_, LVM_INSERTITEM , 0, (LPARAM)&lvItem);
std::cout << "Last error: " << GetLastError() << std::endl;
prints
Last error: 0
Last error: 6
According to Win32 System Error Codes code 6 means ERROR_INVALID_HANDLE.
I create the LVITEM structure as follows:
// define a char-buffer
char szBuffer[256];
szBuffer[0] = '\0';
// create new list view item
LVITEM lvItem;
lvItem.cchTextMax = 256;
lvItem.mask = LVIF_TEXT;
lvItem.iItem = 0;
lvItem.stateMask = 0;
lvItem.state = 0;
lvItem.iSubItem = 0;
snprintf(szBuffer, 256, "%s", myString.c_str());
lvItem.pszText = szBuffer;
This code is called from the same thread which created the window (list view).
Also note that I have
lvItem.iSubItem = 0;
which is required according to LVM_INSERTITEM. The list view is empty prior to this call. Moreover, I can actually see the value being inserted in the list view (i.e. I can see the item in the list view in the GUI).
However, when I try to use the window handle after this the application crashes (no exception, just crashes).
Greatful for any hints on what might cause this.
Thank you.
Thank you David Heffernan for your help. I did at last find the problem, which was (as you suggested) in a different place in the code.
I had missed the following line (in a different function which I call prior to the code I posted above)
lvItem.pszText = szBuffer;
where
char szBuffer[256]; // char-buffer
when doing the follwing call
SendMessage(hListView_, LVM_GETITEMTEXT, (WPARAM) i, (LPARAM) &lvItem);
Thanks a lot for your help!
Edit: If I had done
lvItem.pszText = myString.c_str();
rather than the char-buffer this would probably not have happened, so thanks for that hint!
There's no reason for you to call GetLastError. The documentation for LVM_INSERTITEM doesn't say that you should do so. All it says is that SendMessage returns the index of the new item on success, and -1 on failure. So, check for errors by inspecting the value returned by SendMessage.
The other problem is that you are not initialising all the fields of LVITEM. That's always a mistake. You can use an initialising declaration like this:
LVITEM lvItem = { 0 };
There's no real need for a separate buffer for the text. You can do it all like this:
LVITEM lvItem = { 0 };
lvItem.mask = LVIF_TEXT;
lvItem.cchTextMax = myString.length() + 1;
lvItem.pszText = myString.c_str();
int indexOfNewItem = SendMessage(hListView_, LVM_INSERTITEM, 0, (LPARAM)&lvItem);
if (indexOfNewItem == -1)
// deal with failure
It's quite possible that your error lies elsewhere in fact. I don't see any particular reason for that SendMessage call to lead to an application crash. At least now you know how to check for errors when sending LVM_INSERTITEM. If that does not result in an error then the evidence would be that the crash is caused by some other code and you have mistakenly identified this code because you erroneously called GetLastError when its value was meaningless.

ListView_GetItem() macro & LV_GETITEMTEXT returns empty STRING while getting another APP listview items

I am fully aware if i try to get buffer of another app listview items that i need to solve memory space boundary so What i am doing is hooking which returns well, no error, success then
enter code here
LVITEM lvi;
lvi.cchTextMax = 552;
lvi.mask = LVIF_TEXT;
_tfreopen(path,_T("w"),stdout);
for (int nItem = 0; nItem < nMaxItems; nItem++) {
// Get the name and position of a ListView item.
lvi.iSubItem = 0;
lvi.iItem = nItem;
lvi.pszText = szName;
ListView_GetItem(hWndLV, &lvi);
wprintf(L"%s, ", szName);
wprintf(L"%s, ", lvi.pszText);
for ( int subitem = 0; subitem < columns; subitem++)
{
lvi.iSubItem = subitem;
lvi.iItem = nItem;
lvi.pszText = szName;
ListView_GetItem(hWndLV, &lvi);
wprintf(L"%s, ", szName);
wprintf(L"%s, ", lvi.pszText);
}
wprintf(L"%s", "\n");
}
wprintf(L"%s", GetLastError());
fclose (stdout);
No error with getlasterror and this works perfect on any other listview i tried before except this app, is it possible to still get item text somehow ?
Maybe it is an owner-drawn listview (LVS_OWNERDRAWFIXED)? If this is the case it will not be possible to get the text without private knowledge of the app that owns it.
You don't need to use hooking to get across the process boundary. You can use WriteProcessMemory/ReadProcessMemory as described in the following Code Project article: http://www.codeproject.com/KB/threads/int64_memsteal.aspx
What you have to watch out for is crossing a 32/64 bit boundary. I know of now way to achieve that.