What would be the best way of copying a folder (that contains other folders, files, and so forth)?
I thought about using findfirstfile/findnextfile.
Any suggestions, methods?
Thanks.
Deleted.
Thanks a bunch Ian_B for the clean code, i ppreciate it.
Possibly SHFileOperation is able to do this, though the documentation is not clear. There are some flags which appear to disable recursive behaviour, so presumably it is on by default.
SHFileOperation is the function called when the user drag-drops/deletes files and folders, so there is some (optional but standard) GUI as part of the function.
Cheers,
Zooba :U
SO what are you saying? This is a useless procedure because I didn't also add a little animated progress box? :dazzled:
Glad to be of help, Ghirai. It's nice that someone appreciates the art of an efficient recursive algorithm. I have removed the code so that library-boy can't steal it for one of his own do-everything code packages.
SHFileOperation seems to work, this copied the entire hla directory OK.
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
include \masm32\include\masm32rt.inc
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
.data
shop SHFILEOPSTRUCT <>
from db "c:\hla",0,0
to db "d:\hla",0,0
fAbort dd 0
.code
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
start:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
mov shop.hwnd, 0
mov shop.wFunc, FO_COPY
mov shop.pFrom, offset from
mov shop.pTo, offset to
mov shop.fFlags, FOF_SILENT
mov shop.fAnyOperationsAborted, offset fAbort
invoke SHFileOperation, ADDR shop
print ustr$(eax),13,10
print ustr$(fAbort),13,10
inkey "Press any key to exit..."
exit
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start
typedef struct _SHFILEOPSTRUCT {
HWND hwnd;
UINT wFunc;
LPCTSTR pFrom;
LPCTSTR pTo;
FILEOP_FLAGS fFlags;
BOOL fAnyOperationsAborted;
LPVOID hNameMappings;
LPCTSTR lpszProgressTitle;
} SHFILEOPSTRUCT, *LPSHFILEOPSTRUCT;
I expect SHFileOperation will be much slower, but I don't have time to do a benchmark right now.
Thanks for the info.
Ian_B, i'm having a few probs with your code.
You said that:
; for SourcePathSpec:
; find the end of the path and ensure there's a final terminating "\" character
; the offset to the NULL-termination after this "\" should now be in EAX
; do similar with TargetPathSpec and get offset to final NULL-termination in ECX
I suppose you meant position relative to the offset.
Say the destination is "c:\aaa\",0
This means ecx=8 (including 0)
And the src is "d:\*",0
This means eax=4? Or 5, including the 0?
Thanks in advance for the clarification.
Regards,
Ghirai.
Quote from: Ghirai on July 28, 2006, 02:38:20 PM
; for SourcePathSpec:
; find the end of the path and ensure there's a final terminating "\" character
; the offset to the NULL-termination after this "\" should now be in EAX
; do similar with TargetPathSpec and get offset to final NULL-termination in ECX
I suppose you meant position relative to the offset.
Say the destination is "c:\aaa\",0
This means ecx=8 (including 0)
And the src is "d:\*",0
This means eax=4? Or 5, including the 0?
No. As implied by the function parameters, the offsets are at each recursion the length of the path, ie. the offset from the start of the buffer to the NULL-termination character (NOT including the * wildcard you add afterwards). This is because you want to add new path elements and filenames immediately after the last "\" char from that offset.
So for "c:\aaa\" the offset to the NULL-termination is 7, the length of the path, as the first char "c" is at offset 0 in the buffer. For "d:\" it's 3.
The simplest way to get this is to copy the path byte-by-byte from the location where you acquire it (probably a BROWSEINFO structure) to the buffer. Use a [buffer start reg + buffer offset reg] form of indexing and when you have copied the NULL-termination and exited the loop then your buffer offset reg is correct. Save for later or just use a different reg to do the other path/buffer. Obvously you can set the buffer start registers up earlier at this point in EDI/ESI and then they are already set for the proc. If you don't want to reserve buffer space in the app DATA? section, you can also allocate memory and store the handles (for LocalAlloc these are the same as the start address) all the way through in EDI/ESI without bothering to save them, just use the registers as parameters to release the memory after.
Ian_B
Ok, i have this test code:
.data
szSRC db "c:\RadASM\*",0
szDest db "c:\aaa\",0
.data?
SourcePathSpec db MAX_PATH DUP (?)
TargetPathSpec db MAX_PATH DUP (?)
.code
invoke lstrcat,addr SourcePathSpec,addr szSRC
mov esi,eax
invoke lstrcat,addr TargetPathSpec,addr szDest
mov edi,eax
mov eax,10
mov ecx,7
invoke GetAllFiles,eax,ecx
It creates the first dir in <aaa>, but then crashes at:
FoundSubFolder:
mov WORD PTR [edi+edx], "\"
Any ideas as to what i'm doing wrong?
Check my original source and notes for where and why to push/pop EDI/ESI/EBX. Set the EDI/ESI registers using LEA as I coded for you first. I don't know what the lstrcat function does but assigning the output to ESI/EDI seems likely to be the error. You should be invoking it as "lstrcat, esi, OFFSET szSRC" as you should already have the pointer in ESI at that stage. Similarly for "lstrcat, edi, OFFSET szDest".
I rest my case about the problems of using prepackaged library functions that you haven't coded yourself and therefore can't trace through to find the errors... ::)
Ian_B
Changed as suggested, crash at the same address.
And lstrcat returns pointer to SourcePathSpec (TargetPathSpec).
Thanks for your quick replies :)
You do realise that you are copying an entire subtree into an existing folder? So the folder you are copying the tree into (c:\aaa\) must already exist before you attempt to call this and create subfolders under a non-existent root folder...
I am building a version of my own to debug and see if I've made a logic error.
Yes, i am aware of that.
I created the destination folder.
A small bug - replace the offending section with this (used wrong offset after copy loop, ie. remove two ADD lines):
FoundSubFolder:
mov WORD PTR [edi+edx-1], "\" ; replace NULL-termination with path delimiter
mov DWORD PTR [esi+ecx-1], "*\" ; terminate with wildcard for new search
xor ebx, ebx ; EBX = NULL
push edx ; set params now for recursion call
push ecx
Heh, now even smaller and faster. :wink
Ian_B
Great, thanks a lot for the help, it works fine :U
Quote from: Ian_B on July 28, 2006, 12:09:44 PM
SO what are you saying? This is a useless procedure because I didn't also add a little animated progress box? :dazzled:
Nothing of the sort. I was simply suggesting an alternative, and if he is producing an interactive application then the standard Windows GUI may be preferable. Of course, it depends on his own needs.
I thought that most of us choose to write in Assembler because it is a challenge, it represents an intellectual struggle to write efficiently, artistically, smarter, smaller, faster, finding the limits and exploiting the advantages of the raw processor code... It's like playing chess instread of noughts and crosses. What you seem to want to do is reduce everything to single APIs and easy libraries, removing the skill, the fun, the learning experience and appreciation of how to do something unfamiliar and instead relying on simply remembering the correct combination of input parameters for yet another prepackaged function. You might as well tell people to code in C or VB if that's all they will end up doing, calling libraries and API functions. Why bother with all the effort of writing in Assembler then? :eek
When the day comes that Ghirai needs to do something a little more complex with his subfolders that can't be done with a nice easy API function (there is a reason I have written, so far, 5 recursive procedures for my app, because as far as I'm aware there aren't handy APIs to create and manage linked, specifically-ordered lists of folder trees for speed and flexibility) then he'll have a great example and a basis on which to build, on his own.
The world of programming according to Zooba? A pretty dull and dumb place. No thanks.
Ian_B
Don't make me go back and point out where you've used API calls in your own code.
And, as always, it depends on what the programmer is attempting to do. If your aim is to sort a list, you don't want to spend two years making a console so you can display the output.
If the challenge is to copy folders recursively, of course you're not going to settle for a simple API call. On the other hand, if copying folders is a minor but essential part which just needs to work so you can do the fun stuff, why not? Without meaning any offence to Ghirai, he's probably just taken the code you posted and used it as-is. How is that any different? Because it's your code rather than Evil Microsoft's?
Enjoy your wheel factory.
Zooba
Quote from: zooba on July 29, 2006, 08:28:27 AM
Don't make me go back and point out where you've used API calls in your own code.
Hardly the same. I never use them to do things I can do myself, only at the very lowest level where they are unavoidable like file I/O.
QuoteAnd, as always, it depends on what the programmer is attempting to do. If your aim is to sort a list, you don't want to spend two years making a console so you can display the output.
I could agree there. The irony is that I had a complex working app exactly 2 years ago in VB that did almost everything I wanted, but it was highly speed-limited because of the nature of the processing and subject to the limits of the VB library base in its stability and reliability and its clunky look-and-feel that made it appear far less than professional. It was frustration over those limits that led me to learn programming in Assembler to overcome them completely, first experimenting with adding my own Assembler DLL for the speed-critical parts. Finally I junked more than a year of work to rebuild entirely from scratch and I am much happier with the still incomplete results as they take shape, and the fine control I have over the code, and the ability to take advantage of multithreading and other low-level optimisations easily, which I needed for the effectiveness of the app to be fully realised.
Ditching a dead-end code-base was very hard, and probably from your POV massively unproductive, but it was the best thing I ever did. Perhaps I am unusual, but I need to be able to take pride in the quality of my work, and know it is the best I can create. I program in Assembler because it's an art, not because I want to churn out code which merely "works". And as an artist (in the widest sense, I used to be a concert violinist) I want to communicate my enthusiasm for my art and encourage others to enjoy exploring as I have done.
QuoteWithout meaning any offence to Ghirai, he's probably just taken the code you posted and used it as-is. How is that any different? Because it's your code rather than Evil Microsoft's?
On that simplistic level, this entire forum is a wheel factory, by sharing nuggets of experience of Assembler coding from which everyone can benefit and learn. The difference is whether you just give them a black box that does a particular job or explain and illustrate a more general approach that can be used for all the cases where there is
no neatly pre-packaged solution, especially while showing how existing methods and algorithms can be vastly improved upon in reliability and speed. Sure, I take pleasure and pride in the fact that my unusual combination of curiosity, enthusiasm and art, even without the years of Assembler experience some people here have, seems to keep offering challenges to the received wisdom... :wink But in any case, I'd remind you that Ghirai
did originally ask for a FindFirstFile/FindNextFile example...
I'd also suggest that there is a wider POV in all this which people probably don't consider. I write code from the perspective of someone who hopes their apps will be used by as many people as possible, by trying to ensure that they are useful and time-saving utilities. Better mousetraps, if you like. The difference between two op-codes and four may be trivial on one level, but the week it might cost me sweating over reducing an algorithm to its simplest and smallest form is ultimately repaid many many times over in better productivity for those millions of potential users repeatedly running the code in the apps lifetime. And there's another even more important aspect we all might want to consider nowadays. If all those millions of users have a chance to run highly optimised code instead of unnecessary and poorly optimised API calls, there's a saving not just in clock cycles but also in energy costs for those unnecessary cycles, as modern processors can slip back into energy-saving modes (eg. SpeedStep) more quickly. Consider my proof of the appalling inefficiency of, for example, the ubiquitous wsprintf function in the "QWtoASCII" Laboratory thread...
It may not be massively significant for global warming in the scheme of things compared to, say, air travel, but it's a small contributing factor given the rapidly increasing amount of computer usage per head worldwide. As Assembler writers we have a golden opportunity to reduce our code to a bare minimum for efficiency, not just speed - that doesn't mean simply reducing code size by calling highly inefficient API routines. Don't we therefore have a planetary responsibility and an
obligation to do so? I think your obsessive and short-sighted focus on the productivity of the programmer, while understandable, is looking through the wrong end of the telescope...
Ian_B
I also use VB (version 6, not .Net) and it was those same frustrations that caused me to learn assembly. I also totally support 'ditching', as I find that dropping all my code and starting fresh results in a much more efficient result, regardless of language.
The productivity aspect is very relevant, especially when getting new programmers into assembly language. If the very first thing a new assembly programmer had to do was create an integer-string conversion function and display it on the console, quite a few would likely run away. Sure, there are those who would want to do it, but the rest who have other ideas aren't going to be bothered.
I don't believe in 'global warming/cooling' or 'climate change' as a result of human actions, so I won't comment on that aspect.
As you would know, efficiency goes out the window as soon as you use the processor to access long-term storage (ie. hard drive). In this situation, a native (ie. kernel) API call is going to be much more efficient than dropping in and out of kernel mode so often. Also, if while copying files you fail to take user preferences into account (ie. folder encryption or ACLs), you just make things worse for the user. SHFileOperation is used throughout the OS and is guaranteed to succeed everywhere the OS would succeed.
Cheers,
Zooba :U