[Webinar] Streamline your web hosting managementRegister Today

x
?
Solved

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

Posted on 2000-01-18
28
Medium Priority
?
633 Views
Last Modified: 2012-05-04
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
0
Comment
Question by:FMJMEE
  • 11
  • 8
  • 8
  • +1
28 Comments
 

Expert Comment

by:hlava
ID: 2366197
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
 
LVL 22

Expert Comment

by:nietod
ID: 2368020
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
 

Author Comment

by:FMJMEE
ID: 2369460
..I want to show the 'flying files' so the user wait to the files be copied...
0
Never miss a deadline with monday.com

The revolutionary project management tool is here!   Plan visually with a single glance and make sure your projects get done.

 
LVL 22

Expert Comment

by:nietod
ID: 2370362
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
 

Author Comment

by:FMJMEE
ID: 2378390
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
 
LVL 22

Expert Comment

by:nietod
ID: 2378875
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
 
LVL 3

Expert Comment

by:karouri
ID: 2381547
can you give us the working Delphi code to compare?
0
 

Author Comment

by:FMJMEE
ID: 2383333
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
 
LVL 22

Expert Comment

by:nietod
ID: 2383760
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
 
LVL 3

Expert Comment

by:karouri
ID: 2384824
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
 
LVL 3

Expert Comment

by:karouri
ID: 2384846
karouri changed their proposed answer to a comment
0
 
LVL 3

Expert Comment

by:karouri
ID: 2384921
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
 
LVL 22

Expert Comment

by:nietod
ID: 2385005
Karouri, did you read what I had posted?
0
 
LVL 3

Expert Comment

by:karouri
ID: 2387885
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
 
LVL 3

Expert Comment

by:karouri
ID: 2387888
karouri changed their proposed answer to a comment
0
 
LVL 22

Expert Comment

by:nietod
ID: 2388400
>> 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
 
LVL 3

Expert Comment

by:karouri
ID: 2392968
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
 
LVL 22

Expert Comment

by:nietod
ID: 2393073
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
 

Author Comment

by:FMJMEE
ID: 2399101
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
 
LVL 22

Expert Comment

by:nietod
ID: 2402142
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
 

Author Comment

by:FMJMEE
ID: 2479412
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
 
LVL 22

Expert Comment

by:nietod
ID: 2479547
>> '\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
 

Author Comment

by:FMJMEE
ID: 2480817
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
 
LVL 3

Expert Comment

by:karouri
ID: 2481774
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
 
LVL 22

Accepted Solution

by:
nietod earned 400 total points
ID: 2481868
>> 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
 

Author Comment

by:FMJMEE
ID: 2482933
Adjusted points to 100
0
 

Author Comment

by:FMJMEE
ID: 2482934
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
 
LVL 22

Expert Comment

by:nietod
ID: 2483147
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

Featured Post

Receive 1:1 tech help

Solve your biggest tech problems alongside global tech experts with 1:1 help.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Unlike C#, C++ doesn't have native support for sealing classes (so they cannot be sub-classed). At the cost of a virtual base class pointer it is possible to implement a pseudo sealing mechanism The trick is to virtually inherit from a base class…
Introduction This article is the first in a series of articles about the C/C++ Visual Studio Express debugger.  It provides a quick start guide in using the debugger. Part 2 focuses on additional topics in breakpoints.  Lastly, Part 3 focuses on th…
The goal of the video will be to teach the user the difference and consequence of passing data by value vs passing data by reference in C++. An example of passing data by value as well as an example of passing data by reference will be be given. Bot…
The viewer will learn how to user default arguments when defining functions. This method of defining functions will be contrasted with the non-default-argument of defining functions.

590 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question