|
I don't think I'd use it in production, but thanks for doing the research.
|
|
|
|
|
the internal keyword was invented for a good reason!
|
|
|
|
|
is not internal, is private, so no concernes
|
|
|
|
|
oh sorry, didn't notice that I always make my public API private, this teaches the end user to learn some reflection usages, always good for practice
|
|
|
|
|
The problem with this approach is that if your process is not running in Full Trust or lacks an an appropriate ReflectionPermission, attempts to reflect on or access non-public objects/methods/properties/etc. will throw an exception.
Further, since the classe(s) referenced are internal, Microsoft may chang/modify/remove them at anytime.
Just use DotNetZip. One assembly. Liberal license. Put it in the GAC if you don't want every application to carry it around. Does more, and has cleaner syntax, to boot.
|
|
|
|
|
DotNetZip: Stream based compression is much less efficient than file based compression.
|
|
|
|
|
Using Expression compiles to DynamicMethods - this should be fast as possible - maybe.
Thanks for the suggestion from kornman00 and dave.dolan
class CZipArchive : IDisposable
{
static Func<string, FileMode, FileAccess, FileShare, bool, object> openonfile;
static Func<Stream, FileMode, FileAccess, bool, object> openonstream;
static Func<object, string, object> getfile;
static Func<object, object> getfiles;
static Action<object, string> deletefile;
static Func<object, string, CompressionMethodEnum, DeflateOptionEnum, object> addfile;
object external;
CZipArchive() { }
public static CZipArchive OpenOnFile(string path, FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read, FileShare share = FileShare.Read, bool streaming = false)
{
if (openonfile == null)
{
var type = typeof(System.IO.Packaging.Package).Assembly.GetType("MS.Internal.IO.Zip.ZipArchive");
var meth = type.GetMethod("OpenOnFile", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
var paras = meth.GetParameters().Select(p => Expression.Parameter(p.ParameterType)).ToArray();
openonfile = Expression.Lambda<Func<string, FileMode, FileAccess, FileShare, bool, object>>(Expression.Call(meth, paras), paras).Compile();
}
return new CZipArchive { external = openonfile(path, mode, access, share, streaming) };
}
public static CZipArchive OpenOnStream(Stream stream, FileMode mode = FileMode.OpenOrCreate, FileAccess access = FileAccess.ReadWrite, bool streaming = false)
{
if (openonstream == null)
{
var type = typeof(System.IO.Packaging.Package).Assembly.GetType("MS.Internal.IO.Zip.ZipArchive");
var meth = type.GetMethod("OpenOnStream", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
var paras = meth.GetParameters().Select(p => Expression.Parameter(p.ParameterType)).ToArray();
openonstream = Expression.Lambda<Func<Stream, FileMode, FileAccess, bool, object>>(Expression.Call(meth, paras), paras).Compile();
}
return new CZipArchive { external = openonstream( stream, mode, access, streaming) };
}
public enum CompressionMethodEnum : ushort { Stored = 0, Deflated = 8 };
public enum DeflateOptionEnum : byte { Normal = 0, Maximum = 2, Fast = 4, SuperFast = 6 };
public ZipFileInfo AddFile(string path, CompressionMethodEnum compmeth = CompressionMethodEnum.Deflated, DeflateOptionEnum option = DeflateOptionEnum.Normal)
{
if (addfile == null)
{
var type = external.GetType(); var arco = Expression.Parameter(typeof(object));
var meth = type.GetMethod("AddFile", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
var args = meth.GetParameters().Select(p => Expression.Parameter(p.ParameterType)).ToArray();
var arg1 = Expression.Parameter(typeof(CompressionMethodEnum)); var arg2 = Expression.Parameter(typeof(DeflateOptionEnum));
addfile = Expression.Lambda<Func<object, string, CompressionMethodEnum, DeflateOptionEnum, object>>(Expression.Call(Expression.Convert(arco, type), meth, args[0], Expression.Convert(arg1, args[1].Type), Expression.Convert(arg2, args[2].Type)), arco, args[0], arg1, arg2).Compile();
}
return new ZipFileInfo { external = addfile(external, path, compmeth, option) };
}
public ZipFileInfo GetFile(string name)
{
if (getfile == null)
{
var type = external.GetType(); var arco = Expression.Parameter(typeof(object)); var arg = Expression.Parameter(typeof(string));
var meth = type.GetMethod("GetFile", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
getfile = Expression.Lambda<Func<object, string, object>>(Expression.Call(Expression.Convert(arco, type), meth, arg), arco, arg).Compile();
}
return new ZipFileInfo { external = getfile(external, name) };
}
public IEnumerable<ZipFileInfo> Files
{
get
{
if (getfiles == null)
{
var type = external.GetType(); var arco = Expression.Parameter(typeof(object));
var meth = type.GetMethod("GetFiles", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
getfiles = Expression.Lambda<Func<object, object>>(Expression.Call(Expression.Convert(arco, type), meth), arco).Compile();
}
foreach (var p in (System.Collections.IEnumerable)getfiles(external)) yield return new ZipFileInfo { external = p };
}
}
public void DeleteFile(string name)
{
if (deletefile == null)
{
var type = external.GetType(); var arco = Expression.Parameter(typeof(object)); var arg = Expression.Parameter(typeof(string));
var meth = type.GetMethod("DeleteFile", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
deletefile = Expression.Lambda<Action<object, string>>(Expression.Call(Expression.Convert(arco, type), meth, arg), arco, arg).Compile();
}
deletefile(external, name);
}
public void Dispose()
{
((IDisposable)external).Dispose();
}
public struct ZipFileInfo
{
static Func<object, FileMode, FileAccess, Stream> getstream;
static Func<object, string> get_name;
static Func<object, DateTime> get_lwt;
internal object external;
public override string ToString()
{
return Name;
}
public string Name
{
get
{
if (get_name == null)
{
var arco = Expression.Parameter(typeof(object));
get_name = Expression.Lambda<Func<object, string>>(Expression.PropertyOrField(Expression.Convert(arco, external.GetType()), "Name"), arco).Compile();
}
return get_name(external);
}
}
public DateTime LastModFileDateTime
{
get
{
if (get_lwt == null)
{
var arco = Expression.Parameter(typeof(object));
get_lwt = Expression.Lambda<Func<object, DateTime>>(Expression.PropertyOrField(Expression.Convert(arco, external.GetType()), "LastModFileDateTime"), arco).Compile();
}
return get_lwt(external);
}
}
public Stream GetStream(FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read)
{
if (getstream == null)
{
var type = external.GetType(); var arco = Expression.Parameter(typeof(object)); var arc1 = Expression.Parameter(typeof(FileMode)); var arc2 = Expression.Parameter(typeof(FileAccess));
var meth = type.GetMethod("GetStream", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
getstream = Expression.Lambda<Func<object, FileMode, FileAccess, Stream>>(Expression.Call(Expression.Convert(arco, type), meth, arc1, arc2), arco, arc1, arc2).Compile();
}
return getstream(external, mode, access);
}
}
}
|
|
|
|
|
I would recommend moving the linq-based Func initialization to the type's static constructor. The wrapper method/property code will be less complicated, and will have a better chance at getting inlined.
|
|
|
|
|
Yes, possible but then we initialized and compile at runtime always maybe never used methodes and for basic read access only three methodes are necessary: OpenOnFile, GetFile, GetStream
It is always a compromize...
However: speed test, for flat access it's 10-20 times faster then over Reflection, the request for LastWriteTime for example.
For the unpack case, 50k files for example, the difference is only 0.01%...always a compromize...
I'm not very familar with Expressions. Is there a way to write such things more compact? with templates?
|
|
|
|
|
Have you tried to the code of this class with reflector or ILSpy ?
Maybe the dependencies are not very deep.
I'll try to check that if you have not tried before.
Also why don't you use DotNetZip?
modified on Sunday, June 12, 2011 6:11 PM
|
|
|
|
|
Hi, DotNetZip, yes but why include other libraries when it is not necessary? The functionality is already preinstalled in the global assembly cach.
ILSpy,... was not necessary, 'MS.Internal.IO.Zip.ZipArchive' is free accessible, was only hidden.
|
|
|
|
|
The wrapper is a library, except it is a single .cs file instead of a dll.
I wanted to use ILSpy, to not use reflection... but it's not worth the trouble, zipping took an order of magnitude more time than a reflection call.
|
|
|
|
|
Thanks for the idea, I will check with ILSpy:
Maybe that the core is in another system dll, zipfldr.dll for example, where nobody knows the interfaces.
|
|
|
|
|
|
Good article, but you should note that using reflection has a serious performance hit. This solution should not be used if you're going to create a lot of zip files.
|
|
|
|
|
Yes, but is there any better solution in the moment? Smaller and faster?
This is really the question and was the reason to write this article... maybe that someone else has a better idea.
To improve the performance, it is possible to keep the MethodInfos as static's like:
private static MethodInfo _OpenOnFile, _OpenOnStream, _GetFile;
private static object[] _args1 = new object[1];
public ZipFileInfo GetFile(string name)
{
_args1[0] = name; return new ZipFileInfo { external = _GetFile.Invoke(external, _args1) };
}
public ZipFileInfo GetFile(string name)
{
var meth = external.GetType().GetMethod("GetFile", BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic);
return new ZipFileInfo { external = meth.Invoke(external, new object[] { name }) };
}
But I guess that it makes no big difference in relation to the zip, unzip perfomance itselfe
|
|
|
|
|
Just for fun, the fast version:
The 'new object[]' calls could also replaced with static versions for further improvments
But in the end it's always a compromise: speed - codesize and it depends on the application case
class FastZipArchive : IDisposable
{
static Assembly ass = typeof(System.IO.Packaging.Package).Assembly;
static Type ziparchive = ass.GetType("MS.Internal.IO.Zip.ZipArchive");
static MethodInfo openonfile = ziparchive.GetMethod("OpenOnFile", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
static MethodInfo openonstream = ziparchive.GetMethod("OpenOnStream", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
static MethodInfo addfile = ziparchive.GetMethod("AddFile", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
static MethodInfo deletefile = ziparchive.GetMethod("DeleteFile", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
static MethodInfo getfile = ziparchive.GetMethod("GetFile", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
static MethodInfo getfiles = ziparchive.GetMethod("GetFiles", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
static Type cme = ass.GetType("MS.Internal.IO.Zip.CompressionMethodEnum");
static Type doe = ass.GetType("MS.Internal.IO.Zip.DeflateOptionEnum");
static object cmeStored = cme.GetField("Stored").GetValue(null);
static object cmeDeflated = cme.GetField("Deflated").GetValue(null);
static object doeNormal = cme.GetField("Normal").GetValue(null);
static Type zipfileinfo = ass.GetType("MS.Internal.IO.Zip.ZipFileInfo");
static PropertyInfo name = zipfileinfo.GetProperty("Name", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
static MethodInfo getstream = zipfileinfo.GetMethod("GetStream", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
object external;
public static FastZipArchive OpenOnFile(string path, FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read, FileShare share = FileShare.Read, bool streaming = false)
{
return new FastZipArchive { external = openonfile.Invoke(null, new object[] { path, mode, access, share, streaming }) };
}
public static FastZipArchive OpenOnStream(Stream stream, FileMode mode = FileMode.OpenOrCreate, FileAccess access = FileAccess.ReadWrite, bool streaming = false)
{
return new FastZipArchive { external = openonstream.Invoke(null, new object[] { stream, mode, access, streaming }) };
}
public ZipFileInfo AddFile(string path, bool compressed)
{
return new ZipFileInfo { external = addfile.Invoke(external, new object[] { path, compressed ? cmeDeflated : cmeStored, doeNormal }) };
}
public void DeleteFile(string name)
{
deletefile.Invoke(external, new object[] { name });
}
public ZipFileInfo GetFile(string name)
{
return new ZipFileInfo { external = getfile.Invoke(external, new object[] { name }) };
}
public IEnumerable<ZipFileInfo> Files
{
get { foreach (var p in (System.Collections.IEnumerable)getfiles.Invoke(external, null)) yield return new ZipFileInfo { external = p }; }
}
public void Dispose()
{
((IDisposable)external).Dispose();
}
public struct ZipFileInfo
{
internal object external;
public string Name
{
get { return (string)name.GetValue(external, null); }
}
public Stream GetStream(FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read)
{
return (Stream)getstream.Invoke(external, new object[] { mode, access });
}
}
}
|
|
|
|
|
You may also want to look into using Linq Expressions. Creating the method calls would require some manual code (unless you created some elaborate utility class for generating them), however, you could use the following for accessing properties\fields:
using Expr = System.Linq.Expressions.Expression;
public static Func<object, R> GenerateMemberGetterForType<R>(Type type, string member_name)
{
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(member_name));
Contract.Ensures(Contract.Result<Func<object, R>>() != null);
var param = Expr.Parameter(typeof(object), "this");
var cast = Expr.Convert(param, type);
var member = Expr.PropertyOrField(cast, member_name);
var lambda = Expr.Lambda<Func<object, R>>(member, param);
return lambda.Compile();
}
example:
object obj = "Test";
var length = Util.GenerateMemberGetterForType<int>(typeof(string), "Length");
System.Console.WriteLine(length(obj));
Would output 4 to the console. You could use this for your ZipFileInfo wrapper (which your source's zip has defined as a struct/value type, where the MS assembly has it defined as a class).
|
|
|
|
|
In full trust scenario you can just emit a DynamicMethod that calls the constructor and access the methods. You only have to invoke reflection one time to map it up and then from there on out it's compiled code. LCG, all the way back to .NET 2.0. The trouble is that this requires codegen rights which dramatically reduce your chances of making an xcopy deploy work in say.. a web app. I don't know for sure if the LINQ expression method mentioned in the above posts encapsulates this methodology or not. From the few examples I've seen of linq expression trees they seem to need all the same info you'd need to do it with reflection, and I'd imagine it doesn't re-grab it on every call, but I'm not sure. If you know, reply and I'll be educated.
|
|
|
|
|
Thanks for the idea, I have already tried the LINQ expression method, it works and in fact, there is a DynamicMethod generation behind.
And it doesn't re-grab it on every call. Just for fun, I will try to make a template with DynamicMethod compilation. Maybe that we can use it for other problems too, to get access to thousands of other non-public classes in the GAC
|
|
|
|
|
Many thanks for this, was really new for me, I have tried directly and it works.
Behind seems to be a DynamicMethod generation: Output: (Managed (v4.0.30319)): Loaded 'Anonymously Hosted DynamicMethods Assembly'
I will check if it's possible to use DynamicMethod directly, maybe a small template what helps for other cases too.
Just for fun, we could use it to get access over thousands of non-public classes.
|
|
|
|
|
Thanks a lot for your suggestion, I made a new Linq Expressions based version (published in my last message 'New ZipArchive class with out reflection calls.')
speed test like:
var fileinfo = arc.GetFile("test1.xml");
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 100000; i++) { var lwt = fileinfo.LastModFileDateTime; fileinfo.GetStream().Dispose(); }
sw.Stop();
Debug.WriteLine("new CZipArchive: " + sw.ElapsedMilliseconds + " ms");
old ZipArchive: 501 ms
new CZipArchive: 44 ms
10 times faster
|
|
|
|
|
Good news, are you going to update the source in the article with the Linq Expressions version ?
|
|
|
|
|
Is already some lines before, in the message 'New ZipArchive class with out reflection calls.'
|
|
|
|
|
The overhead of using reflection to call a method to add/extract a file to/from an archive will be negligible in 99% of cases. The actual adding/extracting is so much more expensive than the reflection call that it more or less doesn't really matter, unless maybe you are zipping thousands of 100 byte files.
|
|
|
|
|