李建忠


C#/.NET, C++/CLI 培训/咨询
随笔 - 13, 评论 - 266, 引用 - 26

导航

关于

标签

每月存档

最新留言

广告

 

 

ninputer在这里(http://blog.joycode.com/ninputer/archive/2005/01/12/42866.aspx)有一篇blog提出了一个问题“值类型的Finalize不会被调用?”

我曾经对Rotor,也就是sscli(Shared Source Common Language Infrastructure),有过一些粗略的探索——不过现在由于比较忙,慢慢也半途而废了:)

这个问题可以从sscli里得到解释——sscli和目前运行在我们机器上的CLR实现差别主要在效率和扩展层面,因此研究它有助于理解CLR的行为。所有有关底层运作的代码都在目录sscli\clr\src\vm下。结合sscli的源码,下面我来聊聊这个话题。


首先给出一个结论:这是因为CLR对值类型进行了专门的设计,让它不可能进入Freachable Queue 里面。


下面根据sscli源码来对上述结论进行解释:

1。有关CLR类型一个最关键的类就是MethodTable。它的第一个字段m_wFlags(一个DWORD)的第21位 bit用来标示这个类是否有Finalizer。

MethodTable有一个方法为HasFinalizer就做此用:

MethodTable::HasFinalizer()   
{
     return (m_wFlags & enum_flag_HasFinalizer);
}
其中enum_flag_HasFinalizer =   0x100000,

GC在判断一个类型的实例对象是否需要放到Freachable Queue中,就是采用MethodTable::HasFinalizer()方法来判断。

 

2。最关键的是EEClass::BuildMethodTable,这个方法负责建立类型的方法表,它会被ClassLoader::LoadTypeHandleFromToken调用,ClassLoader::LoadTypeHandleFromToken又被ClassLoader::LoadTypeHandle和Module::BuildClassForModule调用。

用通俗的语言来解释就是“每一个类型被load到内存中的时候,它都会建立和该类型相关的方法表”,而我们在CLR中的所有对象都有自己的类型。

 

3。下面就是看EEClass::BuildMethodTable如何设置MethodTable::m_wFlags。

EEClass::BuildMethodTable中和“值类型的Finalize”这个主题相关的动作有以下几个调用(为简便起见我没有在这里写方法的参数):

EEClass::BuildMethodTable
{
     ...
     CheckForValueType
     ...

     CheckForEnumType

     ...
     GetMethodTable()->MaybeSetHasFinalizer

     ...
}


4。来看CheckForValueType和CheckForEnumType分别做了什么。

HRESULT EEClass::CheckForValueType(bmtErrorInfo* bmtError)
{
    if(...) //查看类型元数据
            SetValueClass();
}

HRESULT EEClass::CheckForEnumType(bmtErrorInfo* bmtError)
{
   if(...) //查看类型元数据
        SetValueClass();
}

再来看SetValueClass做了什么:

inline void EEClass::SetValueClass()
{
    m_VMFlags |= VMFLAG_VALUETYPE;
}

就是设置EEClass::m_VMFlags的第24位bit来表示这个类为“值类型”。

其中VMFLAG_VALUETYPE   = 0x00800000,

 

5。最后来看MaybeSetHasFinalizer做了什么(我简化了其中很多代码,只展示和本问题相关的代码逻辑)。

void MethodTable::MaybeSetHasFinalizer()
{

    if ( !IsValueClass())
    {

        if(...)
        {
            m_wFlags |= enum_flag_HasFinalizer;
        }

    }
}

这段代码的意思是只要IsValueClass()为true,那么MethodTable::m_wFlags的第21位 bit就不会被置1。

那么MethodTable::IsValueClass()做了什么呢?

inline BOOL MethodTable::IsValueClass()
{
    return GetClass()->IsValueClass();
}

inline DWORD EEClass::IsValueClass()
{
    return (m_VMFlags & VMFLAG_VALUETYPE);
}

判断EEClass::m_VMFlags的第24位bit看看其是否为“值类型”。


至此,整个来龙去脉已经非常清晰——CLR的设计者通过MethodTable::m_wFlags的第21位bit来控制一个类型是否有Finalizer,同时通过EEClass::m_VMFlags的第24位bit来控制一个类型是否为值类型。最后在调用EEClass::BuildMethodTable的时候,判断如果一个类型为值类型,那么就让它不可能具有Finalizer语义。

 

Ninputer 在随后的回复中还提了一个问题“如果值类型用了非托管资源怎么释放呢?”。

我的回答是:不要这么做,值类型当初就是为象integer这样的轻量级类型而设计的,持有非托管资源的类型天生就是一个“重量级类型”。当然你可以使struct实现IDisposable,但是那是不完整的Dispose模式。

实际上在我的Effective .NET (in C#)一书的draft里就有这样一个条款:

# 如果使用非托管资源,请把它封装在class而不是struct里面。

 

 

打印 | 张贴于 2005-01-13 14:40:00 | Tag:C#/.NET

留言反馈

#关于“值类型的Finalize不会被调用?” 编辑
关于“值类型的Finalize不会被调用?”
2007-05-04 18:56:00 | [匿名:tanliyoung]
#hoodia 编辑
Talking with you is sort of the conversational equivalent of an out of body experience.
2007-01-22 20:42:00 | [匿名:hoodia]
#re:关于“值类型的Finalize不会被调用?” 编辑
^_~,pretty good!csharpsseeoo
2005-05-18 20:43:00 | [匿名:超声波流量计]
#re:关于“值类型的Finalize不会被调用?” 编辑
^_^,Pretty Good!
2005-04-10 20:28:00 | [匿名:滴胶机]
#re: 关于“值类型的Finalize不会被调用?” 编辑
不错。
2005-01-13 18:34:00 | [匿名:SmartGUy]
#re: 关于“值类型的Finalize不会被调用?” 编辑
To 笔端,

这本书相当慢,因为我有可能把它首先放在美国Addison-Wesley 出版,并且最后可能写成《Effective C++/CLI》。英文写作不是一蹴而就的事情,不过我在努力:)

一切只是可能,最后能否成行取决于很多因素。

To Justion Shen, Ninputer,
实际上C#的Finalize也有相当的缺陷,我会在下面一篇帖子中整理出来。
2005-01-13 18:33:00 | [匿名:李建忠]
#re: 关于“值类型的Finalize不会被调用?” 编辑
VB对Finalize管的可松呢,可以直接重写、直接调用、允许不调用父类的Finalize,或者多次调用父类的Finalize等等……

完全不像C#
2005-01-13 17:43:00 | [匿名:Ninputer]
#re: 关于“值类型的Finalize不会被调用?” 编辑
这样看来,VB.NET不禁止为值类型定义Finalize,可以算是某种程度的失误了。
2005-01-13 15:59:00 | [匿名:Justin Shen]
#re: 关于“值类型的Finalize不会被调用?” 编辑
李先生:
什么时候能一睹你的大作《Effective .NET (in C#)》
2005-01-13 15:36:00 | [匿名:笔端]
对不起,目前本随笔不允许发表新评论.

Powered by: Joycode.MVC引擎 0.5.2.0