News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Copy listbox items to clipboard

Started by Seb, September 27, 2006, 11:40:58 PM

Previous topic - Next topic

Seb

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

japheth


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.







hutch--

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.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

japheth


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.


Seb

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

Tedd

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

No snowflake in an avalanche feels responsible.

Seb

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

japheth


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.

Seb

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

japheth

> 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


Seb

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

japheth


the best is if you post your current source again. Thus we can (more or less) quickly see if there is another bug somewhere.

Seb

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

mnemonic

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
Be kind. Everyone you meet is fighting a hard battle.--Plato
-------
How To Ask Questions The Smart Way

PBrennick

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
The GeneSys Project is available from:
The Repository or My crappy website