The MASM Forum Archive 2004 to 2012

General Forums => The Campus => Topic started by: Seb on September 27, 2006, 11:40:58 PM

Title: Copy listbox items to clipboard
Post by: Seb on September 27, 2006, 11:40:58 PM
Hey there!

I've decided to migrate from VB.NET (yes!!! :dazzled:) to Assembly for this project (performance reasons :U), and until now, the GUI stuff has progressed smootly. In the main window, I got a "console" or "info" listbox (call it whatever you like) where the application sometimes post messages to let the user know what's happening (if an error occurs, if everything goes fine etc) which I assigned a popup menu to (CreatePopupMenu/AppendMenu). Now, I want to be able to copy the entire listbox contents to a buffer and then set it as clipboard data, but it doesn't work as expected. It only copies one character, and when testing multiple times in a row, it copies bogus data. I want this to be as fast as possible, but due to the bugs in the code, I'm sticking with szCopy for readability for you guys and simplicity sake until I got the basics working. I apologize if any bug(s) are blatant, I'm quite unexperienced within Assembler development at serious level (mainly been coding small, personal tools before) and attempting late-night coding sessions without a proper dose of coffee is NOT recommended.:bg

This is the code:


invoke SendDlgItemMessage,hwnd,IDC_CONSOLE,LB_GETCOUNT,0,0
test eax,eax
jz failed
mov cnt,eax
imul eax,sizeof item ; Get total number of bytes to allocate.
mov ecx,eax
invoke GlobalAlloc,GMEM_DDESHARE AND GMEM_MOVEABLE,ecx
mov gbuf,eax
xor ecx,ecx
iterator_begin:
invoke SendDlgItemMessage,hwnd,IDC_CONSOLE,LB_GETTEXT,ecx,addr item
inc ecx ; Increase counter.
inc eax
jz iterator_end ; It failed.
dec eax
mov ebx,eax ; Length of this item.
invoke GlobalLock,gbuf
mov glock,eax
invoke szCopy,addr item,glock ; I'd rather perform my own string copying here,
; but for simplicity (and readability for you guys)
mov byte ptr [glock+eax+1],0dh ; I'm sticking with szCopy until I got it working.
mov byte ptr [glock+eax+2],0ah  ; Will this work?
iterator_end:
invoke GlobalUnlock,glock
cmp ecx,cnt
jb iterator_begin
invoke GlobalLock,gbuf
mov byte ptr [eax+1],0h
invoke GlobalUnlock,glock
invoke OpenClipboard,NULL
jz clip_failed
invoke EmptyClipboard
invoke GlobalLock,gbuf
invoke SetClipboardData,CF_TEXT,gbuf
invoke GlobalUnlock,glock
invoke CloseClipboard
invoke GlobalFree,gbuf
clip_failed:
.endif


Thanks in advance, guys!

Regards,
Seb
Title: Re: Copy listbox items to clipboard
Post by: japheth on September 28, 2006, 05:22:32 AM

Hi,

>  invoke SendDlgItemMessage,hwnd,IDC_CONSOLE,LB_GETTEXT,ecx,addr item

this invoke will most likely destroy ECX register. You should push/pop it.

>  mov byte ptr [glock+eax+1],0dh ; I'm sticking with szCopy until I got it working.
> mov byte ptr [glock+eax+2],0ah  ; Will this work?

No, you need to do it this way:

mov ecx, [glock]
mov byte ptr [ecx+eax+1],0dh

>   invoke szCopy,addr item,glock ; I'd rather perform my own string copying here,

From what I see in your source you should better use some sort of strcat.

> invoke GlobalLock,gbuf
> mov byte ptr [eax+1],0h
> invoke GlobalUnlock,glock

this deletes most of the text you have just copied into the block. Btw, GlobalUnlock also expects the "memory handle", not the locked pointer as parameter, but for win32 this usually is irrelevant.






Title: Re: Copy listbox items to clipboard
Post by: hutch-- on September 28, 2006, 07:43:10 AM
Seb,

One thing you can do to simplify your code is use GlobalAlloc() with the GMEM_FIXED flag instead of the GMEM_DDESHARE AND GMEM_MOVEABLE unless you are specifically using it for DDE. When used this way it simply returns a pointer to the allocated memory and when you are finished with it you use GlobalFree() on that pointer.

This type of interface code is not seriously speed critical so it is probably a good idea in software engineering terms to use an external string copy procedure as it keeps you code easier to read. They are simple enough to write so there is nothing to stop you from rolling your own as a seperate procedure.
Title: Re: Copy listbox items to clipboard
Post by: japheth on September 28, 2006, 10:36:16 AM

Hi Hutch,

> simplify your code is use GlobalAlloc() with the GMEM_FIXED flag instead of the GMEM_DDESHARE AND GMEM_MOVEABLE

although I tend to agree with you, the MSDN docs for SetClipboardData() are telling:

-----------------------------------------------------------------------------------------------------------
If the hMem parameter identifies a memory object, the object must have been allocated using the GlobalAlloc function with the GMEM_MOVEABLE flag.
-----------------------------------------------------------------------------------------------------------

This might be simply wrong or irrelevant, but it should be noted that "officially" the GMEM_MOVEABLE flag should be set.

Title: Re: Copy listbox items to clipboard
Post by: Seb on September 28, 2006, 11:49:42 AM
Hi guys, and thanks for your answers.

I changed the code upon your suggestions, but it still fails with the same symptom (copies one character or bogus data). What I'm trying to do is, let's say there are four items in the listbox:

Error 1 occured.
Successful.
Whatever failed.
Something happened.

I want the clipboard data to be lined up just like that (that's why I add CRLF after, obviously :wink).

Here's the new code (now using .while/.endw for even greater simplicity):


invoke SendDlgItemMessage,hwnd,IDC_CONSOLE,LB_GETCOUNT,0,0
test eax,eax
jz failed
mov cnt,eax
imul eax,sizeof item ; Get total number of bytes to allocate.
mov ecx,eax
invoke GlobalAlloc,GMEM_MOVEABLE,ecx ; I also tried GMEM_FIXED.
mov gbuf,eax
xor ecx,ecx

.while ecx<cnt
push ecx
invoke SendDlgItemMessage,hwnd,IDC_CONSOLE,LB_GETTEXT,ecx,addr item
pop ecx
inc ecx
.continue .if !eax ; Move to next if failed.
mov itemlen,eax ; Store len of item.
invoke GlobalLock,gbuf
mov glock,eax
invoke szCatStr,addr item,glock ; Copy item.
push ecx
mov eax,[glock]
mov ecx,itemlen
mov byte ptr [eax+ecx+1],0dh
mov byte ptr [eax+ecx+2],0ah
pop ecx
invoke GlobalUnlock,gbuf
.endw
invoke OpenClipboard,NULL
test eax,eax
jz clip_failed
invoke EmptyClipboard
invoke GlobalLock,gbuf
mov glock,eax
invoke SetClipboardData,CF_TEXT,glock
invoke CloseClipboard
invoke GlobalUnlock,gbuf
invoke GlobalFree,gbuf


Any idea what it could be?

Thanks!

Regards,
Seb
Title: Re: Copy listbox items to clipboard
Post by: Tedd on September 28, 2006, 12:00:07 PM
Well, for a start, ecx is still getting corrupted -- any calls to an external (API) function will almost definitely destroy its value. You seem to be pushing and popping at inappropriate times. In this case (you have a lot of accesses to it, while calling functions in between) I'd actually just use ebx in place of ecx (it won't get messed up by other functions; but you should also take responsibility for preserving its value.)

push ebx
xor ebx,ebx
.WHILE (ebx<cnt)
    invoke SendDlgItemMessage,hwnd,IDC_CONSOLE,LB_GETTEXT,ebx,addr item
    inc ebx
    .CONTINUE .IF !eax ; Move to next if failed.
    mov itemlen,eax ; Store len of item.
    invoke GlobalLock,gbuf
    mov glock,eax
    invoke szCatStr,addr item,glock ; Copy item.
    mov eax,[glock]
    mov ecx,itemlen                        ;*** this isn't the 'same' ecx that was the counter
    mov byte ptr [eax+ecx+1],0dh
    mov byte ptr [eax+ecx+2],0ah
    invoke GlobalUnlock,gbuf
.ENDW
pop ebx

Title: Re: Copy listbox items to clipboard
Post by: Seb on September 28, 2006, 12:22:45 PM
Hi Tedd, thanks for your answer.

I'm sorry to say it didn't work to ditch ECX and stick with EBX - the sympton is, how funny or weird it may sound, the same. I've also tried locking/unlocking the buffer once (before and after the loop, instead of each time I want to access it - will it have any impact on performance?), but with no luck.

This is what I get when I try to copy the contents (the first listbox item is "testing"): (Þ

I've got the exact same application function to work in C++ before (and VB.NET of course, but that's hardly the same as C++ or ASM), so it really should work in ASM, too. :'(

Thanks again, guys.

Regards,
Seb
Title: Re: Copy listbox items to clipboard
Post by: japheth on September 28, 2006, 01:15:29 PM

strcat (and I suppose szCatStr as well) require the destination string to be properly terminated by a 00.

also have a look at the order of the parameters for this functions, IIRC the destination should be first.
Title: Re: Copy listbox items to clipboard
Post by: Seb on September 28, 2006, 02:00:21 PM
Hi japheth, thanks for the tip.

I've tried ensuring the destination string is null-terminated, by calling RtlZeroMemory (I have no idea if this'll do the job though), and by setting item[0] to 0, but it still copies one character or bogus data. :'(

Thanks.

Regards,
Seb
Title: Re: Copy listbox items to clipboard
Post by: japheth on September 28, 2006, 04:05:23 PM
> I've tried ensuring the destination string is null-terminated, by calling RtlZeroMemory

this might be a bit too much.

1. place a 00 after your first GlobalLock at the very beginning of the block to ensure strcat is not scanning through random contents. Example:

    invoke GlobalLock, gBuf
    mov byte ptr [eax],0

2. Ensure that after your CR/LF adding there is also a '00' just behind this pair:
     
    mov byte ptr [eax+ecx+0],0dh
    mov byte ptr [eax+ecx+1],0ah
    mov byte ptr [eax+ecx+2],00h

Title: Re: Copy listbox items to clipboard
Post by: Seb on September 28, 2006, 04:24:51 PM
Hi japheth,

I followed your suggestions and we finally progressed a little! :bg It copies the last listbox item along with the CRLF bytes if you try it once (attempting to copy multiple times don't give any data at all). Note that "item" is a local variable allocating 50 bytes:


LOCAL item[50]:BYTE


Could that affect the copying process (maybe it should be a global variable?)

Thanks again!

Regards,
Seb
Title: Re: Copy listbox items to clipboard
Post by: japheth on September 28, 2006, 08:50:07 PM

the best is if you post your current source again. Thus we can (more or less) quickly see if there is another bug somewhere.
Title: Re: Copy listbox items to clipboard
Post by: Seb on September 28, 2006, 09:41:25 PM
Hi japheth,

sure, the source is attached.


invoke GlobalAlloc,GMEM_MOVEABLE AND GMEM_DDESHARE,ecx ; I also tried GMEM_FIXED.
mov gbuf,eax
push ebx
xor ebx,ebx
.while (ebx<cnt)
invoke SendDlgItemMessage,hwnd,IDC_CONSOLE,LB_GETTEXT,ebx,addr item
inc ebx
.continue .if !eax ; Move to next if failed.
mov itemlen,eax ; Store len of item.
invoke GlobalLock,gbuf
mov byte ptr [eax],0
mov glock,eax
invoke szCatStr,glock,addr item ; Copy item.
mov eax,[glock]
mov ecx,itemlen
mov byte ptr [eax+ecx+0],0dh
mov byte ptr [eax+ecx+1],0ah
mov byte ptr [eax+ecx+2],00h
invoke GlobalUnlock,gbuf
.endw
pop ebx


Thanks.

Regards,
Seb
Title: Re: Copy listbox items to clipboard
Post by: mnemonic on September 28, 2006, 11:31:10 PM
Seb,

the only Thing i see is - again - a snippet but no full source.
It is nearly to impossible (and unlikely as well) to find the error in the code you postet.

Zip your project and attach it to some post. That way everybody can have an easy look at it.
Refering to code by line numbers makes communication easier, too.

Regards
Title: Re: Copy listbox items to clipboard
Post by: PBrennick on September 28, 2006, 11:38:44 PM
Seb,
One of the problems with just posting a snippet is we are forced to deal with what or where you feel the problem is.  Since you cannot fix the problem, this view is problematic.  You need to post the full source so others can give you the best help.

We really want to help, so help us help you.
Paul
Title: Re: Copy listbox items to clipboard
Post by: japheth on September 29, 2006, 07:15:56 AM

this is an *unoptimised* version how it could be done:


invoke GlobalAlloc,GMEM_MOVEABLE AND GMEM_DDESHARE,ecx ; I also tried GMEM_FIXED.
mov gbuf,eax
invoke GlobalLock,gbuf
mov byte ptr [eax],0   ;<--- this must be done outside of loop
mov glock,eax
push ebx
xor ebx,ebx
.while (ebx<cnt)
invoke SendDlgItemMessage,hwnd,IDC_CONSOLE,LB_GETTEXT,ebx,addr item
inc ebx
.continue .if !eax ; Move to next if failed.
;mov itemlen,eax ; Store len of item.
invoke szCatStr,glock,addr item ; Copy item.
invoke lstrlen, glock   ;<--- added
mov ecx,[glock]
mov byte ptr [eax+ecx+0],0dh
mov byte ptr [eax+ecx+1],0ah
mov byte ptr [eax+ecx+2],00h
.endw
invoke GlobalUnlock,gbuf
pop ebx

Title: Re: Copy listbox items to clipboard
Post by: Seb on September 29, 2006, 09:30:07 AM
Hi japheth,

thanks a lot, your latest code snippet solved the problems! :U And thanks to the rest, too! There is just one minor bug though; it will not copy at all or properly (bogus data bug...) if one attempts to copy several times in a row, but I'll look into that later. The important thing is that I got a functional base to work on.

Thanks again!

Regards,
Seb
Title: Re: Copy listbox items to clipboard
Post by: sinsi on September 29, 2006, 09:50:28 AM
G'day Seb,

Found this in the PSDK
Quote
If an application calls OpenClipboard with hwnd set to NULL, EmptyClipboard sets the clipboard owner to NULL; this causes SetClipboardData to fail.

So it seems you can OpenClipboard with a NULL handle but not use SetClipboardData...
Title: Re: Copy listbox items to clipboard
Post by: Seb on September 29, 2006, 10:43:30 AM
Hi sinsi, thanks for your answer.

I tried passing the hWnd as parameter to OpenClipboard, but the result was the same as before (succeeds to copy once, fails the other times). The odd thing is that, passing NULL to OpenClipboard worked just fine (also to copy to clipboard multiple times) in C++.

Regards,
Seb
Title: Re: Copy listbox items to clipboard
Post by: sinsi on September 29, 2006, 11:22:08 AM
Maybe this has a bearing?
Quote
After SetClipboardData is called, the system owns the object identified by the hMem parameter. The application can read the data, but must not free the handle or leave it locked until the CloseClipboard function is called.

As a couple of people have said, post your source in a .zip and let us see the other bit.
Title: Re: Copy listbox items to clipboard
Post by: Seb on September 30, 2006, 04:56:34 PM
Hi gents,

OK, I've wrapped up and zipped an example which contains a part of the code (I decided not to post my entire project because of some sensitive parts).

Thanks!

Regards,
Seb

[attachment deleted by admin]
Title: Re: Copy listbox items to clipboard
Post by: sinsi on October 01, 2006, 01:37:57 AM
Well, seems to work just fine on my machine (XP Home SP2).

testing
testing2
testing3
testing4
Title: Re: Copy listbox items to clipboard
Post by: Seb on October 01, 2006, 12:54:24 PM
Hi sinsi,

OK, I see. Did you also try copying the listbox contents multiple times in a row?

Regards,
Seb
Title: Re: Copy listbox items to clipboard
Post by: sinsi on October 02, 2006, 06:57:26 AM
Oops. The 2nd time it pastes nothing, 3rd time garbage. 2 demerits for me...

Well, on the 2nd copy, windbg gives two errors:
QuoteInvalid Address specified to RtlGetUserInfoHeap
and if you continue,
QuoteInvalid Address specified to RtlFreeHeap
And on any subsequent copies no error is returned but the memory contains garbage.

In my quick'n'dirty test program, I got that error from putting the test string in .const
Title: Re: Copy listbox items to clipboard
Post by: sinsi on October 02, 2006, 07:23:42 AM
OK. When you pass SetClipboardData a memory handle, you must not lock/unlock/free the handle.
Apparently the system owns the handle from then on (and presumably frees it when you call CloseClipboard).

Getting rid of the GlobalFree in your code makes it work, but doesn't seem to change mem usage in Task Manager.
Title: Re: Copy listbox items to clipboard
Post by: Seb on October 02, 2006, 09:40:30 AM
Hi sinsi,

thanks a lot for your help! :U That did indeed solve the problem! I could've never figured that out myself, especially not since I knew practically the same code worked fine in C++.

Thanks again!

Regards,
Seb
Title: Re: Copy listbox items to clipboard
Post by: sinsi on October 02, 2006, 09:48:37 AM
My pleasure, Seb. As an added bonus, I now know how to use the clipboard  :bg