Link to home
Start Free TrialLog in
Avatar of cbitservices
cbitservicesFlag for United Kingdom of Great Britain and Northern Ireland

asked on

A DLL with an exported function which will copy a file.

I am not a C++ programmer, I work in visual basic. However, I need to create a C++ DLL which exports a function capable of copying a file; this is all it needs to do. I guess this is quite a simple thing to do, but I need to do it quickly and do not have the time to learn C++ just to achieve this. Can anyoone help? Thanks.

Regards,

Colin.
Avatar of cbitservices
cbitservices
Flag of United Kingdom of Great Britain and Northern Ireland image

ASKER

Sorry,

There is one other thing it needs to do, and that is to register the file it copies (this is also a DLL which needs to be registered), Thanks.

Regards,

Colin.
Avatar of jhance
jhance

If you're a VB programmer then WHY don't you do this in VB?
Because I have not written a DLL which exports functions before. I have tried and from all the information I can find there seems to be no way to export a function from an ActiveX DLL (and this is the only type that can be written in VB). If you know how to do it in VB and can help then I would be even more grateful! From all the investigation I have carried out it seems I need to do it in C++. Please help if you can. Thanks,

Colin
VB has the capability to do BOTH ActiveX DLLs as well as non-ActiveX DLLs.  

I'm not much of a VB programmer but I know this much...
It seems you don't know as much as you think about VB, because you it seems you definitely CANNOT create a standard DLL in VB. If you can prove me wrong then please do, but I have done much investigation on the subject, and now believe, as many others do, that it is not possible.
It seems you don't know as much as you think about VB, because you it seems you definitely CANNOT create a standard DLL in VB. If you can prove me wrong then please do, but I have done much investigation on the subject, and now believe, as many others do, that it is not possible.
Well, OK.  Then I'm wrong.  Good luck with your project.
So what about the original question? Can you help?
#include <windows.h>

_declspec(dllexport) extern "c" bool CopyFile(const char *SrcFilNam, const char *DstFilNam))
{
   return CopyFile(SrcFilNam,DstFilNam,TRUE);
}

BOOL APIENTRY DllMain(HANDLE hModule,
                      DWORD  ul_reason_for_call,
                      LPVOID lpReserved)
{
    // This switch isn't need for this, but you might need to use it sometime.
    switch( ul_reason_for_call ) {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
    }

    return TRUE;
}
Note that the __declspec(dllexport) is what you need to tell the compiler to export the function from the DLL.   The extern "c" is needed to remove name decoration, which you often need to do when the function is not called from C++.  (If this is to be used from C++, then its less important.)   the DllMain() is much like an EXE's main() or WinMain().  except it is called multiple times to notify the DLL of certain events.  In many DLL's it just returns true.  That's about it.

Opps, I made the DLL function return bool, but CopyFile returns BOOL  So the line should read

 return CopyFile(SrcFilNam,DstFilNam,TRUE) != 0;
>> There is one other thing it needs to do, and that is to register the file it copies
What do you mena?  Register it with what?  who?
Hi nietod,

Thanks for the information. I will give it a try and see if I can make it work.

When I say register, I mean I want to register the file which I copy with the process (the file to be copied is a DLL which requires registering) in the Windows registry.

I hope you understand this now and look forward to hearing from you.

Regards,

Colin.
In general, a DLL does not need to be registered.

You do need to register DLLs for some purposes, like for COM an things like that.  But that depends on what you are trying to do with the DLL.   In most cases, there is no need to register a DLL.
Hi nietod,

Thanks for the information. I will give it a try and see if I can make it work.

When I say register, I mean I want to register the file which I copy with the process (the file to be copied is a DLL which requires registering) in the Windows registry.

I hope you understand this now and look forward to hearing from you.

Regards,

Colin.
Where in the windows registry?     for what purpose?  

There is no universal place to register DLLs in the registry (there isn't a need for such a thing).   Depending on what you want the DLL to be used for, there may be a place for it to be registrered.
I understand. Perhaps you can also help me with one last bit. I need the filenames to be defined within the function (I do not need to pass this information to the function from externally, because this value will not change). I therefore need how to construct and define the filenames (I am not used to programming in C++ at all and I am aware that things like adding strings togther is completely different. I also need to identify the Windows System directory because this is to be the path for the destination file. I would be very grateful for any further assistance you can provide, and I have increased the points to 150 in view of the amount of additional help I have asked for and also in recognition of the fact that you have replied to promptly each time. Thanks.

Regards,

Colin.
The STL string class is a first-class data type.  i.e. it obeys all the expected behaviors of an object/variable.  , you can concatinate strings using the + operator.   The Windows API however, does not recognize this type, but you can convert back to a "const char *" string type with the c_str() member function.

You can get the Windows System directory using the GetSystemDirrectory() windows API function.  But you will want to convert the C-style string it returns to a string object.  Like

const int DirStrLen = GetSystemDirectory(NULL,0);  // Find length of the directory string.
const char *DirStrPtr = new char[DirStrLen + 1]; // Allocate space for the string luss the NUL at the end.

GetSystemDirectory(DirStrPtr,DirStrLen); // Get the directory string.

string FilNam = DirStrPtr;  // Copy to a real string.

delete [] DirStrPtr; // Delete theC-style string.

FilNam = FilNam + "\\Somefile.extension";  // Create filename from the path.

CallToWindowsAPI(FilNam.c_str());  // Call windows API with the string.
Thanks for the code and the explanations. I am trying to do this using Visual C++ (as it is the only version of C++ I have access to) and I am beginning to think that there are probably significant differences between that and C++?

I pasted your code into the project and it doesn't seem to recognise "string" as a data type (it produces the error message "string undeclared identifier"). It also produces the following errors among others: For the GetSystemDirectory call it returns the error: "cannot convert parameter 1 from 'const char *' to 'char *' " and for the "string FilNam = DirStrPtr;" assignment it produces the error: "'=' : cannot convert from 'const char *' to 'int'"

Can you assist further with this? Thanks,

Kindest regards,

Colin.
>> I am trying to do this using Visual C++ (as it is the only version of C++
>> I have access to) and I am beginning to think that there are probably
>> significant differences between that and C++?
Visual C++ is plain old C++.  nothing different.   Unlike say, Visual Basic and Basic.

However, Visual C++ is very very unstandard--not because of "enhancements", but because its old, from before the standard was finalized, and not a lot of effort was made to brign it up to standard.    However the non-standard stuff tends to be in the advanced features of the language, like templates.   Althoguh tere are some annoyingly more basic places where it is non-standard.  But nothing too hard to deal with.   They claim they are now working towards 100% compliance.

>>  it doesn't seem to recognise "string" as a data type
That is from the STL file <string>.  its in the "std" namespace--like all of the STL.   So its probably best for you to add a "suing namespace std" line like

#include <string>
using namespace std;

int main()
{
   string  S1 = "abc";
   string  S2 = "123";
   string  S3 = S1 + S2;
   return 0;
};


const char *DirStrPtr = new char[DirStrLen + 1];

should be

char *DirStrPtr = new char[DirStrLen + 1];

I have pasted below what I have so far. I think that, even if the syntax is wrong, as well as the data types you may be able to get the idea of what I want to do and perhaps help me to sort it out more quickly? I am still getting two "conversion" error problems associated with the lines " char * SrcFilNam=pSupportDir;"msvbvm60.dll";" and  char * DstFilNam=FilNam;"\";msvbvm60.dll"; the exact error message being: "cannot convert from 'char' to 'char *'".  I have tried changing the data types but then get the same errors only associated with the API calls instead. Thanks,

Regards,

Colin.

#include <windows.h>

_declspec(dllexport) extern "C" bool CopyAFile(long MainHandle,long DialogHandle, char pInstallDir, char pSupportDir, char pUser,char pCompany,char pSerial,char pAdditional)
{
         
 const int DirStrLen = GetSystemDirectory(NULL,0);  // Find length of the directory string.
 
 char *DirStrPtr = new char[DirStrLen + 1]; // Allocate space for the string luss the NUL at the end.

 GetSystemDirectory(DirStrPtr,DirStrLen); // Get the directory string.

 char FilNam = GetSystemDirectory(DirStrPtr,DirStrLen);  // Copy to a real string.

 delete [] DirStrPtr; // Delete theC-style string.

 FilNam = FilNam;"msvbvm60.dll";  // Create filename from the path.

 char * SrcFilNam=pSupportDir;"msvbvm60.dll";
 
 char * ScrFilNam="msvbvm60.dll";
 
 char * DstFilNam=FilNam;"\";msvbvm60.dll";
 
 return CopyFile(SrcFilNam,DstFilNam,TRUE)!= 0;
}
I have been playing with it further and have come up with the following which seems to compile and link, etc., OK. Perhaps it will work. Please take a look and if you see anything wrong please let me know. I will try it out in the meantime to see what happens!

Regards,

Colin.

_declspec(dllexport) extern "C" bool CopyAFile(long MainHandle,long DialogHandle, char pInstallDir, char pSupportDir, char pUser,char pCompany,char pSerial,char pAdditional)
{
 const int DirStrLen = GetSystemDirectory(NULL,0);  // Find length of the directory string.
 char *DirStrPtr = new char[DirStrLen + 1]; // Allocate space for the string luss the NUL at the end.

 GetSystemDirectory(DirStrPtr,DirStrLen); // Get the directory string.

 char FilNam1 [13] = "msvbvm60.dll";

 string FilName2 = pSupportDir + FilNam1;
 
 string FilNam = DirStrPtr;  // Copy to a real string.

 delete [] DirStrPtr; // Delete theC-style string.

 FilNam = FilNam + "msvbvm60.dll";  // Create filename from the path.
 
 return CopyFile(FilNam.c_str(),FilName2.c_str(),TRUE)!= 0;
}
I have tried now it with the calling program, but I get the error message that the dll does not export the function "CopyAFile" when it is called. The function definition (from a third party software company) which it needs to match is given as follows:

function ( MainHandle, DialogHandle: HWnd; const pInstallDir: PChar; pSupportDir, pUser, pCompany, pSerial, pAdditional: PChar ): Word; stdcall; export;

But I have been assuming that this definition is not quite relvant to Visual C++, because anything like this produces all sorts of errors! I therefore made the definition:

_declspec(dllexport) extern "C" bool CopyAFile(long MainHandle,long DialogHandle, char pInstallDir, char pSupportDir, char pUser,char pCompany,char pSerial,char pAdditional)

This doesn't produce compile errors, etc., but I am wondering whether is is the "same" as far as the calling program is concerned; i.e., if it is not the same, perhaps this would be the reason for the error message that the dll does not export the function?

Regards,

Colin.
You have a lot of parameters of type char.  That is suspicious.  That is a parameter/variable that can store a single character.   If you need to store a string, you either need to use the old c-style strings or C++ string objects.  (If this is to be shared among different languages, then C++ string objects are not realistic)

The old c-style strings are what most mixed-language code uses.  Its what the windows API uses.  Buts its far less convenient.  instead of passing a string--which C/C++ doesn't really support--you pass a pointer of type "pointer to character"   The idea is that this pointer will be set to point to the first character of a character array and the string is stored in this character array.  The end fo the string is marked with a character containing NUL (0).

So you don't actually pass strings in thje c-style system.  you pass pointers that are supposed to point to the string.

your code looks pretty good.  But I notice you don't add bacsklash characters ("\")  to your oaths.  That is probably wrong--unless the path string already ends in one.

Note that in C/C+++ a backslash acts as an escape character when used in a string literal.  (A string in the source code file.  So to place a single backslash character in side a string literal, you need to specify two.  Like "AB\\CD"  This creates a string of 5 characters (and a 6th NUL at the end)  There is actually only one backslash character in the string.  
that function definiton (your last post) uses types that are not "universal"   I would guess that PChar means pointer to character and thus type "char *".    But things like "pCompany" are meaningless without more information.   (note those things are not varaible names, they are types names.  We need to know what types.

What is the entire context of this thing?  What are you working with?

I'll try to be back in about 30 minutes,  Otherwise it will be about 3 hours.
Thanks again for your excellent explanation - a crash course in C++ for me! It is starting to make some sense to me now, and it certainly seems like the types in the function are likely to be pointers. I am working on trying to use a DLL from within an installation program (GPInstall) to install a file before the main files are installed - I need to do this because I have written a program in VB which will install various other files which are required by the program, but which are only installed on certain operating system (i.e., versions of Windows), however, the point in the installation at which I need to run the program is before the main required file (the VB runtime file msvbvm60.dll) is installed. Hence, if I can copy this file the main program will be able to run to carry out the rest of the "pre-installation" before the GPInstall program installs the main files! I hope all this makes sense! All the information they provide in their documentation for using DLL "Extensions" to the install program is:

"DLL Extensions

DLL extensions must contain an exported function conforming to the following function prototype:

function ( MainHandle, DialogHandle: HWnd; const pInstallDir: PChar; pSupportDir, pUser, pCompany, pSerial, pAdditional: PChar ): Word; stdcall; export;

The parameters are:

MainHandle: HWnd

This is the handle to the main installation window. i.e. the background form.

DialogHandle: HWnd

This is the handle to the install wizard form (the one where the user selects installation options). Depending when your extension is executed, this handle may be 0.

const pInstallDir: PChar

This is the installation directory the user has selected. If the extension is executed before the user has selected the installation directory, this will contain the default installation directory. Your extension can modify this value. For example, you could have an extension that is executed at the start of your installation that searches for an existing installation and sets pInstallDir to the directory containing the existing version.

pSupportDir: PChar

This is the directory used to extract the installer files during the installation. If your extension is added as baggage, this will point to the directory containing your extension.

If the extension is being run during the UnInstall, this parameter will point to the location of the uninstaller.

pUser, pCompany, pSerial: PChar

These values will contain the values entered by the user in the User dialog box. You could use these values to perform validation and enable/disable installation.

These parameters are nil when the extension is run during Uninstallation.

pAdditional: PChar

This contains any additional information you entered when adding the extension in GP-Builder. Note that paths will not be expanded. It is up to you to expand any relative paths. The data sent in the pAdditional parameter, is exactly as entered in the Extensions Dialog.

This parameter is nil when the extension is run during Uninstallation.

The function returns a Word value. If the return value is 0, the installation will terminate. If it is any other value, it will continue as normal.

Also note that, in Windows, exported function names are case sensitive."
>> a crash course in C++ for me!
This, 3 or 4 good books, and 6 months of study and you will be a C/C++ beginner.  I don't know how long it takes ot get to be expert.  I'll let you know when I find out.

I'm low on time.   The function declaration needs to be something like


_declspec(dllexport)  // to export.
extern "C"   // To export to non-C++
__stdcall  // for the stdcall at the end of the declaration they fave
short  // a word return value.  this is 16 bits.  I hope that is what they mean by "word".  
 CopyAFile(long MainHandle,long DialogHandle, char pInstallDir, char pSupportDir, char pUser,char pCompany,char pSerial,char pAdditional)

Opps.   I didn't get very far!   I'll be back.
I have now modified it to:

_declspec(dllexport) extern "C" bool CopyAFile(long MainHandle,long DialogHandle, char  *InstallDir, char *pSupportDir, char *pUser,char *pCompany, char *pSerial,char *pAdditional)
{
 const int DirStrLen = GetSystemDirectory(NULL,0);  // Find length of the directory string.
 char *DirStrPtr = new char[DirStrLen + 1]; // Allocate space for the string luss the NUL at the end.

 GetSystemDirectory(DirStrPtr,DirStrLen); // Get the directory string.

 string FilName2 = pSupportDir;

 string FilName3 = FilName2 + "\\msvbvm60.dll";
 
 string FilNam = DirStrPtr;  // Copy to a real string.

 delete [] DirStrPtr; // Delete theC-style string.

 FilNam = FilNam + "\\msvbvm60.dll";  // Create filename from the path.
 
 return CopyFile(FilNam.c_str(),FilName2.c_str(),TRUE)!= 0;
}

This seems to compile OK, but I still get the message that the "Function CopyAFile is not exported by the DLL"!

Any ideas why it doesn't even seem to be able to find it?

Regards,

Colin.
I also tried adding __stdcall  and short  as you showed, but this caused about 14 other error messages!

Regards,

Colin.
P.S. I'm going to bed now. I'll come back tomorrow! Good night.
.  i'm back for a minute.  It looks like

_declspec(dllexport)  // to export.
extern "C"   // To export to non-C++
__stdcall  // for the stdcall at the end of the declaration they fave
short  // a word return value.  this is 16 bits.  I hope that is what they mean by "word".  
CopyAFile(
HWND MainHandle,  // => Main installation window.
HWND DialogHandle, // => dialog window.
const char *pInstallDir,  // -> installation directory string.
const char *pSupportDir, // -> directory to install from.
const  char *pUser, // -> string entered in the dialog box.
const char *pCompany,
const char *pSerial,
const char *pAdditional)

you can remove all the comments and line breaks if you want. (or leave then)

but there is one very important thing missing.   The name of the procedure.   You are calling this procedure CopyAFile.  Is that the name of the procedure that the install program is looking for?  I bet not.

Is it supposed to be called "function"?  it seems to look that way to me.  But really that would be a stupid name!

Do you have a sample DLL that you could check.  you could look at the names of the functions it exports.
No, I think that "function" is suppposed to be the name of the function that you write and can be anything. Because in the GPInstall program, you can select a DLL to use and then there is a place to enter the function name. I entered the name "CopyAFile" into this after selecting my DLL. BUt it couldn't find the function it seems. It said that the function was not exported by the DLL. Unfortunately I have no other information from them, I don't have another DLL which they have produced, etc., and they provide limited support. I was assuming that it was my lack of experience and knowledge with C++ that was causing the problem, but if they haven't provided sufficient information I will contact them and ask for additional information/perhaps an example project.

I will first of all try changing the function declaration as you have suggested and give that a try. Thanks,

Regards,

Colin.
I added your function declaration and it produced an error as folows: "error C2062: type 'short' unexpected" and a warning as follows: "anachronism used : modifiers/qualifiers interspersed, qualifier ignored" (the help file seems to suggest that it is ignoring the stdcall type or else the short, but it is unclear which). BUt if you remove the _stdcall value it compiles and links OK, but still doesn't work from GPInstall! (Still says CopyAFile is not exported by the DLL).
>>  Because in the GPInstall program, you can select a DLL to use
>> and then there is a place to enter the function name
Okay.  then be sure to use the exact same spelling, including capitalization, and also be sure to use the extern "c".

Lets first make sure it is getting exported, then worry about the paramameters.   In your VC/BIN directory you should have a program called dumpbin.exe.    If you run it on your DLL it will list the exported functions.    Does it list CopyAFile?
I contacted the company who produces GP Install and they gave me the following information:

"GP-Install uses the StdCall calling convention. Also, the return type of the function (in C) should prefix the function declaration, so the function should look like:
 
short _declspec(dllexport) _stdcall CopyAFile(HWND MainHandle, HWND DialogHandle, const char *pInstallDir,  const char *pSupportDir, const  char *pUser, const char *pCompany, const char *pSerial, const char *pAdditional)"

I copied and pasted their declaration above and it compiled and linked OK, but still the GP Install reported the same error message, i.e., that the function was not exported. I will therefore now do as you suggest and try out dumpbin.exe to see if the function is being exported or not. I will get back to you then.

Regards,

Colin.
>>  Also, the return type of the function (in C) should prefix the function declaration
You have no idea how many times I made that mistake!   The _declspec(dllexport) can come first, and there are good reasons to do so (in a large library, not so much for this case)  then since it is similar I try to put the calling convention next....

>>  I will get back to you then.
I will be available somewhat for the next 2 or 3 hours.   I'll try to check in.   After that it will have to wait until tomorrow.
I have now run dumpbin on the DLL using the /exports switch and it produced the following output:

0 characteristics
3E60B0D4 time date stamp...
0.00 version
1 ordinal base
1 number of functions
1 number of names

ordinal hint name
   1      0  ?CopyAFile@@YGFPAUHWND__@@DPBD11111@Z (0001000)

Summary

2000 .data
1000 .idata
1000 .rdata
1000 .reloc
1000 .rsrc
1000 .text

It seems to be listing the function as an exported function, but should it be showing the question mark in front of the name?

Regards,

Colin.
Okay the problem right now is that the function is being exported with its C++ name decoration.  That is all the "garbage" that appears in the function name starting with the "@" and going to the end of the name (@Z)

That decoration is okay when the function iwll be called only from a C++ program (more or less) It would also be okay if you actually specified that name to your install program.  Although you probably don't want to--pluss the decoration part of the name may change as we correct the other problems.  

So instead we need to remove the decoration.  That is what the "extern "c" " is for.  You seem to have lost it now.    Try

extern "C" short _declspec(dllexport) _stdcall CopyAFile(HWND MainHandle, HWND DialogHandle, const char *pInstallDir,  const char *pSupportDir, const  char *pUser, const char *pCompany, const char *pSerial, const char *pAdditional)"

at this point the function should be exported without decoration adn the installation program should fine.  there is a good chance also that it can be called and run okay too--but lets not hope for too much too soon.
Yes, you are right, that part has gone missing! I copied and pasted the declaration from tech support and it seems that they had left it out. I have added it back in and will try it now. I'll let you know in a few minutes what happens....
OK, now its showing "_CopyAFile@32 (00001000)" with dumpbin, but it still doesn't work with GP Install. Still says the funtion is not exported!
is the initial underscore part of the filename? i.e., should I be naming the function as _CopyAFile? also, is the @32 anything to do with the name, or is this just an indication that it is 32 bit?
Okay, its been a long while since I had to do this sort of stuff--actually I never had to do this, but its been a long while since I had to help someone else do it.

Yes, the underscore AND the "@32" are both part of the exported file name.   This is still there because , although we removed the C++ decoration from the name using extern "C", there is still a 2nd form of decoration that remains.   This is the "standard call" decoration and it was added because we used the "stdcall" keyword to indicate the funciton must use the stadnard call calling convention.  the extern "C" declaration does not affect this because the standard call decoration is not part of C++, but part of the "windows way" of doing things.

There are two possibilities.  ! you can use the name "_CopyAFile@32" in your installation program  That is a reasonable approach.   However that @32 is the total size of the procedure parameters in bytes.  So if we change those parameters (if they are not correct), then that number may change.  So you might have to alter the name a bit beforeit gets working.

Otherwise, we can remove the decoration.   but that can't be done from C++.  to do that you need to create a module definition file.  This file has a .DEF extention and usually the same name as the main C++ source file.   You will need to add this file to your workspace.  In the module definiton you will need something liek

LIBRARY YourDLLsName

EXPORTS
   CopyAFile

That should work.  Theoretically, you need to have a line like

EXPORTS
   CopyAFile=_CopyAFile@32

but, that requires that you alter the module definiton file if the parameters change size.  I've never seen it documented, but for some reason its always been possible to specify the undecorated name and it will then export the undecorated name.  
OK. First I did try specifying the full name with decoration (after checking that it was still correct); that didn't work, then I found the ".def" file in the workspace and added the function name to the appropriate section. Re-compiled and linked it and then tried again; still didn't work! Perhaps there is something more fundamental that I have missed (being a complete novice at C++, it is possible!).
Right, now, I actually think we may be getting somewhere... I realised after I posted that last message that there was a semicolon and space in front of the "CopyAFile" that I added to the DEF file. I have removed that and tried again. It now does not produce the error message! However, at the point where it would run the program (GP Install) crashes (just disappears, no error message, etc). But I am now assuming that this must be a problem in the DLL code. Is there a way I can test the file in such a way that I can "trace" the execution as with VB?
try placing an

__asm int 3;

at the very start of the C++ procedure.   when this is reached, the VC debugger should come up and start debugging the code.  

I think you may need to have turned on just-in-time debugging.   In Vc this in the tools menu, options item, on the debug page tab.

Then lets see where it hangs.  It might never get there, or it might hang when the procedure ends.  Both might tell us something.   Also check the parameters in the variables window (local tab).   Make sure they are correct.  Look for ones that might be "off one" indicating we might be missing a parameter or something.
It now produces an error message of: External Exception 80000003, which it didn't produce before, but the debugger doesn't open up.
ASKER CERTIFIED SOLUTION
Avatar of nietod
nietod

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I have added message boxes and confirmed that it gets all the way to the point where it calls the API function to copy the file. It is then failing, presumably because it can't find the file. I made the assumption that the required file (because it is one which is being installed, would be in the support directory, but now I am begining to wonder!). Anyway, you have solved the major part of my problems for which I am extremely grateful. Hopefully QSC will be able to help me to resolve the remaining issues. Thanks again for all your help, determination and persistence!

Kindest regards,

Colin.
P.S. I have raised the points again to reflect the amount of help you provided! Thanks.
I have started another question to continue this one at: https://www.experts-exchange.com/questions/20534845/Previous-Question-Continued.html
>>  It is then failing, presumably because it can't find the file.
That won't cause a failure.   Did you chekc the parameters you passed to the CopyFile() function?