PropVarient x64 Problem - ArgumentException

Aug 18, 2009 at 10:31 AM

I have a problem with SevenZipSharp in x64 platforms. I have used the  SevenZipCompressor.CompressFilesEncrypted() to compress a file with EncryptHeaders = true. 

I got an argument exception in the following line of the SevenZipCompressor.

setter.SetProperties(namesHandle.AddrOfPinnedObject(), valuesHandle.AddrOfPinnedObject(), names.Count);
            
I download and compiled the 7z.dll source code for x64 platform. After I debug through the 7z source code. I found that the propvarient size is 24 Bytes.

The ParseProperties.SetBoolProperty failed. "value.vt" have some junk values.             
            
The propvarient structure implementation in SevenZipSharp have a small bug. So that it will not work in some cases in x64 os.
            
In x64 bit OS the size of the native propvarient strutcure is 24 bytes. But In SevenZipSharp it explicitly defined as 16 bytes in the propvarient definition. So the values in the structure will not correctly marshaled.

The size of the PropVariant in .NET the Marshal.SizeOf(porpv) returns same for both the platforms. (16 for 32 & 64 bit)

PropVarient Native Definition -
-------------------------------
struct PROPVARIANT
{
    VARTYPE vt;
    PROPVAR_PAD1 wReserved1;
    PROPVAR_PAD2 wReserved2;
    PROPVAR_PAD3 wReserved3;
 
    union
    {
        CHAR cVal;
        UCHAR bVal;
        SHORT iVal;
  ULONG lng;
        CAC cac;
        CAUB caub;
        CADATE cadate;
        CAFILETIME cafiletime;
  .....
  etc
    }
}

typedef struct tagCAC
{
    ULONG cElems;
    CHAR *pElems;
}  CAC;

typedef struct tagCAFILETIME
{
    ULONG cElems;
    FILETIME *pElems;
}  CAFILETIME;

size of the tagCAC in x86 -> 8
 The first member occupies 4 bytes = 4 bytes.
 The second member of occupies 4 bytes.[ pointer is 4 byte long in x86] = 4 bytes.

size of the tagCAC in x64 -> 16
 The first member occupies 4 bytes + 4 bytes (padding) = 8 bytes. I dont know why the padding bytes or came. May be the default struct alignment in x64 is 8 bytes.
 The second member of occupies 8 bytes.[ pointer is 8 byte long in x64] = 8 bytes.

The size of the PROPVARIANT structure in x86 -> 8 bytes + 8 bytes [size of the tagCAC in x86]
The size of the PROPVARIANT structure in x64 -> 8 Bytes + 16 Bytes [size of the tagCAC in x64]


-

So in .NET We do not specfiy the size of the structure. Also We need to add a new structre inside the PropVarient.
In .net the IntPtr is platform depnedant. Its size differs for 32 bit and 64bit platforms.

So the propvarient C# structure should be defined like this.

[StructLayout(LayoutKind.Explicit)]
internal struct PropVariant
{
 [FieldOffset(0)] private ushort _Vt;

 [FieldOffset(8)] private IntPtr _Value;

 [FieldOffset(8)] private UInt32 _UInt32Value;

 [FieldOffset(8)] private Int64 _Int64Value;

 [FieldOffset(8)] private UInt64 _UInt64Value;

 [FieldOffset(8)] private FILETIME _FileTime;

 [FieldOffset(8)] private PROPARRAY _propArray;

....
}

[StructLayout(LayoutKind.Sequential)]
internal struct PROPARRAY
{
 uint cElems;
 IntPtr pElems;
}

After changing that, the size of the PropVariant in .NET the Marshal.SizeOf(porpv) returns correctly for both platforms. (16 for 32 bit. 24 for 64 bit).

Then the file compresed successfully in 64 bit platforms.

Thanks & Regards
Prakash

Coordinator
Aug 21, 2009 at 10:51 AM

Thank you very much!

I was not able to test the library on 64 bit systems much,so unfortunately sometimes such bugs come out. Your solution was gratefully merged with the code.

Really nice of you!

Regards,

Vadim.

Aug 21, 2009 at 12:02 PM

Thank you very much vadim.

you have done a wonderfull job. Before I saw SevenZipSharp I planned to write new wrapper for 7z.dll.

I learned lot of things from your SevenZipSharp source code. Thank you once again for make the SevenZipSharp as open source. :)

just for your infromation,

To invoke the CreateObject() of 7z.dll.

currently we are using,

var createObject =
(NativeMethods.CreateObjectDelegate)Marshal.GetDelegateForFunctionPointer(
           NativeMethods.GetProcAddress(_ModulePtr, "CreateObject"),
           typeof(NativeMethods.CreateObjectDelegate));

We can also use the following ,

[DllImport("7z.dll", EntryPoint = "CreateObject", CallingConvention= CallingConvention.StdCall)]
public static extern int CreateObjectFunction(
 [In] ref Guid classID,
 [In] ref Guid interfaceID,
 [MarshalAs(UnmanagedType.Interface)] out object outObject);
  

[The same signature of the CreateObjectDelegate ]

An just call the CreateObjectFunction().

The dllimport by default first look into the executable folder and then system32 folder for the 7z.dll .

If we need a custom location there is an workaround. Just call the LoadLibrary() with the custom path before any call to the Dllimported methods. It will load the 7z.dll into the process space. Then the call to CreateObjectFunction works :)

Thanks & Regards

Prakash