Windows com C++

O Modelo de Aplicativo de Tempo de Execução do Windows

Kenny Kerr

Nossas vidas estão repletas de abstrações. Como desenvolvedores, muitas vezes enfrentamos dificuldades quando usamos abstrações sem entendê-las como elas são. Abstrações são, por vezes, quebradas e não conseguem esconder completamente a complexidade subjacente. Não me interpretem mal, abstrações são ótimas. Elas ajudam os usuários e eles ajudam os desenvolvedores, mas você fará muito bem se aprofundando nas abstrações das quais depende regularmente para entender como elas funcionam. Além disso, as bibliotecas que reconhecem essa realidade são geralmente mais bem-sucedidas do que as que não reconhecem, em parte porque permitem que você contorne a abstração se e quando sentir necessidade.

O Tempo de Execução do Windows (WinRT) é uma abstração dessas, e na coluna deste mês vou ilustrar isso examinando o modelo de aplicativo básico do WinRT. Ele gira em torno da classe CoreWindow, da qual há uma instância que mora em cada aplicativo "moderno" da Windows Store e do Windows Phone. No entanto, relativamente poucos desenvolvedores nem sabem que ela existe, e muito menos como funciona. Talvez essa seja uma prova do sucesso da abstração.

Desde que a API do Windows 8 foi anunciada pela primeira vez em 2011, muito tem se falado e escrito sobre as várias projeções de linguagem que oferecem uma abstração no Tempo de Execução do Windows. No entanto, a melhor maneira de entender o Tempo de Execução do Windows é evitar as várias projeções de linguagem, incluindo C++/CX, e a abraçar o C++ padrão e o clássico COM. Somente o C++ permite puxar a cortina de lado e ver o que realmente acontece (tecnicamente, a linguagem C faz o mesmo, mas isso seria desnecessariamente trabalhoso). Você ainda pode optar por usar outra projeção de linguagem (com sorte, C++/CX), e provavelmente usou, mas pelo menos você terá uma compreensão muito mais clara do que realmente acontece.

Para começar, abra o Visual Studio 2012 e crie um novo projeto do Visual C++ para um aplicativo da Windows Store ou do Windows Phone. Não importa o modelo usado. Uma vez que carregado, vá até o Gerenciador de Soluções e exclua tudo que não for essencial. Se você escolheu um modelo baseado em XAML, exclua todos os arquivos XAML. Você também pode excluir todos os arquivos de origem C++. Você pode manter o cabeçalho pré-compilado, mas não se esqueça de apagar tudo que está dentro dele. Tudo que deve permanecer são os ativos do pacote necessários para implantar o aplicativo, imagens, certificado e manifesto XML.

Em seguida, abra as páginas de propriedades do projeto e selecione as propriedades do compilador — o nó C/C++ na árvore à esquerda. Encontre a linha para a opção de compilador /ZW que se chama Consume Windows Runtime Extension e selecione No para desabilitar as extensões de linguagem C++/CX. Dessa forma, você pode ter certeza de não há nada de misterioso acontecendo além dos maravilhosos mistérios do compilador C++ padrão. Enquanto você estiver nessa parte, também poderá definir o nível de aviso do compilador como /W4.

Se você tentar compilar o projeto, deverá ser saudado com um erro do vinculador informando que a função de ponto de entrada WinMain do projeto não pode ser encontrada. Adicione um novo arquivo de origem C++ ao projeto, e a primeira coisa a ser feita é adicionar a função WinMain que faltava:

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
}

Como você pode ver, esta é a função WinMain antiquíssima para um aplicativo do Windows baseado nas bibliotecas do CRT (C Runtime). Claro que HINSTANCE e PWSTR não são tipos C++ fundamentais e, portanto, você deverá incluir o cabeçalho do Windows:

#include <windows.h>

Se você manteve o cabeçalho pré-compilado do projeto, pode incluí-lo aqui. Também usei ComPtr da Biblioteca de Modelos C++ do Tempo de Execução do Windows (WRL); Portanto, agora seria um bom momento para incluem isso também:

#include <wrl.h>

Falarei sobre o WRL em mais detalhes nas próxima colunas. Por agora, usarei o modelo de classe ComPtr apenas para manter um ponteiro da interface COM. Tudo que você precisa ter em mente nesta fase é que o ComPtr do WRL é simplesmente um ponteiro inteligente da interface COM. Embora isso forneça alguns recursos que são exclusivos do Tempo de Execução do Windows, não vou usá-los na coluna deste mês. Você pode usar o CComPtr do ATL (Active Template Library) com a mesma facilidade, ou qualquer ponteiro inteligente da interface COM de sua escolha. O ComPtr do WRL é definido no namespace Microsoft::WRL:

using namespace Microsoft::WRL;

Também vou usar uma macro ASSERT, bem como a função HR para a manipulação de erros. Falei sobre isso anteriormente, então não me repetirei aqui. Se você tiver alguma dúvida sobre essas etapas, confira minha coluna de maio de 2013, “Apresentando o Direct2D 1.1” (msdn.microsoft.com/magazine/dn198239).

Finalmente, para usar qualquer uma das funções WinRT mencionadas nesta coluna, você precisa fornecer ao vinculador o nome do arquivo .lib:

#pragma comment(lib, "RuntimeObject.lib")

A primeira coisa que o modelo de aplicativo espera é um MTA (multithreaded apartment). Isso mesmo — o modelo Apartment COM continua vivo. O Tempo de Execução do Windows fornece a função RoInitialize, que é um wrapper estreito em torno de CoInitializeEx:

HR(RoInitialize(RO_INIT_MULTITHREADED));

Apesar do fato de CoInitializeEx ser geralmente suficiente, sugiro que você use RoInitialize. Esta função permite fazer futuras melhorias no Tempo de Execução do Windows sem quebrar potencialmente o COM clássico. Isso é análogo ao OleInitialize, que também chama CoInitializeEx e depois outros. A questão é que não há nada de misterioso sobre o thread principal do aplicativo. A única coisa que pode ser um pouco surpreendente é que não é um STA (Single-Threaded Apartment). Não se preocupe, a janela do aplicativo ainda será executada dentro de um thread STA, mas o Tempo de Execução do Windows é que irá criá-la. Esse STA é, na verdade, um aplicativo STA (ASTA), que é ligeiramente diferente, mas falarei mais sobre isso depois.

 A próxima parte é um pouco complicada. O Tempo de Execução do Windows abandona o modelo tradicional de ativação COM que usa identificadores de classe baseados em GUID a favor de um modelo que ativa as classes com base em identificadores de classe textual. Os nomes textuais são baseados em nomes com escopo de namespace preenchidos pelo Java e pelo Microsoft .NET Framework, mas antes de desprezar e dizer adeus ao registro, tenha em mente que esses novos identificadores de classe ainda são armazenados no Registro. Tecnicamente, apenas tipos primários são registrados no Registro, ao passo que tipos de terceiros são registrados apenas em um manifesto por aplicativo. Essa mudança apresenta prós e contras. Um dos contras é que é ligeiramente mais difícil descrever o identificador de classe ao chamar uma função WinRT. O Tempo de Execução do Windows define um novo tipo de cadeia de caracteres remoto para substituir o tipo de cadeia de caracteres BSTR tradicional, e os identificadores de classe precisam ser fornecidos usando esse novo meio. O HSTRING, como é chamado, é muito menos propenso a erros do que o BSTR, principalmente porque é imutável. A maneira mais simples de criar um HSTRING é com a função WindowsCreateString:

wchar_t buffer[] = L"Poultry.Hatchery";
HSTRING string;
HR(WindowsCreateString(buffer,
                       _countof(buffer) - 1,
                       &string));

WindowsCreateString aloca memória suficiente para armazenar uma cópia da cadeia de caracteres de origem e um caractere nulo de terminação, e depois copia a cadeia de caracteres de origem nesse buffer. Para liberar o buffer de backup, você deve se lembrar de chamar WindowsDeleteString, a menos que a propriedade da cadeia de caracteres seja retornada a uma função de chamada:

HR(WindowsDeleteString(string));

Dado um HSTRING, você pode obter um ponteiro para o buffer de backup com a função WindowsGetStringRawBuffer:

wchar_t const * raw = WindowsGetStringRawBuffer(string, nullptr);

O segundo parâmetro opcional retorna o comprimento da cadeia de caracteres. O comprimento é armazenado com a cadeia de caracteres, poupando-lhe de ter que verificar a cadeia de caracteres para determinar seu comprimento. Antes de você se apressar e escrever uma classe wrapper em C++, é interessante notar que o compilador C++/CX não se incomoda com WindowsCreateString e WindowsDelete­String ao gerar o código para uma matriz de cadeia de caracteres literal ou const. Em vez disso, ele usa o que chamamos de cadeia de caracteres de passagem rápida, que evita a alocação de memória extra e a cópia que mencionei anteriormente. Isso também evita o risco de vazamento de memória. A função WindowsCreate­StringReference cria uma cadeia de caracteres de passagem rápida:

HSTRING_HEADER header;
HSTRING string;
HR(WindowsCreateStringReference(buffer,
                                _countof(buffer) - 1,
                                &header,
                                &string));

Essa função usa o HSTRING_HEADER fornecido pelo chamador para evitar uma alocação de heap. Nesse caso, o buffer de backup do HSTRING é a própria cadeia de caracteres de origem. Portanto, você deve garantir que a cadeia de caracteres de origem (e o cabeçalho) permaneça inalterada durante a vida útil do HSTRING. Essa abordagem obviamente não é útil quando você precisa retornar uma cadeia de caracteres para uma função de chamada, mas é digna de otimização quando você precisa passar uma cadeia de caracteres como entrada para outra função cuja vida útil seja incluída no escopo pela pilha (funções não assíncronas). O WRL também fornece wrappers para HSTRING e cadeias de caracteres de passagem rápida.

RoGetActivationFactory é apenas mais uma função desse tipo e é usada para obter a alocador de ativação ou interface estática para uma determinada classe. Isso é análogo à função COM CoGetClassObject. Dado que essa função é geralmente usada com uma matriz const gerado pelo compilador MIDL, faz sentido escrever um modelo de função simples para fornecer um wrapper de cadeia de caracteres de passagem rápida. A Figura 1 ilustra como seria o resultado.

Figura 1 Modelo de função GetActivationFactory

template <typename T, unsigned Count>
auto GetActivationFactory(WCHAR const (&classId)[Count]) -> ComPtr<T>
{
  HSTRING_HEADER header;
  HSTRING string;
  HR(WindowsCreateStringReference(classId,
                                  Count - 1,
                                  &header,
                                  &string));
  ComPtr<T> result;
  HR(RoGetActivationFactory(string,
    __uuidof(T),
    reinterpret_cast<void **>(result.GetAddressOf())));
  return result;
}

O modelo de função GetActivationFactory infere o comprimento da cadeia de caracteres automaticamente, eliminando um argumento de comprimento de buffer propenso a erros ou uma verificação dispendiosa do tempo de execução. Depois, ele prepara uma cadeia de caracteres de passagem rápida antes de chamar a função RoGetActivationFactory. Aqui, novamente, o modelo de função infere o identificador da interface e retorna com segurança o ponteiro da interface COM resultante encapsulado em um ComPtr do WRL.

Agora você pode usar esta função auxiliar para obter a interface ICoreApplication:

using namespace ABI::Windows::ApplicationModel::Core;
auto app = GetActivationFactory<ICoreApplication>(
  RuntimeClass_Windows_ApplicationModel_Core_CoreApplication);

A interface ICoreApplication é que faz tudo acontecer em seu aplicativo. Para usar essa interface COM, você precisará incluir o cabeçalho do modelo de aplicativo:

#include <Windows.ApplicationModel.Core.h>

Esse cabeçalho define ICoreApplication, dentro do namespace ABI::Windows::ApplicationModel::Core, bem como o identificador de classe textual de Core­Application. O único método de interface em que você realmente precisa pensar é o método Run.

Antes de eu continuar, é útil apreciar como o Tempo de Execução do Windows dá vida ao seu aplicativo. Como mencionei antes, o Tempo de Execução do Windows considera você apenas um convidado dentro de seu próprio processo. Isso é análogo ao como serviços do Windows funcionam há anos. No caso de um serviço do Windows, o Gerenciador de Controle de Serviços do Windows (SCM) inicia o serviço usando a função CreateProcess, ou uma das suas variantes. Depois, ele aguarda o processo chamar a função StartServiceCtrlDispatcher. Esta função estabelece uma conexão novamente com o SCM, pela qual o serviço e o SCM podem se comunicar. Se, por exemplo, o serviço não chamar StartService­CtrlDispatcher em tempo hábil, o SCM irá considerar que algo deu errado e encerrará o processo. A função StartServiceCtrl­Dispatcher retorna apenas quando o serviço é encerrado. Então, o SCM precisa criar um thread secundário para o serviço receber notificações de retorno de chamada. O serviço responde meramente a eventos e fica à mercê do SCM. Você verá que isso é muito semelhante ao modelo de aplicativo do WinRT.

O Tempo de Execução do Windows aguarda o processo obter a interface ICore­Application e chamar seu método Run. Como o SCM, se o processo não fizer isso em tempo hábil, o Tempo de Execução do Windows irá considerar que algo deu errado e encerrará o processo. Felizmente, se um depurador for anexado, o Tempo de Execução do Windows perceberá e desabilitará o tempo limite, ao contrário do SCM. Contudo, o modelo é o mesmo. O Tempo de Execução do Windows assume o comando e chama o aplicativo em um thread criado no tempo de execução quando ocorrem eventos. Obviamente, o Tempo de Execução do Windows é baseado em COM. Então, em vez de uma função de retorno de chamada (como é o caso do SCM), o Tempo de Execução do Windows — que usa o PLM (Gerenciador do Tempo de Vida de Processos) para isso — espera o aplicativo fornecer o método Run com uma interface COM que possa ser usada para chamar o aplicativo.

Seu aplicativo deve fornecer uma implementação do IFramework­ViewSource, que também vem do namespace ABI::Windows::ApplicationModel::Core, e o CoreApplication chamará seu método CreateView solitário depois de criar o thread de interface de usuário do aplicativo. IFrameworkViewSource realmente não tem CreateView como método. Ele deriva de IInspectable, a interface base do WinRT. IInspectable, por sua vez, deriva de IUnknown, interface base COM.

O WRL fornece amplo suporte para implementar classes COM, mas vou poupar isso para uma próxima coluna. No momento, quero destacar como o Tempo de Execução do Windows está bem-enraizado em COM, e qual é a melhor maneira de mostrar isso do que implementar IUnknown? Para o meu objetivo, é útil observar que a classe C++ que irá implementar IFrameworkViewSource — e alguns outros — tem a vida útil definida pela pilha. Essencialmente, a função WinMain do aplicativo se resume a isto:

HR(RoInitialize(RO_INIT_MULTITHREADED));
auto app = GetActivationFactory<ICoreApplication>( ...
SampleWindow window;
HR(app->Run(&window));

Tudo o que resta é escrever a classe SampleWindow para implementar IFrameworkViewSource corretamente. Embora CoreApplication não se importe onde eles serão implementados, no mínimo seu aplicativo precisará implementar não só IFrameworkViewSource, mas também as interfaces IFrameworkView e IActivatedEventHandler. Neste caso, a classe SampleWindow pode implementar todas elas:

struct SampleWindow :
  IFrameworkViewSource,
  IFrameworkView,
  IActivatedEventHandler
{};

A interface IFrameworkView também é definida no namespace ABI::Windows::ApplicationModel::Core, mas IActivatedEvent­Handler é um pouco mais difícil de apontar. Eu mesmo o defini desta forma:

using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::ApplicationModel::Activation;
typedef ITypedEventHandler<CoreApplicationView *, 
  IActivatedEventArgs *>
  IActivatedEventHandler;

Se você tiver alguma experiência em COM, pode considerar isso um tanto não ortodoxo — e com razão. Como já era de esperar, ITypedEventHandler é apenas um modelo de classe, e essa é uma maneira estranha de definir uma interface COM — o problema mais óbvio seria que você provavelmente não saberia que identificador de interface atribuir a ela. Felizmente, todas essas interfaces são geradas pelo compilador MIDL, que se encarrega de especializar cada uma, e é nessas especializações que ele anexa o GUID que representa o identificador da interface. O typedef anterior pode parecer complicado, mas define uma interface COM derivada diretamente de IUnknown e fornece um único método chamado Invoke.

Tenho alguns métodos de interface para implementar, então vamos começar. A primeira é IUnknown e o eficiente método QueryInterface. Não quero dedicar muito tempo às interfaces IUnknown e IInspectable aqui, pois falarei sobre elas em detalhes em uma próxima coluna. A Figura 2 fornece uma implementação simples de QueryInterface para uma classe baseada em pilha como esta.

Figura 2 O método SampleWindow QueryInterface

auto __stdcall QueryInterface(IID const & id,
                              void ** result) -> HRESULT
{
  ASSERT(result);
  if (id == __uuidof(IFrameworkViewSource) ||
      id == __uuidof(IInspectable) ||
      id == __uuidof(IUnknown))
  {
    *result = static_cast<IFrameworkViewSource *>(this);
  }
  else if (id == __uuidof(IFrameworkView))
  {
    *result = static_cast<IFrameworkView *>(this);
  }
  else if (id == __uuidof(IActivatedEventHandler))
  {
    *result = static_cast<IActivatedEventHandler *>(this);
  }
  else
  {
    *result = nullptr;
    return E_NOINTERFACE;
  }
  // static_cast<IUnknown *>(*result)->AddRef();
  return S_OK;
}

Vale a pena tomar nota de algumas coisas sobre essa implementação. Primeiro, o método afirma que seus argumentos são válidos. Uma implementação mais politicamente correta poderia retornar E_POINTER, mas presume-se que esses erros sejam bugs que possam ser resolvidos durante o desenvolvimento. Então, não há necessidade de desperdiçar ciclos extras no tempo de execução. Isso gera o melhor comportamento possível, causando imediatamente uma violação de acesso e um despejo de memória que é bastante fácil de analisar. Se você retornar E_POINTER, o chamador violado provavelmente só irá ignorá-lo. A melhor política é reprovar no início. Essa é, de fato, a posição assumida por muitas implementações, incluindo DirectX e Tempo de Execução do Windows. Implantar a QueryInterface corretamente envolve muita coisa. A especificação COM é bastante específica para que as classes COM forneçam sempre certas garantias de identidade de objeto de forma correta e consistente. Não se preocupe se a cadeia de instruções if parece intimidadora. Falarei sobre ela no momento certo.

O último ponto que vale a pena mencionar sobre essa implementação é que ela não se dá ao trabalho de chamar AddRef. Normalmente, QueryInterface deve chamar AddRef no ponteiro da interface IUnknown resultante antes de retornar. No entanto, como a classe SampleWindow reside na pilha, não é válida para a contagem de referência. Pelo mesmo motivo, implementar os métodos IUnknown AddRef e Release é simples:

auto __stdcall AddRef()  -> ULONG { return 2; }
auto __stdcall Release() -> ULONG { return 1; }

Os resultados desses métodos são apenas consultivos e, assim, você pode tirar proveito desse fato, e qualquer valor diferente de zero serve. Uma nota de advertência aqui: convém substituir os operadores novos e excluir para tornar explícito que a classe é projetada apenas para funcionar na pilha. Como alternativa, você pode simplesmente implementar a contagem de referência, só por precaução.

Em seguida, preciso implementar IInspectable, mas como isso não será usado neste aplicativo simples, vou trapacear e deixar seus métodos não implementados, conforme mostrado na Figura 3. Essa não é uma implementação em conformidade e seu funcionamento não é garantido. Novamente, falarei sobre IInspectable em uma próxima coluna, mas isto é suficiente para colocar em funcionamento as interfaces derivadas de SampleWindow IInspectable.

Figura 3 Os métodos SampleWindow IInspectable

auto __stdcall GetIids(ULONG *,
                       IID **) -> HRESULT
{
  return E_NOTIMPL;
}
auto __stdcall GetRuntimeClassName(HSTRING *) -> HRESULT
{
  return E_NOTIMPL;
}
auto __stdcall GetTrustLevel(TrustLevel *) -> HRESULT
{
  return E_NOTIMPL;
}

Em seguida, preciso implementar IFrameworkViewSource e seu método CreateView. Como a classe SampleWindow também está implementando IFrameworkView, a implementação é simples. Novamente, observe que normalmente seria necessário chamar AddRef no ponteiro da interface derivada de IUnknown resultante antes de retornar. Convém chamar AddRef no corpo desta função, só por precaução:

auto __stdcall CreateView(IFrameworkView ** result) -> HRESULT
{
  ASSERT(result);
  *result = this;
  // (*result)->AddRef();
  return S_OK;
}

A interface IFrameworkView é onde as coisas finalmente ficam interessantes para o aplicativo. Depois de chamar CreateView para recuperar o ponteiro da interface do aplicativo, o Tempo de Execução do Windows chama a maioria de seus métodos em sucessão rápida. É importante responder a essas chamadas rapidamente, pois todas elas contam como o tempo gasto pelo usuário na espera da inicialização do aplicativo. A primeira chama-se Initialize, e é onde o aplicativo deve se registrar para o evento Activated. O evento Activated sinaliza que o aplicativo foi ativado, mas depende do aplicativo ativar sua CoreWindow. O método Initialize é bastante simples:

auto __stdcall Initialize(ICoreApplicationView * view) -> HRESULT
{
  EventRegistrationToken token;
  HR(view->add_Activated(this, &token));
  return S_OK;
}

O método SetWindow é chamado, fornecendo ao aplicativo a implementação real de ICoreWindow. Uma ICoreWindow apenas modela um HWND de área de trabalho regular dentro do Tempo de Execução do Windows. Ao contrário das interfaces de modelo de aplicativo anteriores, a ICoreWindow é definida no namespace ABI::Windows::UI::Core. Dentro do método SetWindow, você deve apenas fazer uma cópia do ponteiro da interface, pois precisará dela em breve:

using namespace ABI::Windows::UI::Core;
ComPtr<ICoreWindow> m_window;
auto __stdcall SetWindow(ICoreWindow * window) -> HRESULT
{
  m_window = window;
  return S_OK;
}

O método Load é o próximo e é onde você deve inserir todo e qualquer código para preparar seu aplicativo para apresentação inicial:

auto __stdcall Load(HSTRING) -> HRESULT
{
  return S_OK;
}

No mínimo, você deve se registrar para eventos relacionados a alterações de tamanho e visibilidade da janela, bem como as alterações de dimensionamento de DPI. Você também pode aproveitar a oportunidade para criar os vários objetos alocadores DirectX, carregar os recursos independentes de dispositivo e assim por diante. O motivo pelo qual esse é um bom lugar para tudo isso é que é nesse ponto que o usuário vê a tela inicial do aplicativo.

Quando o método Load retorna, o Tempo de Execução do Windows presume que o aplicativo está pronto para ser ativado e dispara o evento Activated, do qual cuidarei implementando o método IActivatedEventHandler Invoke, da seguinte forma:

auto __stdcall Invoke(ICoreApplicationView *,
                      IActivatedEventArgs *) -> HRESULT
{
  HR(m_window->Activate());
  return S_OK;
}

Com a janela ativada, o aplicativo está finalmente pronto para execução:

auto __stdcall Run() -> HRESULT
{
  ComPtr<ICoreDispatcher> dispatcher;
  HR(m_window->get_Dispatcher(dispatcher.GetAddressOf()));
  HR(dispatcher->ProcessEvents(CoreProcessEventsOption_ProcessUntilQuit));
  return S_OK;
}

Há muitas maneiras de implementar isso. Aqui só estou recuperando o interface ICoreDispatcher da janela, que representa a bomba de mensagens para a janela. Por fim, há o método Uninitialize, que poderia ser chamado ocasionalmente, mas é inútil em outros casos e pode ser ignorado sem problemas:

auto __stdcall Uninitialize() -> HRESULT
{
  return S_OK;
}

E isso é tudo. Agora, você pode compilar e executar o aplicativo. É claro que você não está criando nada aqui, na verdade. Você pode pegar uma cópia do dx.h em dx.codeplex.com e começar a adicionar códigos de renderização do Direct2D (leia minha coluna de junho de 2013, “Uma biblioteca C++ moderna para programação no DirectX”, em msdn.microsoft.com/magazine/dn201741 para saber mais sobre isso), ou aguarde minha próxima coluna, onde mostrarei a melhor maneira de integrar o Direct2D ao modelo de aplicativo básico do WinRT.

Kenny Kerr é programador de computador, autor da Pluralsight e Microsoft MVP que mora no Canadá. Ele mantém um blog em kennykerr.ca e pode ser seguido no Twitter em twitter.com/kennykerr.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: James McNellis (Microsoft)
James McNellis é um aficionado do C++ e desenvolvedor de software na equipe do Visual C++ na Microsoft, onde ele cria formidáveis bibliotecas do C++ e mantém as bibliotecas do CRT (C Runtime). Ele usa o tweet @JamesMcNellis e pode ser encontrado online também em http://jamesmcnellis.com/.