Por: Roberto Alexis Farah

 

Olá pessoal!

Eis a resposta do desafio #12: http://blogs.technet.com/latam/archive/2006/11/03/desafio-da-semana-12.aspx

 

 

PROBLEMA 1

 

Observe os diferentes cenários com um string válida, vazia e nula.

 

Execução com uma string válida.

 

string str = "Abcd";

                            

DoSomething(str);

    

 

Eis a thread no lado gerenciado (.NET), especificamente no CLR (Common Language Runtime):

 

ESP       EIP    

0012f438 00cc011e Demo.Program.DoSomething(System.String)

    PARAMETERS:

        str = 0x01271be0                      ß Objeto string.

    LOCALS:

        0x0012f43c = 0x00000000

        0x0012f438 = 0x00000000

        0x0012f450 = 0x00000000

        0x0012f44c = 0x00000000

        0x0012f448 = 0x00000000

        0x0012f444 = 0x00000000

 

ESP/REG  Object   Name

0012f47c 00cc0096 Demo.Program.Main(System.String[])

    PARAMETERS:

        args = 0x01271bd0

    LOCALS:

        <CLR reg> = 0x01271be0

 

ESP/REG  Object   Name

0012f69c 79e88f63 [GCFrame: 0012f69c]

 

 

Eis o detalhe do objeto acima:

 

Name: System.String

MethodTable: 790fa3e0

EEClass: 790fa340

Size: 26(0x1a) bytes

GC Generation: 0

 (C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

String: Abcd

Fields:

      MT    Field   Offset                 Type VT     Attr    Value Name

790fed1c  4000096        4         System.Int32  0 instance        5 m_arrayLength

790fed1c  4000097        8         System.Int32  0 instance        4 m_stringLength           ß É maior que 0.

790fbefc  4000098        c          System.Char  0 instance       41 m_firstChar

790fa3e0  4000099       10        System.String  0   shared   static Empty

    >> Domain:Value  0014c1b8:790d6584 <<

79124670  400009a       14        System.Char[]  0   shared   static WhitespaceChars

    >> Domain:Value  0014c1b8:01271754 <<

 

 

 

 

Excução com uma string vazia:

 

DoSomething("Def");

                            

str = "";

 

 

OS Thread Id: 0x72b8 (0)

ESP       EIP     

ESP/REG  Object   Name

eax      01271c14 System.String

ecx      01271c14 System.String

esi      01271c14 System.String

0012f478 00cc00d8 Demo.Program.DoSomething(System.String)

    PARAMETERS:

        str = 0x01271c14                             ß String vazia mas objeto string é válido. Aponta para 790fa3e0 que é o MethodTable.

    LOCALS:

        <no data>

        <no data>

        <no data>

        <no data>

        <no data>

        <no data>

 

ESP/REG  Object   Name

eax      01271c14 System.String

ecx      01271c14 System.String

esi      01271c14 System.String

0012f47c 00cc00b4 Demo.Program.Main(System.String[])

    PARAMETERS:

        args = 0x01271bd0

    LOCALS:

        <CLR reg> = 0x01271c14

 

ESP/REG  Object   Name

eax      01271c14 System.String

ecx      01271c14 System.String

esi      01271c14 System.String

0012f47c 01271bd0 System.Object[]    (System.String[])

0012f534 01271bd0 System.Object[]    (System.String[])

0012f69c 79e88f63 [GCFrame: 0012f69c]

 

 

Eis a string vazia:

 

Name: System.String

MethodTable: 790fa3e0

EEClass: 790fa340

Size: 18(0x12) bytes

GC Generation: 0

 (C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

String:

Fields:

      MT    Field   Offset                 Type VT     Attr    Value Name

790fed1c  4000096        4         System.Int32  0 instance        1 m_arrayLength

790fed1c  4000097        8         System.Int32  0 instance        0 m_stringLength   ß Tamanho 0.

790fbefc  4000098        c          System.Char  0 instance        0 m_firstChar

790fa3e0  4000099       10        System.String  0   shared   static Empty

    >> Domain:Value  0014c1b8:790d6584 <<

79124670  400009a       14        System.Char[]  0   shared   static WhitespaceChars

    >> Domain:Value  0014c1b8:01271754 <<

 

 

 

Agora a execução com um objeto string que é null:

 

str = null;

 

DoSomething(str);

 

 

Vamos aos detalhes:

 

OS Thread Id: 0x72b8 (0)

ESP       EIP    

ESP/REG  Object   Name

0012f478 00cc00d8 Demo.Program.DoSomething(System.String)

    PARAMETERS:

        str = 0x00000000             ß É como se fosse um ponteiro em C++ apontando para NULL.

    LOCALS:

        <no data>

        <no data>

        <no data>

        <no data>

        <no data>

        <no data>

 

ESP/REG  Object   Name

0012f47c 00cc00bf Demo.Program.Main(System.String[])

    PARAMETERS:

        args = 0x01271bd0

    LOCALS:

        <CLR reg> = 0x00000000

 

ESP/REG  Object   Name

0012f47c 01271bd0 System.Object[]    (System.String[])

0012f534 01271bd0 System.Object[]    (System.String[])

0012f69c 79e88f63 [GCFrame: 0012f69c]

                            

 

Em .NET todo objeto deriva de System.Object e um objeto como uma string pode ser vazia mas não nula, ou pode ser nula. São conceitos diferentes.

Observe os objetos gerenciados na pilha:

 

OS Thread Id: 0x72b8 (0)

ESP/REG  Object   Name

0012f47c 01271bd0 System.Object[]    (System.String[])     ß Eis nosso objeto: 01271bd0

0012f534 01271bd0 System.Object[]    (System.String[])

0012f6e0 01271bd0 System.Object[]    (System.String[])

0012f708 01271bd0 System.Object[]    (System.String[])

 

 

Nosso objeto:

 

Name: System.Object[]                   

MethodTable: 79124228

EEClass: 7912479c

Size: 16(0x10) bytes

GC Generation: 0

Array: Rank 1, Number of elements 0, Type CLASS

Element Type: System.String                                    ß Tipo string… mas é apenas um objeto nulo. Não foi inicializado com uma string ou string vazia.

Fields:

None

 

 

Agora a execução vai para:

 

// Checa se string é vazia.

if(0 == str.Length)

 

 

E temos no lado de código nativo:

 

eax=00000000 ebx=0012f4ac ecx=00000000 edx=00000000 esi=00000000 edi=00000000

eip=00cc0123 esp=0012f438 ebp=0012f474 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246

Demo!Demo.Program.DoSomething(System.String)+0x4b:

00cc0123 8b7808          mov     edi,dword ptr [eax+8] ds:0023:00000008=????????     ß Interrogação é memória não inicializada, afinal, o endereço 0x8 é inválido.

 

0x8 nada mais é que eax que é 0x0 + offset que é 0x8. Porque o offset? Porque o offset é onde se está armazenado o tamanho da string em um objeto do tipo System.String.

 

 

Eis a pilha no lado nativo:

 

ChildEBP RetAddr 

0012f474 00cc00bf Demo!Demo.Program.DoSomething(System.String)+0x4b

0012f490 79e88f63 Demo!Demo.Program.Main(System.String[])+0x4f

0012f490 79e88ee4 mscorwks!CallDescrWorker+0x33

0012f510 79e88e31 mscorwks!CallDescrWorkerWithHandler+0xa3

0012f650 79e88d19 mscorwks!MethodDesc::CallDescr+0x19c

0012f668 79e88cf6 mscorwks!MethodDesc::CallTargetWorker+0x20

0012f67c 79f084b0 mscorwks!MethodDescCallSite::Call_RetArgSlot+0x18

0012f7e0 79f082a9 mscorwks!ClassLoader::RunMain+0x220

0012fa48 79f0817e mscorwks!Assembly::ExecuteMainMethod+0xa6

0012ff18 79f07dc7 mscorwks!SystemDomain::ExecuteMainMethod+0x398

0012ff68 79f05f61 mscorwks!ExecuteEXE+0x59

0012ffb0 79011b5f mscorwks!_CorExeMain+0x11b

0012ffc0 7c816fd7 mscoree!_CorExeMain+0x2c

0012fff0 00000000 KERNEL32!BaseProcessStart+0x23

 

E a exceção:

 

ExceptionAddress: 00cc0123 (Demo!Demo.Program.DoSomething(System.String)+0x0000004b)

   ExceptionCode: c0000005 (Access violation)

  ExceptionFlags: 00000000

NumberParameters: 2

   Parameter[0]: 00000000

   Parameter[1]: 00000008

Attempt to read from address 00000008

 

 

O depurador pega a exceção. Nesse ponto continuei a execução para forçar uma exceção fatal. (2nd chance exception):

 

(72bc.72b8): CLR notification exception - code e0444143 (first chance)

(72bc.72b8): Access violation - code c0000005 (!!! second chance !!!)

eax=00000000 ebx=0012f4ac ecx=00000000 edx=00000000 esi=00000000 edi=00000000

eip=00cc0123 esp=0012f438 ebp=0012f474 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

Demo!Demo.Program.DoSomething(System.String)+0x4b:

00cc0123 8b7808          mov     edi,dword ptr [eax+8] ds:0023:00000008=????????

 

E no lado .NET temos:

 

System.NullReferenceException was unhandled

  Message="Object reference not set to an instance of an object."

  Source="Demo"

  StackTrace:

       at Demo.Program.DoSomething(String str) in C:\Development\My Tools\BLOG Articles\Article #17\Demo\Demo\Program.cs:line 31

       at Demo.Program.Main(String[] args) in C:\Development\My Tools\BLOG Articles\Article #17\Demo\Demo\Program.cs:line 24

       at System.AppDomain.nExecuteAssembly(Assembly assembly, String[] args)

       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)

       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()

       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)

       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)

       at System.Threading.ThreadHelper.ThreadStart()

 

 

Portanto, o primeiro problema é que o código não lida com situações onde a string é nula. O comentário do código explica que a rotina deve retornar false se a string do parâmetro for vazia ou nula mas a implementação apenas testa se a string é vazia!

 

 

PROBLEMA 2

 

Sem dúvida o maior causador de instâncias nunca decrementadas no COM+ e consequentes hangs.

 

Eis a execução antes de instanciar o componente COM do Word.

 

 

Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner

-----------------------------

Total           0

CCW             0

RCW             0                  ß Runtime Callable Wrapper, responsável por gerenciar o contador de referência de objetos COM.

ComClassFactory 0

Free            0

 

 

Após a instanciação do componente:

 

Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner

-----------------------------

Total           1

CCW             0

RCW             1                  ß Ok, internamente houve um AddRef() do COM.

ComClassFactory 0

Free            0

 

 

Na linha do msWord.Quit():

 

OS Thread Id: 0x72b8 (0)

ESP/REG  Object   Name

eax      0127244c System.Boolean

esi      0127244c System.Boolean

0012f43c 0127243c Microsoft.Office.Interop.Word.ApplicationClass

0012f440 01272420 System.String    ABCD

0012f450 0127244c System.Boolean

0012f460 01271be0 System.String    Abcd

0012f46c 01271be0 System.String    Abcd

0012f47c 01271bd0 System.Object[]    (System.String[])

0012f534 01271bd0 System.Object[]    (System.String[])

0012f6e0 01271bd0 System.Object[]    (System.String[])

0012f708 01271bd0 System.Object[]    (System.String[])

 

 

Após finally:

 

Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner

    1 001af254            0         0 00000000     none    0127243c Microsoft.Office.Interop.Word.ApplicationClass

-----------------------------

Total           1

CCW             0

RCW             1              ß Ei, Relase() do COM não foi chamado! O objeto COM não sabe que pode sair da memória pois ainda há referências para suas interfaces!

ComClassFactory 0

Free            0

 

 

RuntimeCallableWrappers (RCW) a serem liberados:

     RCW  CONTEXT   THREAD Apartment

       0 80000001        0       MTA

MTA Interfaces to be released: 1

STA Interfaces to be released: 0               

 

 

Após várias execuções do mesmo método temos um aumento constante das referências para o objeto COM MTA do Word.

Veja a segunda execução logo após a segunda instanciação do componente do Word:

 

Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner

    1 001af254            0         0 00000000     none    0127243c Microsoft.Office.Interop.Word.ApplicationClass

    2 001af284            0         0 00000000     none    01272470 Microsoft.Office.Interop.Word.ApplicationClass

-----------------------------

Total           2

CCW             0

RCW             2

ComClassFactory 0

Free            0

 

 

Portanto, no segundo problema o wrapper que o .NET usa para se comunicar com o COM não decrementa as referências logo o objeto continua na memória!

 

 

SOLUÇÃO – PROBLEMA 1

 

Para o Problema 1 a solução é bastante simples…

 

Em .NET Framework 1.1 e 2.0:

 

static bool DoSomething(string str)

{

          // Checa se string é nula ou vazia.

          if((null == str) || (0 == str.Length))

          {

                   return false;

          }

 

Em .NET Framework 2.0 especificamente:

 

static bool DoSomething(string str)

{

          // Checa se string é nula ou vazia.

          if(String.IsNullOrEmpty(str))

          {

                   return false;

          }

 

 

Para evitar esse tipo de problema, mantenha em mente que uma string Null é diferente de uma string vazia.

 

 

<IMPORTANTE>

 

String vazia:

 

Uma string vazia é uma instância de System.String que contém zero caracteres.

 

 

String nula (null string):

 

Uma string nula não referencia uma instância de um objeto System.String e tentativas de se executar métodos de uma string nula resultam em uma exceção do tipo NullReferenceException.

 

</IMPORTANTE>

 

 

Eis alguns artigos de referência:

 

http://msdn2.microsoft.com/en-us/library/ms228362.aspx

http://msdn2.microsoft.com/en-us/library/sxw2ez55.aspx

 

 

SOLUÇÃO – PROBLEMA 2

 

Para o Problema 2 a solução também é bastante simples.

 

O que? Você pensou em set msWord = Nothing no VB.NET ou msWord = null no C#?

Não, infelizmente não… isso funciona no mundo nativo, em Visual Basic 6 e ASP por exemplo. Em .NET as coisas são um pouco diferentes...

 

A solução se baseia em utilizarmos uma chamada de método que força o wrapper usado pelo .NET a liberar a referência para o objeto COM.

 

Eis o código corrigido e o resultado da depuração do mesmo:

 

 

namespace Demo

{

          class Program

          {

                   static void Main(string[] args)

                   {

                             string str = "Abcd";

                            

                             DoSomething(str);

                            

 

                             DoSomething("Def");

                            

                             str = "";

                            

                             DoSomething(str);

 

                             str = null;

 

                             DoSomething(str);    // Agora não falha mais!

                            

                   }

                  

                   static bool DoSomething(string str)

                   {

                             // Checa se string é nula ou vazia.

                             if((null == str) || 0 == str.Length)

                             {

                                      return false;

                             }

                            

                             str = str.ToUpper();

                                     

                             Microsoft.Office.Interop.Word.Application msWord = null;

                                                                            

                             try

                             {

                                      msWord = new Microsoft.Office.Interop.Word.Application();

 

                                      object saveChanges = false;

                                      object missing = null;

 

                                      msWord.Quit(ref saveChanges, ref missing, ref missing);                                   

                             }

                             catch(OutOfMemoryException ex)

                             {

                                      Console.WriteLine("Não há memória disponível...");

                             }

                             finally

                             {

// Libera wrapper da memória que, por sua vez, libera as referências para o

// objeto COM.

                                      // Evita NullReferenceException caso tentarmos liberar o objeto e

                                      // sem que o mesmo tenha sido inicializado!

                                      if(msWord != null)

                                      {

                                                // Para .NET Framework 2.0.

                                                System.Runtime.InteropServices.Marshal.FinalReleaseComObject(msWord);                                                         }

                                     

                                  msWord = null;  

                             }

                            

                             return true;

                   }

          }

}

 

 

Note que para compatibilidade com .NET Framework 1.1 e 2.0, dentro do finally e dentro do if() acima você deve usar:

 

while(System.Runtime.InteropServices.Marshal.ReleaseComObject(msWord) > 0);

 

 

 

<IMPORTANTE>

 

System.Runtime.InteropServices.Marshal.ReleaseComObject() decrementa em um o contador de referência interna portanto se faz necessário chamá-lo em um loop para garantir que o wrapper liberou todas as referências para o objeto COM.

Na aplicação usada no desafio não é necessário se chamar dentro de um loop, entretanto, recomendo que o loop sempre seja utilizado pois numa aplicação real caso haja mudanças que impliquem em incremento de contador de referências interna do COM não é necessário se preocupar em mudar o código que libera o objeto COM, se o loop acima for utilizado. Portanto, eliminando um potencial bug na aplicação! J

 

</IMPORTANTE>

 

 

Eis a depuração logo antes de instanciarmos o componente Word, usando a solução para o problema 2:

 

 

Thread 0:

 

ChildEBP RetAddr 

0012f474 00cc0096 Demo!Demo.Program.DoSomething(System.String)+0x8e

0012f490 79e88f63 Demo!Demo.Program.Main(System.String[])+0x26

0012f490 79e88ee4 mscorwks!CallDescrWorker+0x33

0012f510 79e88e31 mscorwks!CallDescrWorkerWithHandler+0xa3

0012f650 79e88d19 mscorwks!MethodDesc::CallDescr+0x19c

0012f668 79e88cf6 mscorwks!MethodDesc::CallTargetWorker+0x20

0012f67c 79f084b0 mscorwks!MethodDescCallSite::Call_RetArgSlot+0x18

0012f7e0 79f082a9 mscorwks!ClassLoader::RunMain+0x220

0012fa48 79f0817e mscorwks!Assembly::ExecuteMainMethod+0xa6

0012ff18 79f07dc7 mscorwks!SystemDomain::ExecuteMainMethod+0x398

0012ff68 79f05f61 mscorwks!ExecuteEXE+0x59

0012ffb0 79011b5f mscorwks!_CorExeMain+0x11b

0012ffc0 7c816fd7 mscoree!_CorExeMain+0x2c

0012fff0 00000000 KERNEL32!BaseProcessStart+0x23

 

 

Referências para RCW:

 

Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner

-----------------------------

Total           0

CCW             0

RCW             0

ComClassFactory 0

Free            0

 

 

Agora logo após a instanciação:

 

Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner

    1 03ba004c            0         0 00000000     none    0127243c Microsoft.Office.Interop.Word.ApplicationClass

-----------------------------

Total           1

CCW             0

RCW             1

ComClassFactory 0

Free            0

 

 

RuntimeCallableWrappers (RCW) a serem liberados:

     RCW  CONTEXT   THREAD Apartment

       0 80000001        0       MTA

MTA Interfaces to be released: 1

STA Interfaces to be released: 0

 

 

Agora acabo de executar a chamada para ReleaseComObject():

 

Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner

    1 03ba004c            0         0 00000000     none    0127243c Microsoft.Office.Interop.Word.ApplicationClass

-----------------------------

Total           1

CCW             0

RCW             0         ß Liberado! Com isso o objeto COM será liberado também!

ComClassFactory 0

Free            0

 

 

RuntimeCallableWrappers (RCW) a serem liberados:

     RCW  CONTEXT   THREAD Apartment

       0 80000001        0       MTA

MTA Interfaces to be released: 1

STA Interfaces to be released: 0   ß Se fosse um componente VB 6 sendo chamado então seria STA.

 

 

Eis a pilha do CLR:

 

OS Thread Id: 0xa7e4 (0)

ESP       EIP    

ESP/REG  Object   Name

esi      0127244c System.Boolean

0012f434 00cc01ee Demo.Program.DoSomething(System.String)

    PARAMETERS:

        str = 0x01272420

    LOCALS:

        0x0012f43c = 0x0127243c      ß Equivale ao objeto msWord do código fonte.

        0x0012f450 = 0x0127244c         

        0x0012f44c = 0x00000000

        0x0012f438 = 0x00000000

        0x0012f448 = 0x00000000

        0x0012f444 = 0x00000001

 

ESP/REG  Object   Name

esi      0127244c System.Boolean

0012f43c 0127243c Microsoft.Office.Interop.Word.ApplicationClass

0012f440 01272420 System.String    ABCD

0012f450 0127244c System.Boolean

0012f460 01271be0 System.String    Abcd

0012f46c 01271be0 System.String    Abcd

0012f47c 00cc0096 Demo.Program.Main(System.String[])

    PARAMETERS:

        args = 0x01271bd0

    LOCALS:

        <CLR reg> = 0x01271be0

 

ESP/REG  Object   Name

esi      0127244c System.Boolean

0012f47c 01271bd0 System.Object[]    (System.String[])

0012f534 01271bd0 System.Object[]    (System.String[])

0012f69c 79e88f63 [GCFrame: 0012f69c]

 

 

Eis o objeto RCW:

 

Name: Microsoft.Office.Interop.Word.ApplicationClass

MethodTable: 03435afc

EEClass: 0336ea90

Size: 16(0x10) bytes

GC Generation: 0

 (C:\WINDOWS\assembly\GAC\Microsoft.Office.Interop.Word\11.0.0.0__71e9bce111e9429c\Microsoft.Office.Interop.Word.dll)

Fields:

      MT    Field   Offset                 Type VT     Attr    Value Name

790f9c18  4000184        4        System.Object  0 instance 00000000 __identity

790fea70  4000277        8 ...ections.Hashtable  0 instance 00000000 m_ObjectToDataMap   ß Wrapper liberado! Agora é com o GC.

 

 

 

Como estou usando uma versão Debug, o código não está otimizado portanto o IL e o código disassemblado são mais fáceis de visualizar. No caso, eis parte do método DoSomething():

 

00cc01e8 e833167a78    call    mscorlib_ni!System.Runtime.InteropServices.Marshal.ReleaseComObject(System.Object) (79461820)

00cc01ed 90              nop

00cc01ee 33d2           xor     edx,edx                          ß Conteúdo de msWord em null.

00cc01f0 8955c8        mov   dword ptr [ebp-38h],edx     ß Atribui null a msWord.

00cc01f3 90              nop

00cc01f4 58              pop     eax

 

 

Agora a execução de msWord = null.

Note que é uma boa prática se atribuir null (ou nothing em Visual Basic .NET) para um objeto, após usá-lo, e sempre fazer comparações com null antes de usá-lo. Isso ajuda a identificar bugs mais facilmente.

 

edx:

 

00140608  7c97c500

 

ebp-0x38  (msWord):

 

0012f43c  0127243c

 

Após msWord = null:

 

edx:

 

00000000  ????????

 

ebp-0x38 (msWord):

 

0012f43c  00000000   ( msWord = null; )

 

Portanto, nesse ponto seguramente nosso objeto msWord é null e o RCW foi liberado da memória, liberando o objeto COM!

 

Note que é uma boa prática de programação configurar objetos para null após liberá-los/usá-los e sempre testar se um objeto não é null antes de usar.

Por que isso? Porque se você compara o objeto contra null e tenta utilizá-lo, após ter liberado os recursos associados ao mesmo (no nosso caso o wrapper para o objeto COM), a comparação vai suceder e o código vai lançar uma exceção!

 

Por exemplo:

 

if(msWord != null)

{

          // msWord não mais aponta para uma instância do wrapper após a linha abaixo ser executada...

          System.Runtime.InteropServices.Marshal.FinalReleaseComObject(msWord); 

}

 

if(msWord != null)  // Ao mesmo tempo o if() retornará true porque não explicitamente configuramos msWord = null;

{

          // Usa método de msWord. Exceção!!! msWord não aponta para uma instância válida do seu tipo.

}

 

 

Outro importante ponto é o código dentro de finally. Não podemos assumir no código dentro de finally que o objeto que apontava para null agora aponta para uma instância do tipo, afinal, no bloco try uma exceção OutOfMemoryException poderia ter sido lançada na inicialização do objeto! Se assumirmos que o objeto sempre é inicializado teremos uma exceção NullReferenceException dentro do bloco finally quando o objeto for null!

 

Eis os artigos de referências:

 

PRB: COM+ Instance Count Does Not Decrease When Called from .NET Application

http://support.microsoft.com/kb/305823/en-us

 

Marshal.FinalReleaseComObject Method 

http://msdn2.microsoft.com/en-us/library/system.runtime.interopservices.marshal.finalreleasecomobject(VS.80).aspx

 

Marshal.ReleaseComObject

http://msdn2.microsoft.com/en-us/library/system.runtime.interopservices.marshal.releasecomobject(VS.80).aspx

 

E artigo explicando sobre RCW:

 

Runtime Callable Wrapper

http://msdn2.microsoft.com/en-us/library/8bwh56xe.aspx

 

Hum... agora você está entendendo porque aquele componente no COM+ sendo chamado pela sua página ASPX sempre aumenta o contador de referências, algumas vezes causando hang no servidor. Ótimo!

 

 

Até a próxima!