Descrições e trabalhos de modelos de threading OLE

Este artigo descreve modelos de threading OLE.

Versão original do produto: Modelos de threading OLE
Número de KB original: 150777

Resumo

Objetos COM podem ser usados em vários threads de um processo. Os termos "Apartamento com thread único" (STA) e "Apartamento Multi threaded" (MTA) são usados para criar uma estrutura conceitual para descrever a relação entre objetos e threads, as relações de simultaneidade entre objetos, os meios pelos quais as chamadas de método são entregues a um objeto e as regras para passar ponteiros de interface entre threads. Os componentes e seus clientes escolhem entre os dois modelos de apartamento atualmente compatíveis com COM:

  1. STA (modelo de apartamento com thread único): um ou mais threads em um processo usam COM e as chamadas para objetos COM são sincronizadas pelo COM. As interfaces são empacotadas entre threads. Um caso degenerado do modelo de apartamento com thread único, em que apenas um thread em um determinado processo usa COM, é chamado de modelo de threading único. Anteriormente, às vezes, referiam-se ao modelo STA simplesmente como o "modelo de apartamento".

  2. MTA (modelo de apartamento com vários threads): um ou mais threads usam COM e as chamadas para objetos COM associados ao MTA são feitas diretamente por todos os threads associados ao MTA sem qualquer interposição de código do sistema entre o chamador e o objeto. Como vários clientes simultâneos podem estar chamando objetos de forma mais ou menos simultânea (simultaneamente em sistemas multiprocessadores), os objetos devem sincronizar seu estado interno sozinhos. As interfaces não são empacotadas entre threads. Anteriormente, às vezes, referiam-se a esse modelo como o "modelo de thread livre".

  3. O modelo STA e o modelo MTA podem ser usados no mesmo processo. Isso às vezes é chamado de processo de "modelo misto".

O MTA é introduzido no NT 4.0 e está disponível no Windows 95 com DCOM95. O modelo STA existe em Windows NT 3.51 e Windows 95, bem como NT 4.0 e Windows 95 com DCOM95.

Visão Geral

Os modelos de threading em COM fornecem o mecanismo para componentes que usam diferentes arquiteturas de threading trabalharem juntos. Eles também fornecem serviços de sincronização para componentes que exigem eles. Por exemplo, um objeto específico pode ser projetado para ser chamado apenas por um único thread e pode não sincronizar chamadas simultâneas de clientes. Se esse objeto for chamado simultaneamente por vários threads, ele falhará ou causará erros. O COM fornece os mecanismos para lidar com essa interoperabilidade de arquiteturas de threading.

Mesmo componentes com reconhecimento de thread geralmente precisam de serviços de sincronização. Por exemplo, componentes que têm uma GUI (interface gráfica do usuário), como controles OLE/ActiveX, inserções ativas in loco e documentos ActiveX, exigem sincronização e serialização de chamadas COM e mensagens de janela. O COM fornece esses serviços de sincronização para que esses componentes possam ser gravados sem código de sincronização complexo.

Um "apartamento" tem vários aspectos inter-relacionados. Primeiro, é uma construção lógica para pensar em simultaneidade, como como os threads se relacionam com um conjunto de objetos COM. Em segundo lugar, é um conjunto de regras que os programadores devem obedecer para receber o comportamento de simultaneidade que esperam do ambiente COM. Por fim, é um código fornecido pelo sistema que ajuda os programadores a gerenciar a simultaneidade do thread em relação aos objetos COM.

O termo "apartamento" vem de uma metáfora na qual um processo é concebido como uma entidade discreta, como um "edifício" que é subdividido em um conjunto de "localidades" relacionadas, mas diferentes, chamadas "apartamentos". Um apartamento é um "contêiner lógico" que cria uma associação entre objetos e, em alguns casos, threads. Threads não são apartamentos, embora possa haver um único thread logicamente associado a um apartamento no modelo STA. Objetos não são apartamentos, embora cada objeto esteja associado a um e apenas um apartamento. Mas apartamentos são mais do que apenas uma construção lógica; suas regras descrevem o comportamento do sistema COM. Se as regras dos modelos de apartamento não forem seguidas, os objetos COM não funcionarão corretamente.

Mais detalhes

Um STA (apartamento com thread único) é um conjunto de objetos COM associados a um thread específico. Esses objetos são associados ao apartamento sendo criados pelo thread ou, mais precisamente, sendo expostos pela primeira vez ao sistema COM (normalmente por marshaling) no thread. UM STA é considerado um lugar onde um objeto ou um proxy "vive". Se o objeto ou proxy precisar ser acessado por outro apartamento (no mesmo ou em um processo diferente), seu ponteiro de interface deve ser empacotado para aquele apartamento em que um novo proxy é criado. Se as regras do modelo de apartamento forem seguidas, nenhuma chamada direta de outros threads no mesmo processo será permitida nesse objeto; que violaria a regra de que todos os objetos dentro de um determinado apartamento são executados em um único thread. A regra existe porque a maioria dos códigos em execução em um STA não funcionará corretamente se for executada em threads adicionais.

O thread associado a uma STA deve chamar CoInitialize ou CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) e deve recuperar e despachar mensagens de janela para que os objetos associados recebam chamadas recebidas. O COM despacha e sincroniza chamadas para objetos em um STA usando mensagens de janela, conforme descrito posteriormente neste artigo.

O "main STA" é o thread que chama CoInitialize ou CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) primeiro dentro de um determinado processo. A STA main de um processo deve permanecer viva até que todo o trabalho COM seja concluído porque alguns objetos no proc são sempre carregados no STA main, conforme descrito posteriormente neste artigo.

Windows NT 4.0 e DCOM95 introduzem um novo tipo de apartamento chamado MTA (apartamento multi-threaded). Um MTA é um conjunto de objetos COM associados a um conjunto de threads no processo de modo que qualquer thread possa chamar qualquer implementação de objeto diretamente sem a interposição do código do sistema. Os ponteiros de interface para qualquer objeto no MTA podem ser passados entre os threads associados ao MTA sem precisar ser empacotado. Todos os threads no processo que chamam CoInitializeEx(NULL, COINIT_MULTITHREADED) estão associados ao MTA. Ao contrário do STA descrito acima, os threads em um MTA não precisam recuperar e despachar mensagens de janela para que os objetos associados recebam chamadas de entrada. O COM não sincroniza chamadas para objetos em um MTA. Os objetos em um MTA devem proteger seu estado interno contra corrupção pela interação de vários threads simultâneos e não podem fazer nenhuma suposição sobre o conteúdo de Thread-Local Armazenamento permanecendo constante entre diferentes invocações de método.

Um processo pode ter qualquer número de STAs, mas, no máximo, pode ter um MTA. O MTA consiste em um ou mais threads. Os STAs têm um thread cada. Um thread pertence, no máximo, a um apartamento. Os objetos pertencem a um e apenas um apartamento. Os ponteiros de interface devem sempre ser empacotados entre apartamentos (embora o resultado do marshaling possa ser um ponteiro direto em vez de um proxy). Confira as informações abaixo em CoCreateFreeThreadedMarshaler.

Um processo escolhe um dos modelos de threading fornecidos pelo COM. Um processo de modelo STA tem um ou mais STAs e não tem um MTA. Um processo de modelo MTA tem um MTA com um ou mais threads e não tem nenhum STAs. Um processo de modelo misto tem um MTA e qualquer número de STAs.

Modelo de apartamento com thread único

O thread de um STA deve chamar CoInitialize ou CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) e deve recuperar e despachar mensagens de janela porque o COM usa mensagens de janela para sincronizar e despachar a entrega de chamadas para um objeto neste modelo. Confira a seção REFERÊNCIAS abaixo para obter mais informações.

Servidor que dá suporte ao modelo STA:

No modelo STA, as chamadas para um objeto são sincronizadas pelo COM da mesma maneira que as mensagens de janela postadas em uma janela são sincronizadas. As chamadas são entregues usando mensagens de janela para o thread que criou o objeto. Consequentemente, o thread do objeto deve chamar Get/PeekMessage e DispatchMessage receber chamadas. O COM cria uma janela oculta associada a cada STA. Uma chamada para um objeto de fora do STA é transferida pelo runtime COM para o thread do objeto usando uma mensagem de janela postada nesta janela oculta. Quando o thread associado ao STA do objeto recupera e despacha a mensagem, o procedimento de janela da janela oculta, também implementado pela COM, recebe-a. O procedimento de janela é usado pelo runtime COM para "enganchar" o thread associado ao STA porque o runtime COM está em ambos os lados da chamada do thread de propriedade COM para o thread do STA. O runtime COM (agora em execução no thread do STA) chama "up" por meio de um stub fornecido por COM no método de interface correspondente do objeto. O caminho de execução que retorna da chamada de método inverte a chamada "up"; a chamada retorna para baixo para o stub e o runtime COM, que passa o controle de volta para o thread de runtime COM por meio de uma mensagem de janela, que retorna pelo canal COM para o chamador original.

Quando vários clientes chamam um objeto STA, as chamadas são automaticamente enfileiradas na fila de mensagens pela transferência do mecanismo de controle usado no STA. O objeto recebe uma chamada sempre que seu STA recupera e envia mensagens. Como as chamadas são sincronizadas pelo COM dessa maneira e, como as chamadas são sempre entregues no thread único associado ao STA do objeto, as implementações de interface do objeto não precisam fornecer sincronização.

Observação

O objeto pode ser ressentido se uma implementação do método de interface recuperar e despachar mensagens durante o processamento de uma chamada de método, fazendo com que outra chamada seja entregue ao objeto pelo mesmo STA. Uma maneira comum em que isso ocorre é se um objeto STA faz uma chamada de saída (entre apartamentos/processo cruzado) usando COM. Isso é idêntico à maneira como um procedimento de janela pode ser reentrado se recuperar e despachar mensagens durante o processamento de uma mensagem. O COM não impede a reentrada no mesmo thread, mas impede a execução simultânea. Ele também fornece um meio pelo qual a reentrada relacionada ao COM pode ser gerenciada. Confira a seção REFERÊNCIAS abaixo para obter mais informações. O objeto não será re-inserido se as implementações de método não chamarem para fora de seu apartamento ou, de outra forma, recuperar e expedir mensagens.

Responsabilidades do cliente no modelo STA:

O código do cliente em execução em um processo e/ou thread que usa o modelo STA deve empacotar interfaces de um objeto entre apartamentos usando CoMarshalInterThreadInterfaceInStream e CoGetInterfaceAndReleaseStream. Por exemplo, se o Apartamento 1 no cliente tiver um ponteiro de interface e o Apartamento 2 exigir o uso dele, o Apartamento 1 deverá empacotar a interface usando CoMarshalInterThreadInterfaceInStream. O objeto stream retornado por essa função é thread-safe e seu ponteiro de interface deve ser armazenado em uma variável de memória direta acessível pelo Apartamento 2. O apartamento 2 deve passar essa interface de fluxo para CoGetInterfaceAndReleaseStream desmarsalar a interface no objeto subjacente e obter de volta um ponteiro para um proxy por meio do qual ele pode acessar o objeto.

O apartamento main de um determinado processo deve permanecer vivo até que o cliente tenha concluído todo o trabalho COM porque alguns objetos in-proc são carregados no main-apartamento. (Mais informações são detalhadas abaixo).

Modelo de apartamento com vários threads

Um MTA é a coleção de objetos criados ou expostos por todos os threads no processo que chamaram CoInitializeEx(NULL, COINIT_MULTITHREADED).

Observação

As implementações atuais do COM permitem que um thread que não inicialize explicitamente o COM faça parte do MTA. Um thread que não inicializa COM faz parte do MTA somente se ele começar a usar COM depois que pelo menos um outro thread no processo tiver chamado CoInitializeEx(NULL, COINIT_MULTITHREADED)anteriormente . (É até possível que o próprio COM possa ter inicializado o MTA quando nenhum thread do cliente tiver feito isso explicitamente; por exemplo, um thread associado a chamadas STA CoGetClassObject/CoCreateInstance[Ex] em um CLSID marcado como "ThreadingModel=Free" e COM cria implicitamente um MTA no qual o objeto de classe é carregado.) Confira as informações sobre a interoperabilidade do modelo de threading abaixo.

No entanto, essa é uma configuração que pode causar problemas, como violações de acesso, em determinadas circunstâncias. Portanto, é recomendável que cada thread que precisa fazer o trabalho COM inicialize COM chamando CoInitializeEx e, ao concluir o trabalho COM, chame CoUninitialize. O custo de inicializar "desnecessariamente" um MTA é mínimo.

Os threads MTA não precisam recuperar e despachar mensagens porque o COM não usa mensagens de janela neste modelo para entregar chamadas a um objeto.

  • Servidor que dá suporte ao modelo MTA:

    No modelo MTA, as chamadas para um objeto não são sincronizadas pelo COM. Vários clientes podem chamar simultaneamente um objeto que dá suporte a esse modelo em threads diferentes, e o objeto deve fornecer sincronização em suas implementações de interface/método usando objetos de sincronização, como eventos, mutexes, semáforos, etc. Os objetos MTA podem receber chamadas simultâneas de vários clientes fora de processo por meio de um pool de threads criados por COM pertencentes ao processo do objeto. Objetos MTA podem receber chamadas simultâneas de vários clientes em processo em vários threads associados ao MTA.

  • Responsabilidades do cliente no modelo MTA:

    O código do cliente em execução em um processo e/ou thread que usa o modelo MTA não precisa empacotar ponteiros de interface de um objeto entre si e outros threads MTA. Em vez disso, um thread MTA pode usar um ponteiro de interface obtido de outro thread MTA como um ponteiro de memória direto. Quando um thread do cliente faz uma chamada para um objeto fora de processo, ele é suspenso até que a chamada seja concluída. As chamadas podem chegar em objetos associados ao MTA, enquanto todos os threads criados pelo aplicativo associados ao MTA são bloqueados em chamadas de saída. Nesse caso e, em geral, as chamadas de entrada são entregues em threads fornecidos pelo runtime COM. Os filtros de mensagem (IMessageFilter) não estão disponíveis para uso no modelo MTA.

Modelos de threading misto

Um processo que dá suporte ao modelo de threading misto usará um MTA e um ou mais STAs. Os ponteiros de interface devem ser empacotados entre todos os apartamentos, mas podem ser usados sem marshaling dentro do MTA. As chamadas para objetos em um STA são sincronizadas pelo COM para serem executadas em apenas um thread, enquanto as chamadas para objetos no MTA não são. No entanto, as chamadas de um STA para um MTA normalmente passam pelo código fornecido pelo sistema e alternam do thread STA para um thread MTA antes de serem entregues ao objeto.

Observação

Consulte a documentação do SDK e CoCreateFreeThreadedMarshaler() a discussão dessa API abaixo para obter informações sobre casos em que ponteiros diretos podem ser usados e como um thread STA pode chamar diretamente em um objeto associado primeiro ao MTA e, vice-versa, de vários apartamentos.

Escolhendo os modelos de threading

Um componente pode optar por dar suporte ao modelo STA, ao modelo MTA ou a uma combinação dos dois usando o modelo de threading misto. Por exemplo, um objeto que faz E/S extenso pode optar por dar suporte ao MTA para fornecer resposta máxima aos clientes, permitindo que chamadas de interface sejam feitas durante a latência de E/S. Como alternativa, um objeto que interage com o usuário quase sempre opta por dar suporte ao STA para sincronizar chamadas COM de entrada com suas operações de GUI. Dar suporte ao modelo STA é mais fácil porque o COM fornece sincronização. Dar suporte ao modelo MTA é mais difícil porque o objeto deve implementar a sincronização, mas a resposta aos clientes é melhor porque a sincronização é usada para seções menores de código, em vez de para toda a chamada de interface, conforme fornecido pelo COM.

O modelo STA também é usado pelo MTS (Microsoft Transaction Server, anteriormente chamado de "Viper" e, portanto, objetos baseados em DLL que planejam ser executados no ambiente MTS devem usar o modelo STA. Os objetos implementados para o modelo MTA normalmente funcionarão bem em um ambiente MTS. No entanto, eles serão executados com menos eficiência porque usarão primitivos de sincronização de thread desnecessários.

Marcando o modelo de threading com suporte de servidores In-Proc

Um thread usa o modelo MTA se ele chama CoInitializeEx(NULL, COINIT_MULTITHREADED) ou usa COM sem inicializá-lo. Um thread usa o modelo STA se ele chama CoInitialize ou CoInitializeEx(NULL, COINIT_APARTMENTTHREADED).

As CoInitialize APIs fornecem controle de apartamento para o código do cliente e para objetos empacotados. EXEs, porque o código de inicialização do runtime COM pode inicializar COM da forma desejada.

No entanto, um servidor COM no proc (baseado em DLL) não chama CoInitialize/CoInitializeEx porque essas APIs terão sido chamadas no momento em que o servidor DLL for carregado. Portanto, um servidor DLL deve usar o registro para informar o COM do modelo de threading que ele dá suporte para que o COM possa garantir que o sistema funcione de forma compatível com ele. Um valor nomeado da chave CLSID\InprocServer32 do componente chamada ThreadingModel é usado para essa finalidade da seguinte maneira:

  • ThreadingModel valor não presente: dá suporte ao modelo de threading único.
  • ThreadingModel=Apartment: dá suporte ao modelo STA.
  • ThreadingModel=Both: dá suporte ao modelo STA e MTA.
  • ThreadingModel=Free: dá suporte apenas ao MTA.

Observação

ThreadingModel é um valor nomeado, não uma subchave do InprocServer32 conforme documentado incorretamente em algumas versões anteriores da documentação do Win32.

Os modelos de threading de servidores in-proc são discutidos posteriormente neste artigo. Se um servidor no proc fornecer muitos tipos de objetos (cada um com seu próprio CLSID exclusivo), cada tipo poderá ter um valor diferente ThreadingModel . Em outras palavras, o modelo de threading é por CLSID, não por pacote de código/DLL. No entanto, os pontos de entrada da API necessários para "bootstrap" e consultar todos os servidores in-proc (DLLGetClassObject(), DLLCanUnloadNow()) devem ser thread-safe para qualquer servidor in-proc que dê suporte a vários threads (o que significa um ThreadingModel valor de Apartment, Both ou Free).

Conforme indicado anteriormente, os servidores fora de processo não se marcam usando o valor ThreadingModel. Em vez disso, eles usam CoInitialize ou CoInitializeEx. Servidores baseados em DLL que esperam ficar sem processo usando a funcionalidade "substituta" do COM (como o DLLHOST.EXE substituto fornecido pelo sistema) seguem as regras para servidores baseados em DLL; não há considerações especiais nesse caso.

Quando o cliente e o objeto Usar modelos de threading diferentes

A interação entre um cliente e um objeto fora de processo é direta mesmo quando diferentes modelos de threading são usados porque o cliente e o objeto estão em processos diferentes e o COM está envolvido na passagem de chamadas do cliente para o objeto. Como o COM é interposto entre o cliente e o servidor, ele fornece o código para interoperação dos modelos de threading. Por exemplo, se um objeto STA for chamado simultaneamente por vários clientes STA ou MTA, o COM sincronizará as chamadas colocando mensagens de janela correspondentes na fila de mensagens do servidor. O STA do objeto recebe uma chamada cada vez que recupera e envia mensagens. Todas as combinações de interoperabilidade de modelo de threading são permitidas e com suporte total entre clientes e objetos fora de processo.

A interação entre um cliente e um objeto no proc que usa diferentes modelos de threading é mais complicada. Embora o servidor esteja no proc, o COM deve se interpor entre o cliente e o objeto em alguns casos. Por exemplo, um objeto in-proc projetado para dar suporte ao modelo STA pode ser chamado simultaneamente por vários threads de um cliente. O COM não pode permitir que os threads do cliente acessem diretamente a interface do objeto porque o objeto não foi projetado para esse acesso simultâneo. Em vez disso, o COM deve garantir que as chamadas sejam sincronizadas e feitas apenas pelo thread associado ao STA que "contém" o objeto. Apesar da complexidade adicionada, todas as combinações de interoperabilidade de modelo de threading são permitidas entre clientes e objetos in-proc.

Modelos de threading em servidores fora de processo (baseados em EXE)

A seguir estão três categorias de servidores fora de processo, cada uma das quais pode ser usada por qualquer cliente COM, independentemente do modelo de threading usado por esse cliente:

  1. Servidor de Modelo STA:

    O servidor faz com que o COM funcione em um ou mais STAs. As chamadas de entrada são sincronizadas pelo COM e entregues pelo thread associado ao STA no qual o objeto foi criado. As chamadas do método class-factory são entregues pelo thread associado ao STA que registrou a fábrica de classes. O objeto e a fábrica de classes não precisam implementar a sincronização. No entanto, o implementador deve sincronizar o acesso a quaisquer variáveis globais usadas por várias STAs. O servidor deve usar CoMarshalInterThreadInterfaceInStream e CoGetInterfaceAndReleaseStream para empacotar ponteiros de interface, possivelmente de outros servidores, entre STAs. Opcionalmente, o servidor pode implementar IMessageFilter em cada STA para controlar aspectos da entrega de chamadas por COM. Um caso degenerado é o servidor de modelo de thread único que faz com que o COM funcione em um STA.

  2. Servidor de Modelo MTA:

    O servidor faz com que o COM funcione em um ou mais threads, todos pertencentes ao MTA. As chamadas não são sincronizadas pelo COM. O COM cria um pool de threads no processo do servidor e uma chamada do cliente é entregue por qualquer um desses threads. Os threads não precisam recuperar e enviar mensagens. O objeto e a fábrica de classes devem implementar a sincronização. O servidor não precisa empacotar ponteiros de interface entre threads.

  3. Servidor de Modelo Misto:

    Confira a seção neste artigo intitulada "Modelo de threading misto" para obter mais informações.

Modelos de threading em clientes

Há três categorias de clientes:

  1. Cliente modelo STA:

    O cliente faz com que o COM funcione em threads associados a um ou mais STAs. O cliente deve usar CoMarshalInterThreadInterfaceInStream e CoGetInterfaceAndReleaseStream para empacotar ponteiros de interface entre STAs. Um caso degenerado é o cliente de modelo de thread único que faz COM funcionar em um STA. O thread do cliente insere um loop de mensagem fornecido por COM quando ele faz uma chamada de saída. O cliente pode usar IMessageFilter para gerenciar retornos de chamada e processamento de mensagens de janela enquanto aguarda chamadas pendentes e outros problemas de simultaneidade.

  2. Cliente modelo MTA:

    O cliente faz com que o COM funcione em um ou mais threads, todos pertencentes ao MTA. O cliente não precisa empacotar ponteiros de interface entre seus threads. O cliente não pode usar IMessageFilter. Os threads do cliente suspendem quando fazem uma chamada COM para um objeto fora de processo e são retomados quando a chamada retorna. As chamadas de entrada chegam em threads criados por COM e gerenciados.

  3. Cliente de modelo misto:

    Confira a seção neste artigo intitulada "Modelo de threading misto" para obter mais informações.

Modelos de threading em servidores in-proc (baseados em DLL)

Há quatro categorias de servidores in-proc, cada uma delas pode ser usada por qualquer cliente COM, independentemente do modelo de threading usado por esse cliente. No entanto, os servidores no proc devem fornecer código de marshaling para qualquer interface personalizada (não definida pelo sistema) que implementem se quiserem dar suporte à interoperabilidade do modelo de threading, pois isso normalmente exige que o COM marshal a interface entre apartamentos cliente. As quatro categorias são:

  1. Servidor in-proc que dá suporte a threading único ("main" STA)- nenhum ThreadingModel valor:

    Um objeto fornecido por este servidor espera ser acessado pelo mesmo STA cliente pelo qual foi criado. Além disso, o servidor espera que todos os seus pontos de entrada, como DllGetClassObject e DllCanUnloadNow, e dados globais sejam acessados pelo mesmo thread (aquele associado ao MAIN STA). Os servidores que existiam antes da introdução do threading múltiplo no COM estão nessa categoria. Esses servidores não são projetados para serem acessados por vários threads, portanto, o COM cria todos os objetos fornecidos pelo servidor no MAIN STA do processo e as chamadas para os objetos são entregues pelo thread associado ao MAIN STA. Outros apartamentos cliente ganham acesso ao objeto por meio de proxies. As chamadas dos outros apartamentos vão do proxy para o stub no main STA (marshaling entre threads) e, em seguida, para o objeto. Esse marshaling permite que o COM sincronize chamadas para o objeto e as chamadas são entregues pelo STA no qual o objeto foi criado. O marshaling entre threads é lento em relação à chamada direta, portanto, é recomendável que esses servidores sejam reescritos para dar suporte a várias STAs (categoria 2).

  2. In-proc Server que dá suporte ao modelo de apartamento com thread único (vários STAs) - marcado com ThreadingModel=Apartment:

    Um objeto fornecido por este servidor espera ser acessado pelo mesmo STA cliente pelo qual foi criado. Portanto, ele é semelhante a um objeto fornecido por um servidor no proc de thread único. No entanto, objetos fornecidos por esse servidor podem ser criados em vários STAs do processo, portanto, o servidor deve projetar seus pontos de entrada, como DllGetClassObject e DllCanUnloadNow, e dados globais para uso multi threaded. Por exemplo, se dois STAs de um processo criarem duas instâncias do objeto in-proc simultaneamente, DllGetClassObject poderão ser chamados simultaneamente por ambos os STAs. Da mesma forma, deve ser gravado para DllCanUnloadNow que o servidor esteja protegido contra ser descarregado enquanto o código ainda estiver sendo executado no servidor.

    Se o servidor fornecer apenas uma instância da fábrica de classes para criar todos os objetos, a implementação do factory de classe também deverá ser projetada para uso multi-thread porque ele é acessado por vários STAs cliente. Se o servidor criar uma nova instância da fábrica de classes cada vez DllGetClassObject que for chamada, a fábrica de classes não precisará ser thread-safe. No entanto, o implementador deve sincronizar o acesso a quaisquer variáveis globais.

    Os objetos COM criados pela fábrica de classes não precisam ser thread-safe. No entanto, o acesso de variáveis globais deve ser sincronizado pelo implementador. Depois de criado por um thread, o objeto sempre é acessado por esse thread e todas as chamadas para o objeto são sincronizadas pelo COM. Os apartamentos cliente diferentes do STA em que o objeto foi criado devem acessar o objeto por meio de proxies. Esses proxies são criados quando o cliente empacota a interface entre seus apartamentos.

    Qualquer cliente que cria um objeto STA por meio de sua fábrica de classes obtém um ponteiro direto para o objeto. Isso é diferente dos objetos no proc de thread único, em que apenas o MAIN STA do cliente obtém um ponteiro direto para o objeto e todos os outros STAs que criam o objeto ganham acesso ao objeto por meio de um proxy. Como o marshaling entre threads é lento em relação à chamada direta, a velocidade pode ser aprimorada alterando um servidor no proc de thread único para dar suporte a vários STAs.

  3. Servidor in-proc que dá suporte apenas ao MTA - marcado com ThreadingModel=Free:

    Um objeto fornecido por esse servidor é seguro somente para o MTA. Ela implementa sua própria sincronização e é acessada por vários threads de cliente ao mesmo tempo. Esse servidor pode ter um comportamento incompatível com o modelo STA. (Por exemplo, pelo uso da fila de mensagens do Windows de uma forma que quebra a bomba de mensagem de um STA.) Além disso, marcando o modelo de threading do objeto como "Gratuito", o implementador do objeto está afirmando o seguinte: esse objeto pode ser chamado de qualquer thread do cliente, mas esse objeto também pode passar ponteiros de interface diretamente (sem marshaling) para quaisquer threads criados e esses threads podem fazer chamadas por meio desses ponteiros. Assim, se o cliente passar um ponteiro de interface para um objeto implementado pelo cliente (como um coletor) para esse objeto, ele poderá optar por chamar de volta por meio desse ponteiro de interface de qualquer thread que ele criou. Se o cliente for um STA, uma chamada direta de um thread, que é diferente do thread que criou o objeto coletor, estará em erro (conforme demonstrado em 2 acima). Portanto, o COM sempre garante que os clientes em threads associados a um STA obtenham acesso a esse tipo de objeto in-proc somente por meio de um proxy. Além disso, esses objetos não devem ser agregados com o marshaler de thread livre, pois isso permite que eles sejam executados diretamente em threads STA.

  4. In-proc Server que dá suporte ao modelo de apartamento e ao threading livre - marcado com ThreadingModel=Both:

    Um objeto fornecido por esse servidor implementa sua própria sincronização e é acessado simultaneamente por vários apartamentos cliente. Além disso, esse objeto é criado e usado diretamente, em vez de por meio de um proxy, em STAs ou no MTA de um processo cliente. Como esse objeto é usado diretamente em STAs, o servidor deve empacotar interfaces de objetos, possivelmente de outros servidores, entre threads para que seu acesso a qualquer objeto de forma apropriada para threading seja garantido. Além disso, marcando o modelo de threading do objeto como "Ambos", o implementador do objeto está informando o seguinte: esse objeto pode ser chamado de qualquer thread do cliente, mas todos os retornos de chamada desse objeto para o cliente serão feitos somente no apartamento em que o objeto recebeu o ponteiro de interface para o objeto de retorno de chamada. O COM permite que esse objeto seja criado diretamente em um STA, bem como em um MTA do processo de cliente.

    Como qualquer apartamento que cria esse objeto sempre obtém um ponteiro direto em vez de um ponteiro proxy, ThreadingModel "Both" os objetos fornecem melhorias de desempenho sobre ThreadingModel "Free" objetos quando carregados em um STA.

    Como um ThreadingModel "Both" objeto também foi projetado para acesso ao MTA (ele é thread-safe internamente), ele pode acelerar o desempenho agregando-se com o marshaler fornecido por CoCreateFreeThreadedMarshaler. Esse objeto fornecido pelo sistema é agregado em todos os objetos de chamada e marshals personalizados direcionam ponteiros para o objeto em todos os apartamentos no processo. Os clientes em qualquer apartamento, seja um STA ou MTA, podem acessar o objeto diretamente em vez de por meio de um proxy. Por exemplo, um cliente de modelo STA cria o objeto in-proc no STA1 e empacota o objeto para STA2. Se o objeto não se agregar com o marshaler de thread livre, o STA2 obterá acesso ao objeto por meio de um proxy. Se isso acontecer, o marshaler de thread livre fornece STA2 com um ponteiro direto para o objeto

    Observação

    É necessário ter cuidado ao agregar com o marshaler de thread livre. Como exemplo, suponha que um objeto marcado como ThreadingModel "Both" (e também agregando com o marshaler de thread livre) tenha um membro de dados que é um ponteiro de interface para outro objeto cujo ThreadingModel é "Apartment". Em seguida, suponha que um STA crie o primeiro objeto e, durante a criação, o primeiro objeto cria o segundo objeto. De acordo com as regras discutidas acima, o primeiro objeto agora está segurando um ponteiro direto para o segundo objeto. Agora suponha que o STA marshals o ponteiro de interface para o primeiro objeto para outro apartamento. Como o primeiro objeto é agregado com o marshaler de thread livre, um ponteiro direto para o primeiro objeto é dado ao segundo apartamento. Se o segundo apartamento chamar por meio desse ponteiro e, se essa chamada fizer com que o primeiro objeto chame pelo ponteiro da interface para o segundo objeto, ocorreu um erro, pois o segundo objeto não deve ser chamado diretamente do segundo apartamento. Se o primeiro objeto estiver segurando um ponteiro para um proxy para o segundo objeto em vez de um ponteiro direto, isso causará um erro diferente. Proxies do sistema também são objetos COM associados a um e apenas um apartamento. Eles mantêm o controle de seu apartamento a fim de evitar determinadas circularidades. Portanto, um objeto chamando em um proxy associado a um apartamento diferente do thread no qual o objeto está em execução receberá o RPC_E_WRONG_THREAD retornar do proxy e a chamada falhará.

Interoperabilidade do modelo de threading entre clientes e objetos em processo

Todas as combinações de interoperabilidade de modelo de threading são permitidas entre clientes e objetos em processo.

O COM permite que todos os clientes do modelo STA interoperem com objetos no proc de threading único criando e acessando o objeto no STA main do cliente e empacotando-o no STA do cliente chamado CoCreateInstance[Ex].

Se um MTA em um cliente criar um modelo STA no servidor proc, o COM criará um STA "host" no cliente. Este STA host cria o objeto e o ponteiro da interface é empacotado de volta para o MTA. Da mesma forma, quando um STA cria um servidor MTA no proc, o COM gira um MTA de host no qual o objeto é criado e empacotado de volta para o STA. A interoperabilidade entre o modelo de thread único e o modelo MTA é tratada da mesma forma porque o modelo de threading único é apenas um caso degenerado do modelo STA.

O código de marshaling deve ser fornecido para qualquer interface personalizada que um servidor no proc implemente se quiser dar suporte à interoperabilidade que exige COM para empacotar a interface entre apartamentos cliente. Confira a seção REFERÊNCIAS abaixo para obter mais informações.

Relação entre o modelo de threading e o objeto Class Factory retornado

Uma definição precisa de servidores in-proc sendo "carregados em" apartamentos é explicada nas duas etapas a seguir:

  1. Se a DLL que contém a classe de servidor no proc não tiver sido carregada anteriormente (mapeada no espaço de endereço do processo) pelo carregador do sistema operacional, essa operação será executada e o COM obterá o DLLGetClassObject endereço da função exportada pela DLL. Se a DLL tiver sido carregada anteriormente por um thread associado a qualquer apartamento, esse estágio será ignorado.

  2. O COM usa o thread (ou, no caso do MTA, um dos threads) associado ao apartamento "carregamento" para chamar a DllGetClassObject função exportada pela DLL solicitando o CLSID da classe necessária. O objeto factory retornado é então usado para criar instâncias de objetos da classe.

    A segunda etapa (a chamada de DllGetClassObject por COM) ocorre sempre que um cliente chama CoGetClassObject/CoCreateIntance[Ex], mesmo de dentro do mesmo apartamento. Em outras palavras, DllGetClassObject pode ser chamado muitas vezes por um thread associado ao mesmo apartamento; tudo depende de quantos clientes nesse apartamento estão tentando obter acesso a um objeto de fábrica de classe para essa classe.

Os autores de implementações de classe e, em última instância, o autor do pacote DLL de um determinado conjunto de classes têm total liberdade para decidir qual objeto factory retornará em resposta à chamada de DllGetClassObject função. O autor do pacote DLL tem a palavra final porque o código "por trás" do único ponto de entrada em toda DllGetClassObject() a DLL é o que tem o primeiro e potencialmente final direito de decidir o que fazer. As três possibilidades típicas são:

  1. DllGetClassObject retorna um objeto de fábrica de classe exclusivo por thread de chamada (o que significa um objeto de fábrica de classe por STA e potencialmente várias fábricas de classe dentro do MTA).

  2. DllGetClassObject sempre retorna o mesmo objeto de fábrica de classe, independentemente da identidade do thread de chamada ou do tipo de apartamento associado ao thread de chamada.

  3. DllGetClassObject retorna um objeto de fábrica de classe exclusivo por apartamento de chamada (um por apartamento em STA e MTA).

Há outras possibilidades para a relação entre chamadas para DllGetClassObject e o objeto factory de classe retornado (como um novo objeto de fábrica de classe por chamada para DllGetClassObject) mas atualmente não parecem ser úteis.

Resumo de modelos de threading de cliente e objeto para servidores In-Proc

A tabela a seguir resume a interação entre diferentes modelos de threading quando um thread do cliente chama CoGetClassObject pela primeira vez em uma classe que é implementada como um servidor no proc.

Tipos de clientes/threads:

  • o cliente está em execução em um thread associado ao STA "main" (primeiro thread a ser chamado CoInitialize ou CoInitializeEx com COINIT_APARTMENTTHREADED sinalizador)- chame esse STA0 (também chamado de modelo de threading único).
  • o cliente está em execução em um thread associado em qualquer outro STA [ASCII 150] chame esta STA*.
  • o cliente está em execução em um thread associado ao no MTA.

Tipos de servidores DLL:

  • O servidor não tem chave ThreadingModel — chame isso de "Nenhum".
  • O servidor está marcado como "Apartamento"-- chame isso de "Apt".
  • O servidor está marcado como "Gratuito".
  • O servidor está marcado como "Ambos".

Ao ler a tabela abaixo, tenha em mente a definição feita acima de "carregar" um servidor em um apartamento.

Client         Server                 Result
STA0           None                   Direct access; server loaded into STA0  
STA*           None                   Proxy access; server loaded into STA0.  
MTA            None                   Proxy access; server loaded into STA0; STA0 created automatically by COM if necessary;  
STA0           Apt                    Direct access; server loaded into STA0  
STA*           Apt                    Direct access; server loaded into STA*  
MTA            Apt                    Proxy access; server loaded into an STA created automatically by COM.
STA0           Free                   Proxy access; server is loaded into MTA MTA created automatically by COM if necessary.
STA*           Free                   Same as STA0->Free
MTA            Free                   Direct access
STA0           Both                   Direct access; server loaded into STA0
STA*           Both                   Direct access; server loaded into STA*
MTA            Both                   Direct access; server loaded into the MTA

Referências

Documentação do SDK na CoRegisterMessageFilter() interface e IMessageFilter .