Windows 7 - Using SHFileOperation to copy files to a zip file

Asked By Priy on 05-Nov-07 12:47 PM
Hi,

Could someone tell me if it is possible to use SHFileOperation() function to
copy files to a ZIP file?

I have seen a C# program which uses Shell32.Folder CopyHere() function to do
this successfully and I am trying to port this to C++ using standard Shell
functions. I have read that CopyHere is a wrapper around SHFileOperation so I
am trying to use that.

My problem is that SHFileOperation returns success, but the source file is
not copied to the destination zip file. Instead a copy of the source file is
created in the same folder as the source file.

I have set the wFunc field of the SHFILEOPSTRUCT structure that I pass into
SHFileOperation to FO_COPY and the fFlags field has been set to
FOF_NOCONFIRMATION | FOF_SILENT | FOF_RENAMEONCOLLISION | FOF_NOCONFIRMMKDIR

Can I use SHFileOperation() to add files to a zip folder? If so, what is the
right way to do this? If not, is there another method to accomplish this? Any
information is greatly appreciated.

Thanks,
Priya




Jim Barry replied on 06-Nov-07 07:25 AM
No, SHFileOperation only deals with filesystem files, not virtual =
namespace objects.


CopyHere uses SHFileOperation only if the parameter is a string =
containing a wildcard path. Otherwise, it simulates a drag-drop =
operation. You can code this directly, but it's probably easier just to =
use the shell folder object (it is a COM object that can be used in C++ =
- it does not require C#).

--=20
Jim Barry, MVP (Windows SDK)
Priy replied on 06-Nov-07 12:53 PM
Hi Jim,

Thank you for your response.

I have not worked with Shell objects before, so could you please give me
some more information about using the Shell folder object in C++? Should I be
calling CoCreateInstance to create the object? What CLSID and IID should I
pass to CoCreateInstance?

I could not find the relevant information in the "Shell Objects for C++"
section of MSDN.

Thanks,
Priya
Jim Barry replied on 07-Nov-07 09:12 AM
The CLSID is {13709620-C279-11CE-A49E-444553540000} (CLSID_Shell). The =
interface is IShellDispatch, IID {D8F015C0-C278-11CE-A49E-444553540000}. =
The definitions are in ShlDisp.idl/ShlDisp.h.=20


It's in the "Shell Objects for Scripting and Microsoft Visual Basic" =
section:

http://msdn2.microsoft.com/en-us/library/bb774094.aspx

--=20
Jim Barry, MVP (Windows SDK)
Priy replied on 07-Nov-07 12:43 PM
Hi Jim,

Thanks again for the information. I wrote a sample program to test this out,
but could not get it to work properly. The program compiles and runs fine,
but the source file is not copied to the destination compressed folder. When
I step through the code I see that CopyHere() returns S_OK even though the
file is not copied to the ZIP file. I tried replacing the compressed folder
with a regular folder and CopyHere() worked as expected. Here is my sample
code:

int _tmain(int argc, _TCHAR* argv[])
{
DWORD            strlen = 0;
char                   szFrom[] = "C:\\Test.log",
szTo[] = "C:\\Sample.zip";
HRESULT          hResult;
IShellDispatch *pISD;
Folder                *pToFolder = NULL;
VARIANT          vDir, vFile, vOpt;
BSTR                  strptr1, strptr2;

CoInitialize(NULL);

hResult = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_INPROC_SERVER,
IID_IShellDispatch, (void **)&pISD);

if  (SUCCEEDED(hResult))
{
strlen = MultiByteToWideChar(CP_ACP, 0, szTo, -1, 0, 0);
strptr1 = SysAllocStringLen(0, strlen);
MultiByteToWideChar(CP_ACP, 0, szTo, -1, strptr1, strlen);

VariantInit(&vDir);
vDir.vt = VT_BSTR;
vDir.bstrVal = strptr1;
hResult = pISD->NameSpace(vDir, &pToFolder);

if  (SUCCEEDED(hResult))
{
strlen = MultiByteToWideChar(CP_ACP, 0, szFrom, -1, 0, 0);
strptr2 = SysAllocStringLen(0, strlen);
MultiByteToWideChar(CP_ACP, 0, szFrom, -1, strptr2, strlen);

VariantInit(&vFile);
vFile.vt = VT_BSTR;
vFile.bstrVal = strptr2;

VariantInit(&vOpt);
vOpt.vt = VT_I4;
vOpt.lVal = 4;          // Do not display a progress dialog box

hResult = pToFolder->CopyHere(vFile, vOpt);

SysFreeString(strptr2);
pToFolder->Release();
}

SysFreeString(strptr1);
pISD->Release();
}

CoUninitialize();

return 0;
}

Is this the proper way to do it or am I missing some steps? If I wanted to
pass in FolderItem or FolderItems to CopyHere() function what is the variable
type to use?

Please excuse the very basic questions. Normally I look up things like this
online, but there seems to surprisingly little information about this on MSDN
or the other developer sites like CodeProject. Or maybe I am not looking in
the right places. Thanks again for taking the time to answer my questions.

Regards,
Priya
Jim Barry replied on 07-Nov-07 03:22 PM
I think the problem here is that the copy operation is scheduled on a =
background thread, and your process exits before the thread has had a =
chance to run. I guess you need to look out for a new thread being =
created and wait for it to finish before exiting the process.

--=20
Jim Barry, MVP (Windows SDK)
Priy replied on 08-Nov-07 05:20 AM
Hi Jim,

Yes, that was the problem. I have got it working now. Thanks for all your
help.

Regards,
Priya
Christian Kaiser replied on 08-Nov-07 09:32 AM
And what if the thread list differences cannot be reliably detected
(for example when having a DLL that creates threads independent of the
applicaction)? Is there no reliable way to ask the shell object? It
would mean it's pretty useless as you can never be sure all files are
packed into the ZIP file.



Periodically checking by opeining the file READWRITE, DENY_ALL does
not work - possibly the file is not opened by the thread after some
CopyHere() calls.

Do you have any idea?

Christian

--
Jim Barry, MVP (Windows SDK)
Jim Barry replied on 09-Nov-07 09:37 AM
The correct way is to use SHSetInstanceExplorer, but not all context =
menu handlers conform to this protocol. There is a good discussion of =
the issue here:

http://www.eluent.com/runmenu.htm

--=20
Jim Barry, MVP (Windows SDK)
CamperRo replied on 05-Jul-08 04:40 PM
Can you refer me to some sample code showing how to look out for a new
thread being created and waiting for it to finish? I need to detect when
CopyHere completes. Thanks.
Jim Barry replied on 07-Jul-08 06:40 AM
No sample code, but the idea is basically this:

1) Enumerate current threads in the process using =
Thread32First/Thread32Next

2) Start the operation

3) Enumerate the threads again

4) Wait for any new threads using WaitForMultipleObjects

Of course, if the operation creates any new threads that don't exit, =
then you have a problem.

--=20
Jim Barry, Microsoft MVP