Copy files in C++ Builder 3 using the "flying files"

I need to copy files from C: to A:, but I want to show the 'flying files' so the user wait to the files be copied (one at a time), I am using the following code; it works in Delphi but it doesn't work in C++ Builder 3:

#include <SHELLAPI.H>
 
String Name = "My_File.txt", Form_Dir, To_Dir;
LPSHFILEOPSTRUCT ShellOp;

From_Dir = "C:/MyDir/" + Name;
To_Dir = "A:/" + Name;
ShellOp->hwnd  = Form1->Handle;
ShellOp->wFunc = FO_COPY;
ShellOp->pFrom = From_Dir.c_str() + NULL + NULL;
ShellOp->pTo   = To_Dir.c_str() + NULL + NULL;
ShellOp->hNameMappings = NULL;
if(SHFileOperationA(ShellOp)) {...}

 What is going wrong with it?, I do not mind to use any other code anyway, it just have to work.

Any hint?
Thank you very much for your help
FMJMEEAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

hlavaCommented:
Why you don´t use standard fopen, functions for reading and writing fread,fwrite fclose, or something like this.
define the Buffer size forexample 1024.
then copy and copy until EOF(enf of file)
0
nietodCommented:
You are setting the to and from strings incorrectly.  They are set to the  temporary objects (the result of an operator +) and that object is destroyed before SHFileOperation() is called.  So you are passing garbage for these paths.
0
FMJMEEAuthor Commented:
..I want to show the 'flying files' so the user wait to the files be copied...
0
Introducing Cloud Class® training courses

Tech changes fast. You can learn faster. That’s why we’re bringing professional training courses to Experts Exchange. With a subscription, you can access all the Cloud Class® courses to expand your education, prep for certifications, and get top-notch instructions.

nietodCommented:
FMJMEE, did you understand my comment?  I don't know what exactly "String" is, but almost any string class will work impropperly under these curcumstances.  (Actually, its not the string class that is at fault, its the programmer, the string is beign missused.)

In

ShellOp->pFrom = From_Dir.c_str() + NULL + NULL;

you are creating a temporary string object and getting a pointer to the character array in the string object.  You store that pointer in pFrom, then that temporary string object is destroyed.  That frees the character array, but pFrom still points to the character arrray.  When you use c_str() the pointer returned is valid only until the next time the string is changed (or destroyed) so you must be very careful to "hold on to" the string object and "prevent changes" to it until you are done with the pointer returned from c_str().

Line

string FromString = From_Dir + '\0' + '\0';
ShellOp->pFrom = FromString.c_str();
if(SHFileOperationA(ShellOp)) {...}
//Now FromString may be changed or destroyed.
0
FMJMEEAuthor Commented:
Doesn't work either, it break at :  ShellOp->hwnd  = Form1->Handle;
I changed the previous line to:
ShellOp->hwnd  = Application->Handle;
it doesn't work; maybe needs its space or a "fillchar" in ShellOp?

Thank you very much for your time!


0
nietodCommented:
You don't have to reject an answer if you just want additional help or clarification.  The points I made were correct, no incorrect, but there may still be additional problems in the program.

>> ShellOp->hwnd  = Application->Handle;
>> it doesn't work;
In what way doesn't it work?  Does it crash?  Does it crash on that line?  Does it store an invalid handle?  Do the files not copy?  Be specific.

>> maybe needs its space or a "fillchar" in ShellOp?
What do you mean?

What do you set the fFlags member of ShellOp to?
0
karouriCommented:
can you give us the working Delphi code to compare?
0
FMJMEEAuthor Commented:
Nietod I'm sorry I just wanted to place a comment... I checked the wrong option... Sorry again!!

I am posting the delphi code to do the same thing (copy files with 'flying files').If it is easer, I can use the .pas function in my program
(I am learning)

function TForm1.CopyAFile(AFrom,ATo:String):Boolean;
var
  { make sure ShellApi is located in your uses clause }
  SHFileOpStruct : TSHFileOpStruct;
begin
  AFrom := AFrom+#0+#0;
  ATo   := ATo+#0+#0;
  Result := FALSE;

  { exit if source file does not exist }
  if not FileExists(AFrom) then
  begin
    MessageDlg(AFrom+' Does not exist', mtError,[mbOK],0);
    Exit;
  end;

  { initialize our TSHFileOpStruct }
  FillChar(SHFileOpStruct, SizeOf(TSHFileOpStruct),#0);

  { set TSHFileOpStruct handle }
  SHFileOpStruct.Wnd    := Application.Handle;

  { set TSHFileOpStruct function }
  SHFileOpStruct.wFunc  := FO_COPY;

  { set TSHFileOpStruct source file }
  SHFileOpStruct.pFrom  := pChar(AFrom);

  { set TSHFileOpStruct destination file }
  SHFileOpStruct.pTo    := pChar(ATo);

  { nill out name mappings as we will not use them }
  SHFileOpStruct.hNameMappings := nil;

  { do our file copy }
  Result := (ShFileOperation(SHFileOpStruct)=0);
end;

This is for you nietod!
0
nietodCommented:
Note the differece, the pascal code creates a new string (changes an existing string) like
AFrom := AFrom+#0+#0;
ATo   := ATo+#0+#0;

But the C++ code does not store the new string, it creates a temporary like in

ShellOp->pFrom = From_Dir.c_str() + NULL + NULL;
ShellOp->pTo   = To_Dir.c_str() + NULL + NULL;

Try

From_Dir = From_Dir.c_str() + NULL + NULL;
To_Dir = To_Dir.c_str() + NULL + NULL;

ShellOp->pFrom = From_Dir.c_str();
ShellOp->pTo   = To_Dir.c_str();

Also make sure that when you do the + NULL, the string is actulally being changed correctly.  may sure that two NUL characters appear at the end of the string.
                 
0
karouriCommented:
extracted from the microsoft API for SHFILEOPSTRUCT:
pFrom and pTo: The list of names must be double null-terminated.
To do so in C++ Builder:
try changing the lines:

ShellOp->pFrom = From_Dir.c_str() + NULL + NULL;
ShellOp->pTo   = To_Dir.c_str() + NULL + NULL;

to be:
sprintf(ShellOp->pFrom,"%s\0\0",From_Dir.c_str());
sprintf(ShellOp->pTo,"%s\0\0",To_Dir.c_str());

By the way, adding NULL to a C string does not change the contents of the string,it changes the pointer by NULL..
If it doesn't work, try changing String to AnsiString in your definition and make the above changes.
Regards
0
karouriCommented:
karouri changed their proposed answer to a comment
0
karouriCommented:
sorry,it seems that sprintf will not dp it..
instead of the above two lines allocate memory for pFrom and pTo and fill it, so the lines become:

int l=From_Dir.Length();
ShellOp->pFrom = new char[l+2];
strcpy(ShellOp->pFrom,From_Dir.c_str());
ShellOp->pFrom[l]=0;ShellOp->pFrom[l+1]=0;

l=To_Dir.Length();
ShellOp->pTo = new char[l+2];
strcpy(ShellOp->pTo,To_Dir.c_str());
ShellOp->pTo[l]=0;ShellOp->pTo[l+1]=0;

instead of the substituted lines before.
don't forget to free the memory at the end of the function i.e.
delete ShellOp->pFrom;
delete ShellOp->pTo;



0
nietodCommented:
Karouri, did you read what I had posted?
0
karouriCommented:
Nietod, may be I didn't read it well, but as I see you fell in the same error I did int the 1st proposed answer which I withdrew.
All C strings and AnsiString are added up to the first '\0' so when you do the commands like
string FromString = From_Dir + '\0' + '\0';
or
sprintf(ShellOp->pFrom,"%s\0\0",From_Dir.c_str());
no new '\0' are appended to the string.
In my proposed solution I tried to insert the two #0 manually.
Anyway we let FMJMEE try my solution and see.
I withdraw my answer to kepp the question open ..
0
karouriCommented:
karouri changed their proposed answer to a comment
0
nietodCommented:
>> All C strings and AnsiString are added up to
>> the first '\0' so when you do the commands like
>> string FromString = From_Dir + '\0' + '\0';
It depends on how the "String" class is written, which I don't have access to ("string" is the STL class.)  But I specifically said that I doubted that would work.  That is why I said

>> make sure that when you do the + NULL, the string
>> is actulally being changed correctly.  may [make] sure
>> that two NUL characters appear at the end of the string.
0
karouriCommented:
Nietod, I already read that. To be fair I withdrew the question, but why don't you test my solution yourself and see if it works. You see,the idea of EE is not about points,it is about exchanging experience ..
0
nietodCommented:
I can't test it.  I don't have "String", that is why I was forced to make assumptions.  I assumed that it probably worked, since FMJMEE knows more about the class than I did, but I did point out it might be a problem, because it would be a problem with any of the string classes I am familair with.  However it would not be a problem with a class that defines operator +(string,char) but not operator +(string,char *).  

On the other hand my solution was correct, at least in elimnating the temporary strings.  The NULs were not being appended but i was waiting to hear from FMJMEE to find out if that was a problem and that could probably be fixed with the use of operator [].

EE is also about recognition.  When one expert proposes an idea in a question, another expert should not re-propose it, at least not without giving credit to the first expert.

I had correctly described one problem in the code and suggested what had been a second problem.  FMJMEE rejected my answer, not because it was wrong, but because he had problems understanding it.  I was hoping to help him correct it so I could resubmit an answer.  It seems unfair for you to propose an answer that corrects the points I made without any recognition of the fact.   That is the EE equavelent of plaugerism.
0
FMJMEEAuthor Commented:
I am very sorry for the delay.
Nietod String is AnsiString, C++Builder takes it as AnsiString.

the following code send an "Access Violation at address 004016E7 in project P1.EXe. Write of Address 02150718" and the file is not copied.

AnsiString Name = "My_File.txt", From_Dir, To_Dir;
LPSHFILEOPSTRUCT ShellOp;

From_Dir = "C:\\" + Name;
From_Dir = From_Dir.c_str() + NULL + NULL;

To_Dir = "A:\\" + Name;
To_Dir = To_Dir.c_str() + NULL + NULL;

ShellOp->hwnd  = Form1->Handle;
ShellOp->wFunc = FO_COPY;
ShellOp->pFrom = From_Dir.c_str();
ShellOp->pTo   = To_Dir.c_str();
ShellOp->hNameMappings = NULL;
if(SHFileOperationA(ShellOp)) {...}

Here is karouri code results

AnsiString From_Dir, To_Dir;
LPSHFILEOPSTRUCT ShellOp;
int l= From_Dir.Length();
(1.-) ShellOp->pFrom = new char[l+2];
(2.-) strcpy(ShellOp->pFrom, From_Dir.c_str());
(3.-) ShellOp->pFrom[l]=0;ShellOp->pFrom[l+1]=0;

l=To_Dir.Length();
ShellOp->pTo = new char[l+2];
strcpy(ShellOp->pTo,To_Dir.c_str());
ShellOp->pTo[l]=0;ShellOp->pTo[l+1]=0;
if(SHFileOperationA(ShellOp)){...}delete ShellOp->pFrom;
delete ShellOp->pTo;

the error codes are:

(1.-)[C++Warning] Unit1.cpp(23): Possible  use of 'ShellOp' before definition.

(2.-)[C++Error] Unit1.cpp(24): Cannot convert 'const char *' to 'char *'.

(2.-)[C++Error] Unit1.cpp(24): Type mismatch in parameter '__dest' in call to 'std::strcpy(char *,const char *)'.

(3.-)[C++Error] Unit1.cpp(25): Cannot modify a const object.
....
It seems it can't be done without a lot of code, is a bad idea to use the pascal code?

Thanks
0
nietodCommented:
The line

From_Dir = From_Dir.c_str() + NULL + NULL;

is a problem.  The AnsiString + operator tries to concatenate the string with a NUL terminated string that is specified by a pointer to a character array.  So it treats the NULL as a pointer to a character array, not a NUL character.  So This causes a crash because the poihnter you are specifying is not a valid pointer--it is NULL.

Instead tack on two characters to the string then change them to NUL characters, like

From_Dir = From_Dir + "12";
From_Dir[From_Dir.Length()-1] = '\0';
From_Dir[From_Dir.Length()-2] = '\0';

Also

LPSHFILEOPSTRUCT ShellOp
     *   *   *
if(SHFileOperationA(ShellOp)

is incorrect.  The SHFileOperation() function takes a pointer to a SHFILEOPSTRUCT that specifies the parameters to be used in the operation.  So you must create a SHFILEOPSTRUCT and then pass a pointer that points to the structure to the ShFileOperation() procedure.  That is not what you are doing.  You are creating a pointer of a type that could point to one of these structures, however the pointer is not initialized, so it points to some invlaid location in memory and then you pass this invalid pointer to the procedure.  Not good.  Instead you must create a structure, initialize it, and then pass a pointer to it.  Like

SHFILEOPSTRUCT ShellOp;
ShellOp.xxxx = yyyy;
   *   *   *
if(SHFileOperationA(&ShellOp)
0
FMJMEEAuthor Commented:
Nietod, I filled all the fields of the structure and did not worked, I placed the '&' in the call if(SHFileOperationA(&ShellOp) and got an error :

[C++Error] Unit1.cpp(41): Cannot convert '_SHFILEOPSTRUCTA * *' to '_SHFILEOPSTRUCTA *'.

Also the code :

From_Dir = From_Dir + "12";
From_Dir[From_Dir.Length()-1] = '\0';
From_Dir[From_Dir.Length()-2] = '\0';

did not worked, the '\0' are not appended to the From_Dir

Thank you for your time.
0
nietodCommented:
>> '\0' are not appended to the From_Dir
They shouldn't be appended.  They should be inserted in the last and 2nd to last place.  If you look at the memory used to store the string you should see two 0's at the end of the path.

>> Cannot convert '_SHFILEOPSTRUCTA * *' to
>> '_SHFILEOPSTRUCTA *
Did you declare a SHFILEOPSTRUCT?  or did you declare a pointer to a SHFILEOPSTRUCT?  a LPSHFILEOPSTRUCT is a pointer to a SHFILEOPSTRUCT, it is not a structure.  You must declare a structure and initialize it.  Then pass a pointer to that strcture.  It sounds like you are declaring a pointer, then passing a pointer to the pointer.  That is wong.  Look at my example above.

If that doesn't help post the code.
0
FMJMEEAuthor Commented:
I wrote the wrong thing, I actually used your code :
From_Dir = From_Dir + "12";
From_Dir[From_Dir.Length()-1] = '\0';
From_Dir[From_Dir.Length()-2] = '\0';

but it just do not work, try it! you will never see any NUL's at the end of the string.
I was wrong with the structure, I delete the LP and now it is OK.

I found that the following code works:

char Buffer[20]; //File format is 8.3
sprintf(Buffer, "%s%c%c", From_Dir.c_str(), 0x00, 0x00);

I am not sure when to use New/delete, is this a case?

The working code is:

AnsiString Name = "My_File.txt";
SHFILEOPSTRUCT ShellOp;
char Buffer[18];

sprintf(Buffer, "C:\\%s\0\0", Name.c_str());
ShellOp.pFrom = Buffer;
ShellOp.pTo   = "A:\\";
ShellOp.hwnd  = Form1->Handle;
ShellOp.fFlags = 0x00;
ShellOp.wFunc = FO_COPY;
if(SHFileOperationA(&ShellOp)) {...}

Nietod, I accept your comment as an answer.
Thanks!
0
karouriCommented:
You needn't use new/delete the way you worked it out as the SHFILEOPSTRUCT and the buffer are both statically allocated.
Now that you accepted an answer, pass the points by accepting Nietod's comment, so the question will not stay open..
0
nietodCommented:
>> try it! you will never see any NUL's at the end of the string.
I can't try it.  I don't have that string class.  but the 3 string classes I do have would all handle this correctly, so I really think there is a good chance the one you are using will as well.

How are you trying to "see" the NULs?  Remember that a NUL ends a standard C string, so most ways of "looking" at a string won't show the NULs.  One way would be to look at the memory used to store the string.  You would have to do this is some sort of memory window, not in a variable window.  A veriable window will "know" that the data is a string and will no show the NUL, because that ends the string.  you have to use a window that shows "raw" memory.


>> char Buffer[20]; //File format is 8.3
>> sprintf(Buffer, "%s%c%c", From_Dir.c_str(), 0x00, 0x00);
Yes, that works, but the problem is you might not know how big to make Buffer[].  20 seems too short for most cases.  Also I really suspect it is not needed because I suspect the code I suggested really does work, and if it does work is much more efficient.

>> I am not sure when to use New/delete,
>> is this a case?
You could do, which is sort of what Karouri suggested
char *buffer = new char[From_Dir.Length()+2];
sprintf(Buffer, "%s%c%c", From_Dir.c_str(), 0x00, 0x00);
    *   *   *
delete [] buffer;

but both this approach and Karouri's suffer from an extra, unneded copy oepration.  That is innefficient.  If the operator [] code I suggested doesn't work then I guess you have no choice, but I would find that a little surprising.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
FMJMEEAuthor Commented:
Adjusted points to 100
0
FMJMEEAuthor Commented:
Nietod, you have C++ Builder3 ?, I do not created/installed the AnsiString class,it comes with the compiler.
(From CBuilder Help)
"Description
C++Builder implements the AnsiString type as a class. AnsiString is designed to function like the Delphi long  string type. Accordingly, AnsiString provides the following string handling characteristics which are required when you call VCL-type functions that use any of the Delphi long string types.
•      reference count
•      string length
•      data
•      null string terminator"

I used your code and nothing happend, i.e. no file is copied, when I call the code (press a button) it is like not pressing the button at all, and the string "My_File.txt"become "My_File.tx" because when you call
When I added the code:

char Buffer[20]; //File format is 8.3
sprintf(Buffer, "C:\\%s\0\0", Name.c_str());

it works!, and I can see now the NULL's at the end of From_Dir in the Tooltip expression evaluation (while debugging) that comes with C++ Builder.
if you insert a NULL at any place in a AnsiString, then you cut the string at that place.
Thank you very much
0
nietodCommented:
I don't use Builder, I use VC.

>> if you insert a NULL at any place in a AnsiString,
>> then you cut the string at that place.
That would be a little unusual, ost other string classes don't treat NULs specially.  But it is possible that AnsiString does.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
C++

From novice to tech pro — start learning today.