谈.NET中几个怪异的CustomAttribute

大家都知道AssemblyVersionAttribute是用来指定Assembly的版本号使用的,但是不知道你有没有考虑过这个问题:这个Attribute真的生成到了最后的Assembly中吗?

我们建立一个简单的C#项目试一下便可以知道。在新建的C#项目中AssemblyInfo.cs缺省有如下的内容:

using System.Reflection;

using System.Runtime.CompilerServices;

using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following

// set of attributes. Change these attribute values to modify the information

// associated with an assembly.

[assembly: AssemblyTitle("TestAssemblyVersionAttribute")]

[assembly: AssemblyDescription("")]

[assembly: AssemblyConfiguration("")]

[assembly: AssemblyCompany("MSIT")]

[assembly: AssemblyProduct("TestAssemblyVersionAttribute")]

[assembly: AssemblyCopyright("Copyright © MSIT 2008")]

[assembly: AssemblyTrademark("")]

[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible

// to COM components. If you need to access a type in this assembly from

// COM, set the ComVisible attribute to true on that type.

[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM

[assembly: Guid("a39eea23-7bc8-47c2-aca8-93136279a0ec")]

// Version information for an assembly consists of the following four values:

//

// Major Version

// Minor Version

// Build Number

// Revision

//

// You can specify all the values or you can default the Build and Revision Numbers

// by using the '*' as shown below:

// [assembly: AssemblyVersion("1.0.*")]

[assembly: AssemblyVersion("1.0.0.0")]

[assembly: AssemblyFileVersion("1.0.0.0")]

Build一下,生成的文件用ILDASM查看,大部分Attribute都可以在Manifest中找到:

// Metadata version: v2.0.50727

.assembly extern mscorlib

{

.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..

.ver 2:0:0:0

}

.assembly TestAssemblyVersionAttribute

{

.custom instance void [mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 1C 54 65 73 74 41 73 73 65 6D 62 6C 79 56 // ...TestAssemblyV

65 72 73 69 6F 6E 41 74 74 72 69 62 75 74 65 00 // ersionAttribute.

00 )

.custom instance void [mscorlib]System.Reflection.AssemblyDescriptionAttribute::.ctor(string) = ( 01 00 00 00 00 )

.custom instance void [mscorlib]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 00 00 00 )

.custom instance void [mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 04 4D 53 49 54 00 00 ) // ...MSIT..

.custom instance void [mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 1C 54 65 73 74 41 73 73 65 6D 62 6C 79 56 // ...TestAssemblyV

65 72 73 69 6F 6E 41 74 74 72 69 62 75 74 65 00 // ersionAttribute.

00 )

.custom instance void [mscorlib]System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 01 00 16 43 6F 70 79 72 69 67 68 74 20 C2 A9 20 // ...Copyright ..

4D 53 49 54 20 32 30 30 38 00 00 ) // MSIT 2008..

.custom instance void [mscorlib]System.Reflection.AssemblyTrademarkAttribute::.ctor(string) = ( 01 00 00 00 00 )

.custom instance void [mscorlib]System.Runtime.InteropServices.ComVisibleAttribute::.ctor(bool) = ( 01 00 00 00 00 )

.custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string) = ( 01 00 24 61 33 39 65 65 61 32 33 2D 37 62 63 38 // ..$a39eea23-7bc8

2D 34 37 63 32 2D 61 63 61 38 2D 39 33 31 33 36 // -47c2-aca8-93136

32 37 39 61 30 65 63 00 00 ) // 279a0ec..

.custom instance void [mscorlib]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 31 2E 30 2E 30 2E 30 00 00 ) // ...1.0.0.0..

// --- The following custom attribute is added automatically, do not uncomment -------

// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 )

.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )

.custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx

63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows.

.hash algorithm 0x00008004

.ver 1:0:0:0

}

.module TestAssemblyVersionAttribute.exe

// MVID: {383D6DF2-7A1D-41BA-944D-26979117014E}

.imagebase 0x00400000

.file alignment 0x00000200

.stackreserve 0x00100000

.subsystem 0x0003 // WINDOWS_CUI

.corflags 0x00000001 // ILONLY

// Image base: 0x00350000

只有一个例外,也就是AssemblyVersionAttribute在这个Manifest中是找不到的。而真正的指定Version的语句在这里:

.ver 1:0:0:0

我们再用另外一种方式,仿照C#编译器来动态生成一个Assembly来试一下:

AssemblyName name = new AssemblyName();

name.Name = "Result";

name.Version = new Version("1.1");

AssemblyBuilder asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.ReflectionOnly);

ConstructorInfo asmVersionCtor = typeof(AssemblyVersionAttribute).GetConstructor(new Type[] { typeof(string) });

CustomAttributeBuilder attrBuilder = new CustomAttributeBuilder(asmVersionCtor, new object[] { "2.2" });

asmBuilder.SetCustomAttribute(attrBuilder);

asmBuilder.Save("Result.DLL");

这里我们指定Assembly版本号为1.1,而AssemblyVersionAttribute则是2.2,那么最终的结果如何呢?

// Metadata version: v2.0.50727

.assembly extern mscorlib

{

.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..

.ver 2:0:0:0

}

.assembly Result

{

.custom instance void [mscorlib]System.Reflection.AssemblyVersionAttribute::.ctor(string) = ( 01 00 03 32 2E 32 00 00 ) // ...2.2..

.hash algorithm 0x00008004

.ver 1:1:0:0

}

.module RefEmit_OnDiskManifestModule

// MVID: {AF35088A-F419-4F88-A011-D29FF41C1E20}

.imagebase 0x00400000

.file alignment 0x00000200

.stackreserve 0x00100000

.subsystem 0x0003 // WINDOWS_CUI

.corflags 0x00000001 // ILONLY

// Image base: 0x00880000

可以看到,首先AssemblyVersionAttribute并没有决定Assembly的版本号,而AssemblyVersionAttribute本身却被放到了AssemblyManifest里面,反而和c#编译器的结果不一样了。

事实上,AssemblyVersionAttribute对于CLR本身并没有意义,并不能指定Assembly的版本号。AssemblyVersionAttribute本身只是对于编译器比如c#或者VB.NET等才有意义,编译器根据Attribute的内容生成对应版本号的文件,这个Attribute的内容本身并没有最后进入结果的Assembly

除此之外,还有一些Attribute本身并不是CustomAttribute,而是作为Metadata中的特殊信息存在,可以是Bit Flag,也可以是Signature的一部分。典型的例子是和COM Interop有关的Attribute,如MarshalAsAttributeDllImportAttributeStructLayoutAttribute,比如:

[DllImport("kernel32.dll", EntryPoint="Beep", PreserveSig=true, SetLastError=true)]

[return:MarshalAs(UnmanagedType.Bool)]

public static extern bool MyBeep(uint freq, uint duration);

实际上对应的则是:

.method public hidebysig static pinvokeimpl("kernel32.dll" as "Beep" lasterr winapi)

bool marshal( bool) MyBeep(uint32 freq,

uint32 duration) cil managed preservesig

{

}

其中pinvokeimpl对应DllImportAttribute”kernel32.dll” as “Beep”很明显对应DLL名字和EntryPointlasterr则是SetLastError=truemarshal(bool)对应MarshalAs(bool)preservesig对应PreserveSig=true。其中,有些信息是整个函数的Signature的一部分,有些则是BitFlagCLR在运行的时候动态解析这些信息速度会更快,比CustomAttribute要快很多,原因很简单:我们随便找一个CustomAttribute来看:

.assembly Result

{

.custom instance void [mscorlib]System.Reflection.AssemblyVersionAttribute::.ctor(string) = ( 01 00 03 32 2E 32 00 00 ) // ...2.2..

.hash algorithm 0x00008004

.ver 1:1:0:0

}

看看上面的AssemblyVersionAttribute,内容为2.2,这个2.2是怎么读出来的呢?.ctor(string)表示构造Attribute的实例的时候要调用参数是String的构造函数,而后面的那一串数字,则是根据CLI所定义的规则指定的构造函数的参数:第一个01 0016位长的标识,总是不变,03是字符串长度,后面的32 2E 32是字符串的UTF8编码,也就是2.2(这里只讲到字符串,对于其他不同类型的参数解释方式又不一样),00 0016位长的0,表示后面跟0个可选命名参数(比如上面提到的PreserveSig=true这样的形式)。换句话说,CustomAttribute是需要CLR在运行时候动态调用构造函数,并分析这一串二进制数值得出参数来创建一个新的CustomAttribute的实例,比动态解析Metadata要慢。当然了,只有CLR自己支持的特殊Attribute才有这种待遇了,一般我们写的Attribute还是要老老实实的这么构造的。这些特殊的Attribute,用GetCustomAttributes函数是无法得到的,而只能用特殊方法获得。比如PreserveSig只能用GetMethodImplementationFlags函数获得。

Adam Nathan有一篇文章也讲到了这个问题,并有一个比较详尽的列表,有兴趣的朋友也可以参考一下他的这篇文章:http://blogs.msdn.com/adam_nathan/archive/2003/04/29/56645.aspx


作者: 张羿(ATField)
Blog:
http://blog.csdn.net/atfield
http://blogs.msdn.com/yizhang
转载请注明出处

你可能感兴趣的:(C++,c,.net,vb,VB.NET)