IStorage::CopyTo throws Invalid Pointer exception

K

KWienhold

I'm currently working on a project in C# (VS 2003 SP1, .Net 1.1) that
utilizes IStream/IStorage COM-Elements.
Up to now I have gotten everything to work to my satisfaction, but now
I have come across a problem I can't really explain:
When deleting an object from an IStorage, the space it used up will not
be freed, but rather marked as unused and overwritten the next time you
add an object to the storage.
This is obviously working as intended (according to the MSDN) and there
is no simple way to change this behavior.
Since my compound files can contain quite large amounts of data, this
seemed somewhat wasteful (especially given the fact that these files
will probably be sent through Email or uploaded to FTP sites), so I
intended to write a simple function that takes the contents of one
file, copies it to a temporary file, deletes the old and copies the old
contents back to the new file. According to the information I found
about this subject this should free the unused space.
Unfortunately all information I found on the topic was geared towards
C++, wrapping it in C# seems to make things a little more interesting
(as it does for most COM-Interfaces, in my experience).
I create a temporary IStorage using CreateDocFile pass it to the CopyTo
function, along with the parameters that closest resemble the ones I
saw in the C++ examples (0, Guid.Empty, (IntPtr)null), this results in
a COMException saying that an error occured due to an invalid pointer.
According to the MSDN this should only be caused when the pointer to
the IStorage is invalid, but the IStorage-object I pass is definately
functional (I tried adding substorages, streams etc.).
Here are some code snippets from my project that may be relevant:

The lines that actually cause the error:
---------------------------------------------------------------------------------------------------------------------------------------
string TempFile = System.IO.Path.GetTempFileName();
System.IO.File.Delete(TempFile);
APIImports.IStorage TempStorage =
APIImports.CreateStorage(TempFile,m_Read,m_Write);
Document.CopyTo(0,Guid.Empty,(IntPtr)null,TempStorage);
---------------------------------------------------------------------------------------------------------------------------------------

The CreateStorage-function:
---------------------------------------------------------------------------------------------------------------------------------------
IStorage RetVal = null;
uint STGM_Flags = 0;
int ErrCode = -1;
if (Read && !Write)
STGM_Flags = STGM_Flags | STGM_ACCESS_READ;
else if (!Read && Write)
STGM_Flags = STGM_Flags | STGM_ACCESS_WRITE;
else if (Read && Write)
STGM_Flags = STGM_Flags | STGM_ACCESS_READWRITE;
else
return null;
STGM_Flags = STGM_Flags | STGM_SHARE_EXCLUSIVE;
ErrCode = StgCreateDocfile(Path,STGM_Flags,0,out RetVal);
if (ErrCode == 0)
return RetVal;
else
{
Marshal.ThrowExceptionForHR(ErrCode);
return null;
}
---------------------------------------------------------------------------------------------------------------------------------------

My definition for IStorage::CopyTo
---------------------------------------------------------------------------------------------------------------------------------------
void CopyTo(
uint ciidExclude,
Guid rgiidExclude,
IntPtr snbExclude,
IStorage pstgDest);
---------------------------------------------------------------------------------------------------------------------------------------

Any comments would be appreciated.

Sincerely,
Kevin Wienhold
 
M

Michael Phillips, Jr.

This error(STG_E_INVALIDPOINTER) usually occurs when the interface(IStorage)
or interface method(s) are not prototyped correctly in C#.
My definition for IStorage::CopyTo
---------------------------------------------------------------------------------------------------------------------------------------
void CopyTo(
uint ciidExclude,
Guid rgiidExclude,
IntPtr snbExclude,
IStorage pstgDest);

The docs state that snbExclude is an array of OLESTR which in c# would be
string[] snbExclude.

Since this parameter may be null, you may try IntPtr[] snbExclude and
passing IntPtr.Zero as the value of the first element of an array of IntPtr.
 
K

KWienhold

Thank you for your reply.
This error(STG_E_INVALIDPOINTER) usually occurs when the interface(IStorage)
or interface method(s) are not prototyped correctly in C#.

That's what I figured, but there don't seem to be too many IStorage
Interface definitions around for C#, most examples seem to use the one
from pInvoke.net (which is where I got mine from).
The docs state that snbExclude is an array of OLESTR which in c# would be
string[] snbExclude.

Since this parameter may be null, you may try IntPtr[] snbExclude and
passing IntPtr.Zero as the value of the first element of an array of IntPtr.

When passing an IntPtr-Array the call will fail, saying that the
parameter was not of the expected type.
I tried changing it to a string-array, its first element set to null,
but this will throw the same invalid pointer exception as before.
In addition I noticed that calling SetClass on my IStorage objects will
fail with that error as well.
 
M

Michael Phillips, Jr.

Here is the layout that I use:

[ComImport,ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("0000000b-0000-0000-C000-000000000046")]
public interface IStorage
{
[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint CreateStream(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
[In] int grfMode,
[In] int reserved1,
[In] int reserved2,
[Out] out UCOMIStream ppstm);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint OpenStream(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
[In] IntPtr reserved1,
[In] int grfMode,
[In] int reserved2,
[Out] out UCOMIStream ppstm);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint CreateStorage(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
[In] int grfMode,
[In] int reserved1,
[In] int reserved2,
[Out] out IStorage ppstg);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint OpenStorage(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
[In, MarshalAs(UnmanagedType.Interface)] IStorage pstgPriority,
[In] uint grfMode,
[In] string[] snbExclude,
[In] uint reserved,
[Out] out IStorage ppstg);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint CopyTo(
[In] uint ciidExclude,
[In] Guid rgiidExclude,
[In] string[] snbExclude,
[In, MarshalAs(UnmanagedType.Interface)] IStorage pstgDest);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint MoveElementTo(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
[In, MarshalAs(UnmanagedType.Interface)] IStorage pstgDest,
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsNewName,
[In] int grfFlags);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint Commit(
[In] int grfCommitFlags);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint Revert();

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint enumElements(
[In] int reserved1,
[In] IntPtr reserved2,
[In] int reserved3,
[Out] out IenumFORMATETC ppenum);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint DestroyElement(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsName);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint RenameElement(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsOldName,
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsNewName);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint SetElementTimes(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
[In] ref FILETIME pctime,
[In] ref FILETIME patime,
[In] ref FILETIME pmtime);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint SetClass(
[In] ref Guid clsid);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint SetStateBits(
[In] uint grfStateBits,
[In] uint grfMask);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint Stat(
[Out] out STATSTG pstatstg,
[In] uint grfStatFlag);
}

I have not tried to use CopyTo or SetClass. I know that if the vtable is
not layed out correctly, you will get com errors that don't make any sense
given the fact that parameters are passed correctly.

Have you tried to pass a valid string for a snb to exclude just to test if
the function succeeds? Perhaps the error has nothing to do with the layout
of the method but with the IStorage object itself that you are passing an
Interface pointer to CopyTo.


KWienhold said:
Thank you for your reply.
This error(STG_E_INVALIDPOINTER) usually occurs when the
interface(IStorage)
or interface method(s) are not prototyped correctly in C#.

That's what I figured, but there don't seem to be too many IStorage
Interface definitions around for C#, most examples seem to use the one
from pInvoke.net (which is where I got mine from).
The docs state that snbExclude is an array of OLESTR which in c# would be
string[] snbExclude.

Since this parameter may be null, you may try IntPtr[] snbExclude and
passing IntPtr.Zero as the value of the first element of an array of
IntPtr.

When passing an IntPtr-Array the call will fail, saying that the
parameter was not of the expected type.
I tried changing it to a string-array, its first element set to null,
but this will throw the same invalid pointer exception as before.
In addition I noticed that calling SetClass on my IStorage objects will
fail with that error as well.
 
K

KWienhold

Thank you again for your reply, I really appreciate it.
After adding the [PreserveSig] directives to my definition both
functions now execute without error, unfortunately the compound file I
copy the data into is empty afterwards...
I tried changing the parameters around again (IntPtr, string, string[],
object etc.) and passed different values (valid and null ones) to no
avail.
Could you possibly try using CopyTo with your implementation? I'm
afraid something fundamental is wrong with my IStorage definition, but
I have no clue what it could be.

Sincerely,
Kevin Wienhold
Here is the layout that I use:

[ComImport,ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("0000000b-0000-0000-C000-000000000046")]
public interface IStorage
{
[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint CreateStream(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
[In] int grfMode,
[In] int reserved1,
[In] int reserved2,
[Out] out UCOMIStream ppstm);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint OpenStream(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
[In] IntPtr reserved1,
[In] int grfMode,
[In] int reserved2,
[Out] out UCOMIStream ppstm);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint CreateStorage(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
[In] int grfMode,
[In] int reserved1,
[In] int reserved2,
[Out] out IStorage ppstg);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint OpenStorage(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
[In, MarshalAs(UnmanagedType.Interface)] IStorage pstgPriority,
[In] uint grfMode,
[In] string[] snbExclude,
[In] uint reserved,
[Out] out IStorage ppstg);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint CopyTo(
[In] uint ciidExclude,
[In] Guid rgiidExclude,
[In] string[] snbExclude,
[In, MarshalAs(UnmanagedType.Interface)] IStorage pstgDest);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint MoveElementTo(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
[In, MarshalAs(UnmanagedType.Interface)] IStorage pstgDest,
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsNewName,
[In] int grfFlags);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint Commit(
[In] int grfCommitFlags);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint Revert();

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint enumElements(
[In] int reserved1,
[In] IntPtr reserved2,
[In] int reserved3,
[Out] out IenumFORMATETC ppenum);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint DestroyElement(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsName);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint RenameElement(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsOldName,
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsNewName);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint SetElementTimes(
[In, MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
[In] ref FILETIME pctime,
[In] ref FILETIME patime,
[In] ref FILETIME pmtime);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint SetClass(
[In] ref Guid clsid);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint SetStateBits(
[In] uint grfStateBits,
[In] uint grfMask);

[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
uint Stat(
[Out] out STATSTG pstatstg,
[In] uint grfStatFlag);
}

I have not tried to use CopyTo or SetClass. I know that if the vtable is
not layed out correctly, you will get com errors that don't make any sense
given the fact that parameters are passed correctly.

Have you tried to pass a valid string for a snb to exclude just to test if
the function succeeds? Perhaps the error has nothing to do with the layout
of the method but with the IStorage object itself that you are passing an
Interface pointer to CopyTo.


KWienhold said:
Thank you for your reply.
This error(STG_E_INVALIDPOINTER) usually occurs when the
interface(IStorage)
or interface method(s) are not prototyped correctly in C#.

That's what I figured, but there don't seem to be too many IStorage
Interface definitions around for C#, most examples seem to use the one
from pInvoke.net (which is where I got mine from).
The docs state that snbExclude is an array of OLESTR which in c# would be
string[] snbExclude.

Since this parameter may be null, you may try IntPtr[] snbExclude and
passing IntPtr.Zero as the value of the first element of an array of
IntPtr.

When passing an IntPtr-Array the call will fail, saying that the
parameter was not of the expected type.
I tried changing it to a string-array, its first element set to null,
but this will throw the same invalid pointer exception as before.
In addition I noticed that calling SetClass on my IStorage objects will
fail with that error as well.
 
K

KWienhold

It seems I finally got it to work, yay ;)
I replaced my IStorage definition with yours and changed the second
parameter from your CopyTo from Guid to Guid[].
If you pass null as the second parameter now everything gets copied to
the new file, as it should be. I guess Guid.Empty screwed up the
function as it was expecting an array of Interface IDs.
Again, thank you very much for your help, I don't think I would have
thought of PreserveSig to fix the error in the first place.

Sincerely,
Kevin Wienhold
 
M

Michael Phillips, Jr.

I am happy everything worked out. I thank you for alerting me that
parameter 2 was not correct. I misread the typing for that parameter.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top