Seguindo as aventuras no mundo dos bindings C# para a EFL, um problema que enfrentei semana passada durante o port dos bindings para o Windows envolvia a famigerada API Win32. Tudo estava correndo bem até tentar mostrar algo na tela, quando os módulos de UI simplesmente se recusavam a carregar, ao contrário de quando tentávamos rodar apenas o código nativo, sem o binding. O culpado: OleInitialize.
A EFL no Windows utiliza por baixo dos panos a Win32, API tradicional para apps nativas do sistema operacional. Entre os componentes utilizados, está o velho OLE (Object Linking and Embedding), responsável por compartilhar itens entre aplicações, como por exemplo os serviços de drag and drop e clipboard, desde o começo dos anos 90. Ao longo do tempo, o OLE acabou gerando o COM (Component Object Model), que serve comunicação interprocessos para outras tecnologias do Windows – numa analogia meio “grosseira”, seria uma espécie de “DBus do Windows”, porém pelo menos 10 anos mais velho.
Para lidar com threading, o COM agrupa os objetos em apartments, que podem ser single thread – todas as chamadas a um objeto saem da mesma thread – e multi thread – cada objeto cuida de sua própria sincronização e chamadas podem vir de múltiplas threads. Entre outras coisas, o Oleinitialize cuida de inicializar o COM caso este já não esteja inicializado, e obrigatoriamente no modo single-thread. Caso a função retorne RPC_E_CHANGED_MODE, significa que o COM já foi inicializado anteriormente em modo multi-thread. E era exatamente isso que estava acontecendo.
Investigando mais um pouco, o principal suspeito era o próprio runtime do .NET. Verificando o número de threads com System.Diagnostics.Process.GetCurrentProcess().Threads.Count, um simples “hello world” indicava 6 threads em execução. E ao inspecionar o código do Mono, realmente o COM era inicializado em modo multithread. E a resposta é dada na documentação do atributo STAThreadAttribute (livre tradução):
A partir da versão 2.0 do framework .NET, o modelo de threading padrão da interoperabilidade COM depende da linguagem em que se está desenvolvendo a aplicação:
- C++/C#: Multi-thread
- Visual Basic: Single thread.
Prevendo esse tipo de problema, .NET oferece para C# o já mencionado atributo STAThreadAttribute, que obrigatoriamente deve decorar o Main() da aplicação para indicar que a interoperabilidade COM deve excepcionalmente ser inicializada como single thread. Para aplicações C++, a flag /CLRTHREADATTRIBUTE:STA deve ser fornecida para o linker.
o/