Jun 30

VB2010和C#4.0后期绑定的实现比较

昨天EP研究了一下C#4.0和VB2010的后期绑定实现,弄了两个简单的测试程序,反编译出来代码如下:

   1: <STAThread> _

   2: Public Shared Sub Main()

   3:     Dim o As Object = New ExpandoObject

   4:     NewLateBinding.LateCall(o, Nothing, "Test", New Object(0  - 1) {}, Nothing, Nothing, Nothing, True)

   5:     NewLateBinding.LateSet(o, Nothing, "NewMember", New Object() { 1 }, Nothing, Nothing)

   6: End Sub

   1: private static void Main(string[] args)

   2: {

   3:     object o = new ExpandoObject();

   4:     if (<Main>o__SiteContainer0.<>p__Site1 == null)

   5:     {

   6:         <Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Test", null, typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));

   7:     }

   8:     <Main>o__SiteContainer0.<>p__Site1.Target.Invoke(<Main>o__SiteContainer0.<>p__Site1, o);

   9:     if (<Main>o__SiteContainer0.<>p__Site2 == null)

  10:     {

  11:         <Main>o__SiteContainer0.<>p__Site2 = CallSite<Func<CallSite, object, int, object>>.Create(Binder.SetMember(CSharpBinderFlags.None, "NewMember", typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.Constant | CSharpArgumentInfoFlags.UseCompileTimeType, null) }));

  12:     }

  13:     <Main>o__SiteContainer0.<>p__Site2.Target.Invoke(<Main>o__SiteContainer0.<>p__Site2, o, 1);

  14: }

C#会给调用生成一个CallSite缓存起来,然后后续调用全部是通过这个CallSite。VB 则是全部通过 NewLateBinding 来调用,而且内部每次调用都会新建一个CallSite。

开始还以为M$脑子进水了,VB的后期绑定这样弄性能得差到一个什么程度。后来跟进CallSite的代码,发现其实CallSite里面也是有缓存机制的,内部的Delegate不会重复建立,这样的话虽然VB的性能会比C#稍差,应该也不到不能接受的地步。

这么做应该是为了兼容旧的VB6后期绑定,从代码也可以看出来,如果对象没有实现IDynamicMetaObjectProvider,就会用旧版的后期绑定方式。

这样保持兼容性感觉很不方便,VB.NET相比VB6的一大改进是Option Strict,之前写代码的时候一直开着,能减少很多编码错误,其中包括误用后期绑定。而如果要使用新的ExpendoObject和其它动态功能的话,又不能启用这个检查。

另外VB不像C#新增了dynamic关键字,要使用后期绑定得把变量声明为Object类型,使代码更容易出错。

结论:在后期绑定这方面,VB本来是前辈,不过现在反而被C#赶上了。。。该说是VB的又一个悲剧么。。。

(.NET内部的反编译代码就不贴了,各位有兴趣可以用Reflector研究一下)

——————————————–

7月11日更新:

早上突然发现上次的推理有个大漏洞,VB的NewLateBinding只会对IDynamicMetaObjectProvider使用CallSite,一般对象还是会使用老版的反射调用。这样一来多次调用的性能就远远比不上C#了。。。

Mar 31

VFRHelper 1.2.4[2010.3.31更新][连接修复]

3.31:这次更新是@lititude的feature request,意见感谢!

 TFMOVR

      
VFRHelper - MKV章节制作工具(不要问我为什么叫这个名字。。。
更新日志:
1.2.4
(*)FFMS更新到2.13
(*)点击视频进度条可直接跳转到指定位置
(+)TFMOverrideEditor可使用AVS脚本作为视频
(*)其它改动略
1.2.3
(*)修复部分MKV章节文件无法加载的问题
(*)Bug修复
1.2.2
(*)FFMpegSource更新到2.0 beta 4
1.2.1
(*)Bug修复及易用性改动
1.2.0
(+)新增插件框架,章节编辑器改为插件形式
(+)新增插件:TFM OVR文件编辑器
(*)一些小改动
1.1.1
(+)新增功能:按时间跳转
1.1.0
(*)视频代码重写,速度提高
(+)FFMpegSource更新到2.0 beta 3,现在打开非avs文件不需要AviSynth了
(+)使用FFMpegSource打开文件时,在标题栏显示索引进度
(+)新增支持读取VFR MP4的Timecode
(*)一些小改动及bug修复
1.0.2
(+)新增1个快捷键
(+)FFMpegSource更新到1.19
(-)使用按钮能够正常打开非AVI文件了
1.0.1
(+)新增2个快捷键
(+)快捷键现在可以自定义
(*)退出程序时会询问是否保留临时文件(如果有的话)
1.0.0
初始版本
功能:
*可视化制作MKV章节文件
*支持打开TXT及XML格式的章节
*支持VFR(只支持V2的Timecode,如果是V1的话请预先转换好
*查看V2 Timecode各帧的时间(附带功能
*编辑TFM的OVR文件
支持的视频格式:
AVI
AVS
MKV
MP4
FLV
MKV、MP4及FLV需要FFMS2.dll支持。
快捷键说明:
方向键左/右					跳转至上一个/下一个关键帧
Shift+方向键左/右			跳转至上一帧/下一帧(注意:跳转的时候会忽略空帧)
方向键上/下					上一个/下一个章节
空格								设置当前选中章节的时间
F12									解码速度测试(可以无视
快捷键可以自定义,使用记事本之类的工具编辑keymappings.xml即可。按键名称可查看KeyNames.htm获得。
一些注意事项:
*章节文件的格式无法被改变(即只能保存为打开时候的格式)
*新建章节只支持TXT格式

 

下载:

完整包(内附源代码)

系统需求:

.Net Framework 2.0

Windows(废话

AviSynth(可选

源代码编译需求VS2008

Mar 24

gcc编译基本要点

弄了一上午终于开始make gcc了。。。于是记录一下要点。。。

0、在/etc/profile加上以下设置可减少很多麻烦事:

export CFLAGS='-I/usr/local/include'

export CPPFLAGS='-I/usr/local/include'

export LDFLAGS='-L/usr/local/lib'

1、必须库及编译顺序:gmp –> mpfr –> polylib –> ppl –> cloog-ppl

2、必须库configure的时候全部用–enable-shared –disable-static

特例:

ppl:需要加上LDFLAGS=’-no-undefined -L/usr/local/lib’,否则编译出来的还是静态库,后面编译cloog-ppl的时候会出现一大堆undefined reference。这个错误折磨了我很久。。。

cloog-ppl:需加上–with-ppl=/usr/local/,这个是编译gcc的要求

3、必须在gcc源代码目录树的外面新建一个文件夹,并在里面configure及make(请允许我吐槽。。。这是什么鬼规定。。。

3-26更新:

(前面的写完之后又折腾了一整天,编译gcc果然非常ep。。。

4、mingw目录必须在分区根部(如D:\MingW),不然会出现很奇怪的问题。。。

5、gcc在configure的时候要显式指定1里面4个库的位置,否则stage2会出错

6、cloog-ppl也要加上ppl的ldflags,不加的话编译出来的还是静态库

7、编译libgomp的时候有可能会出现ar.exe的错误,把其它目录的libtool复制到libgomp里面覆盖掉继续make就可以。这个操作可能要重复5次以上。。。

大概就是这样了。。。另外感谢squallatf同学的大力协助,很多问题都是他解答的,让我少走很多弯路。。。

Mar 12

Entity Framework CompiledQuery某bug及workaround

最近的项目里面用到了EF,某个功能需要做大量查询,性能很糟糕。

于是尝试把查询改成CompiledQuery,结果发现使用后性能反而下降了。祭出Profiler,结果发现:

image

有1/3的查询都调用了System.Data.Metadata.Edm.ObjectItemCollection.LoadAllReferencedAssemblies这个方法,时间基本全部耗在上面了。

看名字猜测,这方法似乎是用来加载查询引用的程序集的,但是理论上程序集只需要加载一次而已,感觉是EF内部的缓存机制出了问题。

Reflector反编译之,发现以下代码段:

   1: ' System.Data.Objects.CompiledQuery

   2: Private Function Invoke(Of TArg0 As ObjectContext, TArg1, TArg2, TArg3, TResult)(ByVal arg0 As TArg0, ByVal arg1 As TArg1, ByVal arg2 As TArg2, ByVal arg3 As TArg3) As TResult

   3:     EntityUtil.CheckArgumentNull(Of TArg0)(arg0, "arg0")

   4:     arg0.MetadataWorkspace.LoadAssemblyForType(GetType(TResult), Assembly.GetCallingAssembly)

   5:     Return Me.ExecuteQuery(Of TResult)(arg0, New Object() { arg1, arg2, arg3 })

   6: End Function

   7:  

   8: ' System.Data.Metadata.Edm.MetadataWorkspace

   9: Friend Sub LoadAssemblyForType(ByVal type As Type, ByVal callingAssembly As Assembly)

  10:     Dim items As ItemCollection

  11:     If Me.TryGetItemCollection(DataSpace.OSpace, items) Then

  12:         Dim items2 As ObjectItemCollection = DirectCast(items, ObjectItemCollection)

  13:         If (Not items2.LoadAssemblyForType(type) AndAlso (Not callingAssembly Is Nothing)) Then

  14:             items2.LoadAllReferencedAssemblies(callingAssembly)

  15:         End If

  16:     End If

  17: End Sub

  18:  

  19: ' System.Data.Metadata.Edm.ObjectItemCollection

  20: Friend Function LoadAssemblyForType(ByVal type As Type) As Boolean

  21:     Dim flag As Boolean = ObjectItemCollection.LoadAssemblyFromCache(Me, type.Assembly, False)

  22:     If type.IsGenericType Then

  23:         Dim type2 As Type

  24:         For Each type2 In type.GetGenericArguments

  25:             flag = (flag Or Me.LoadAssemblyForType(type2))

  26:         Next

  27:     End If

  28:     Return flag

  29: End Function

  30:  

  31:  

  32:  

  33: Private Shared Function LoadAssemblyFromCache(ByVal objectItemCollection As ObjectItemCollection, ByVal [assembly] As Assembly, ByVal loadReferencedAssemblies As Boolean) As Boolean

  34:     Dim flag As Boolean

  35:     If ObjectItemCollection.ShouldFilterAssembly([assembly].FullName) Then

  36:         Return False

  37:     End If

  38:     

  39:     ' skipped

  40: End Function

  41:  

  42: Friend Shared Function ShouldFilterAssembly(ByVal fullName As String) As Boolean

  43:     Return fullName.EndsWith("PublicKeyToken=b77a5c561934e089", StringComparison.OrdinalIgnoreCase)

  44: End Function

 

b77a5c561934e089是M$程序集的PublicKeyToken,看到这里基本就明白了。CompiledQuery执行的时候会加载调用方程序集引用的所有程序集,以TResult及其泛型参数作为键来缓存(具体的流程没有深入研究,可能不太准确)。在取缓存的时候如果键类型是M$程序集里面的话就会直接跳过。

但是取缓存的代码有个bug,如果TResult和它的泛型参数全部是M$程序集里面的类型的话,LoadAssemblyForType就会返回False,导致每次查询都会调用LoadAllReferencedAssemblies,大幅拖慢速度。

项目里面的符合条件的查询有2个,刚好是1/3,分别是返回IQueryable(Of Integer)和IQueryable(Of Decimal)。知道原因之后解决方法就很简单了。新建一个容器类如PrimitiveHolder(Of T),然后把CompiledQuery里面的TResult改成容器类就可以了,如IQueryable(Of PrimitiveHolder(Of Integer))和IQueryable(Of PrimitiveHolder(Of Decimal))。

(话说这几天弄项目一直在捣鼓EF,发现问题真的很多。。。果然M$的东西第一个版本都是这样么。。。希望.Net 4.0的EF会有改进。。。

Mar 04

小技巧:在VB中使用Module模拟C#的静态类

VB中建立助手类一般有两种方法,一是使用Module,另一个是用Class然后把成员全部声明为Shared。

不过这两种方法都有缺点,第一种方法由于VB的特性会污染命名空间;第二个需要显式声明Shared,如果以后要把代码转换到实例方法会很麻烦。(还有个问题是我经常忘记加,在调用成员失败的时候才想起来。。。)

最近发现了一个很简单的方法,能把两者的优点结合起来,范例:

   1:  

   2: Namespace Utils

   3:     <HideModuleName()> _

   4:     Module Utils

   5:         Public Sub XXX()

   6:             ' ...

   7:         End Sub

   8:     End Module

   9: End Module

  10:  

  11: ' 使用方法时Import ExcelUtils的上层命名空间即可

  12: Utils.XXX()

HideModuleName属性使VS的Intellisense在显示Utils命名空间的时候不显示类名,这样这个命名空间里面就只有Utils类的成员了,间接实现了静态类的效果。

Feb 07

Silverlight3小技巧:从Binding获取值

BlendSDK里面的交互模式很好用,不过交互的类全部从DependencyObject继承,没办法绑定很不方便。于是就弄了这个类出来:

   1: Public NotInheritable Class BindingEvaluator(Of T)

   2:     Private Shared ReadOnly DummyProperty As DependencyProperty = DependencyProperty.RegisterAttached("Dummy", GetType(T), GetType(BindingEvaluator(Of T)), New PropertyMetadata(DependencyProperty.UnsetValue))

   3:  

   4:     Private Shared Function GetDummy(ByVal element As DependencyObject) As T

   5:         ' IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!

   6:         If (element Is Nothing) Then

   7:             Throw New ArgumentNullException("element")

   8:         End If

   9:         Return CType(element.GetValue(BindingEvaluator(Of T).DummyProperty), T)

  10:     End Function

  11:     Private Shared Sub SetDummy(ByVal element As DependencyObject, ByVal value As T)

  12:         ' IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!

  13:         If (element Is Nothing) Then

  14:             Throw New ArgumentNullException("element")

  15:         End If

  16:         element.SetValue(BindingEvaluator(Of T).DummyProperty, value)

  17:     End Sub

  18:  

  19:     Public Shared Function GetValue(ByVal source As FrameworkElement, ByVal binding As Binding) As T

  20:         Dim value = GetRawValue(source, binding)

  21:         If value Is DependencyProperty.UnsetValue Then

  22:             Return Nothing

  23:         End If

  24:         Return CType(value, T)

  25:     End Function

  26:  

  27:     Public Shared Function GetRawValue(ByVal source As FrameworkElement, ByVal binding As Binding) As Object

  28:         If source Is Nothing Then

  29:             Throw New ArgumentNullException("source", "source is nothing.")

  30:         End If

  31:         If binding Is Nothing Then

  32:             Throw New ArgumentNullException("binding", "binding is nothing.")

  33:         End If

  34:         Dim newBinding As New Binding() With { _

  35:             .BindsDirectlyToSource = binding.BindsDirectlyToSource, _

  36:             .Converter = binding.Converter, _

  37:             .ConverterCulture = binding.ConverterCulture, _

  38:             .ConverterParameter = binding.ConverterParameter, _

  39:             .Mode = BindingMode.OneTime, _

  40:             .Path = binding.Path _

  41:         }

  42:         If Not String.IsNullOrEmpty(binding.ElementName) Then

  43:             newBinding.ElementName = binding.ElementName

  44:         ElseIf binding.RelativeSource IsNot Nothing Then

  45:             newBinding.RelativeSource = binding.RelativeSource

  46:         ElseIf binding.Source IsNot Nothing Then

  47:             newBinding.Source = binding.Source

  48:         Else

  49:             '    newBinding.Source = source.DataContext

  50:         End If

  51:  

  52:         source.SetBinding(DummyProperty, newBinding)

  53:         Dim value = GetDummy(source)

  54:         source.ClearValue(DummyProperty)

  55:  

  56:         Return value

  57:     End Function

  58:  

  59:     Private Sub New()

  60:  

  61:     End Sub

  62: End Class

SL4就要支持DependencyObject绑定了,期待。。。

Oct 01

WinDbg调试小记:STAThread+COM调用+Console.ReadLine=内存泄漏

这两天发现某服务器进程出现内存泄漏,遂抓dump调试之。

调试内存泄漏首先当然是!dumpheap -stat:

.....

71861128    52018      2913008 System.Threading.Thread
71862cf0    22130      2962628 System.Int32[]
0040a230      198      3034188      Free
71e7fcf8   680643     13612860 System.Threading.SafeCompressedStackHandle
05ac45cc  1412552     22600832 System.Net.IPEndPoint
05fb5690   675403     24314508 UDT.UDTSocket
71860b54   727921     34970436 System.String
718542d4  1350915     37825620 System.UInt16[]
05a7b37c  1350904     54036160 System.Net.IPAddress

.....

一看吓了一跳,Thread应该是会被垃圾回收掉的,不可能会有这么多留在堆里面。于是看看Finalize Queue:

0:000> !finalizequeue
SyncBlocks to be cleaned up: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
generation 0 has 16 finalizable objects (0f751b0c->0f751b4c)
generation 1 has 4 finalizable objects (0f751afc->0f751b0c)
generation 2 has 1719 finalizable objects (0f750020->0f751afc)
Ready for finalization 1406640 objects (0f751b4c->0fcaf60c)

.....

 

100W+个对象等待Finalize,基本可以确定Finalizer thread出问题了。切到里面看看Call stack:

0:000> ~2s
eax=00000001 ebx=ffffffff ecx=00000000 edx=00000000 esi=00000000 edi=00000000
eip=77b191d5 esp=049eea00 ebp=049eea6c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
ntdll!ZwWaitForSingleObject+0x15:
77b191d5 c20c00          ret     0Ch
0:002> k
ChildEBP RetAddr  
049ee9fc 77611270 ntdll!ZwWaitForSingleObject+0x15
049eea6c 776111d8 kernel32!WaitForSingleObjectEx+0xbe
049eea80 770bed85 kernel32!WaitForSingleObject+0x12
049eeaa4 771b9427 ole32!GetToSTA+0xad
049eead0 771b9b4d ole32!CRpcChannelBuffer::SwitchAptAndDispatchCall+0x132
049eebb0 770d37b4 ole32!CRpcChannelBuffer::SendReceive2+0xef
049eec28 770be868 ole32!CAptRpcChnl::SendReceive+0xaf
049eec7c 75dd1074 ole32!CCtxComChnl::SendReceive+0x95
049eec98 75dd102b rpcrt4!NdrProxySendReceive+0x49
049eeca4 75dd0146 rpcrt4!NdrpProxySendReceive+0xb
049ef0ac 75dd11ee rpcrt4!NdrClientCall2+0x18f
049ef0d0 75d45f22 rpcrt4!ObjectStublessClient+0x90
049ef0e0 770bf163 rpcrt4!ObjectStubless+0xf
049ef168 770a2b14 ole32!CObjectContext::InternalContextCallback+0x128
049ef1b8 72dc3fb7 ole32!CObjectContext::ContextCallback+0x87
049ef204 72dc4bcc mscorwks!CtxEntry::EnterContextOle32BugAware+0x2b
049ef324 72df9aeb mscorwks!CtxEntry::EnterContext+0x325
049ef358 72df9b90 mscorwks!RCWCleanupList::ReleaseRCWListInCorrectCtx+0xc4
049ef3a8 72d2befb mscorwks!RCWCleanupList::CleanupAllWrappers+0xdb
049ef3ec 72d2be0b mscorwks!SyncBlockCache::CleanupSyncBlocks+0xec

 

Google一番后发现,如果Finalizer thread的调用里面出现ole32!GetToSTA并且卡死的话,多半由于主线程为STA并且使用了COM,然后调用了阻塞的方法。(http://support.microsoft.com/?id=828988

这程序在初始化之后就调用了Console.ReadLine,工作全部在其它线程进行。程序并没有直接调用COM,但是使用了Remoting和MAF。推测是里面隐式调用了COM,导致出现了这问题。。。

解决方法那篇KB里面有给出,这里就不说了。话说要不是会一点WinDBG,应该就找不出问题根源了吧。。。M$的东西还真是。。。