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
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.
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.
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.
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
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
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
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.
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
> 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
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
the best is if you post your current source again. Thus we can (more or less) quickly see if there is another bug somewhere.
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
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
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
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
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
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...
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
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.
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]
Well, seems to work just fine on my machine (XP Home SP2).
testing
testing2
testing3
testing4
Hi sinsi,
OK, I see. Did you also try copying the listbox contents multiple times in a row?
Regards,
Seb
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
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.
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
My pleasure, Seb. As an added bonus, I now know how to use the clipboard :bg