Por que o Visual Studio gera executáveis mutantes
Wanderley Caloni, 2015-01-11: bugs, visual studio, erros, código

Esse é um post antigo que encontrei no meio dos meus emails de 2006, mas que contém uma boa dica para quem já entendeu o passo-a-passo da compilação, mas ainda tem sérios problemas quando os projetos ficam gigantes.

Essa é a segunda vez que encontro esse mesmo problema. Como acredito que outras almas podem estar sofrendo do mesmo mal, coloco aqui uma breve descrição de como o VC8 faz para gerar um executável que, mesmo não dependendo das DLLs de runtime, não são executados em sistemas que suportam a interpretação do “.manifest”. De canja, um pequeno programa que exibe a lista dos programas instalados no sistema.

Primeiro, precisamos de um solution que contenha um projeto console e uma LIB. O projeto console deve usar a LIB para fazer alguma coisa. No exemplo abaixo, estarei listando os programas instalados no Windows (os mostrados no painel de controle através da opção “Adicionar/remover programas”.

/** library.h
*/
#pragma once
#include <string>
#include <vector>

typedef std::vector<std::string> InstalledSoftwareList;
int getInstalledSoftware(InstalledSoftwareList&);



/** library.cpp
*/
#include "library.h"
#include <windows.h> // aqui precisamos do windows para as funções de registro
#include <tchar.h> // suporte a unicode condicional

#define SW_ROOT_KEY "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
#define SW_DISPLAY_NAME "DisplayName"

/** Retorna o número de elementos em um array. */
template<typename T, size_t Sz>
DWORD SizeofArray(const T(&arr)[Sz]) { return Sz; }


/** Retorna lista com descrição de cada programa instalado no sistema. */
int getInstalledSoftware(InstalledSoftwareList& installedSoftware)
{
   HKEY swRoot = NULL;
   DWORD err = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T(SW_ROOT_KEY), 0, KEY_READ, &swRoot);

   if( err == ERROR_SUCCESS )
   {
      DWORD swIndex = 0;
      TCHAR swKeyName[MAX_PATH] = _T("");

      // para cada chave dentro da raiz de programas instalados
      while( (err = RegEnumKey(swRoot, swIndex++, swKeyName, SizeofArray(swKeyName))) 
         == ERROR_SUCCESS )
      {
         HKEY swCurrent = NULL;
         err = RegOpenKeyEx(swRoot, swKeyName, 0, KEY_READ, &swCurrent);

         if( err == ERROR_SUCCESS )
         {
            CHAR swDisplay[MAX_PATH] = ""; // vamos obter a string já em mb
            DWORD swDisplaySz = SizeofArray(swDisplay);

            if( (err = RegQueryValueExA(swCurrent, SW_DISPLAY_NAME, 0, NULL, 
               reinterpret_cast<PBYTE>(swDisplay), &swDisplaySz)) == ERROR_SUCCESS )
            {
               installedSoftware.push_back(swDisplay);
            }

            RegCloseKey(swCurrent);
         }
      }

      // se não tem mais itens, então não é um erro
      if( err == ERROR_NO_MORE_ITEMS )
         err = ERROR_SUCCESS;
      RegCloseKey(swRoot);
   }
   return int(err);
}


/** console.cpp
*/
#include "../library/library.h" // include da nossa lib
#include <algorithm>
#include <iostream>

using namespace std;


int main()
{
   int ret;
   InstalledSoftwareList swList;

   cout << "MSVC Mutant - v. beta\n"
      << "by Wanderley Caloni (www.caloni.com.br)\n\n";


   // obtém a lista de programas instalados e exibe na tela
   ret = getInstalledSoftware(swList);

   if( ret == 0 )
   {
      cout << "Programs installed on your system\n"
         << "=================================\n";
      copy(swList.begin(), swList.end(), ostream_iterator<string>(cout, "\n"));

   }
   else
      cout << "Error " << ret << " trying to list installed programs.\n";

   return ret;
}

Observação importante: para ignorar todas as estripulias da versão Debug, todos os testes foram compilados em Release.

Primeiramente, modifico a configuração padrão dos dois projetos para não depender da DLL de runtime do VC. Isso está em Project, Properties, C/C++, Code Generation, Runtime Library. Depois executo em uma máquina virtual sem as runtimes do VC8 instaladas:

MSVC Mutant - v. beta
by Wanderley Caloni (www.caloni.com.br)

Programs installed on your system
=================================
Windows XP Service Pack 2
WebFldrs XP
VMware Tools

Perfeito. Exatamente o que eu queria: um executável console que não dependesse de DLL nenhuma exceto as que já estão instaladas em um Windows ordinário.

Agora, vamos imaginar que esse é um daqueles projetos enormes de 5 * 10 ^ 42 de linhas (obs: dramatização) e que meu aplicativo console está linkado com cerca de 3 * 10 ^ 666 de LIBs. E uma delas (a library do exemplo) está com a configuração original, ou seja, com a dependência da DLL de runtime. E ela usa a STL. Provavelmente o aplicativo console não irá compilar, mas isso não é problema, pois estamos acostumados a colocar a msvcrt.lib na lista de LIBs ignoradas, pois em muitos outros casos (que não vale a pena discutir aqui) esse workaround é válido. E tudo volta a funcionar. Quer dizer, linkar:

O sistema no pode executar o programa especificado.

Tudo bem, meu executável não é mutante ainda. Mas agora vamos trocar a chamada da nossa função que usa STL por uma função que não usa:

/** library.h
*/
int doesNothing();


/** library.cpp
*/

/** Essa função não faz nada. Quer dizer, ela retorna 0. Mas é só isso. */
int doesNothing()
{
   return 0;
}


/** console.cpp
*/
#include "../library/library.h" // include da nossa lib

int main()
{
   int ret;

   // não faz nada. bom, chama uma função. mas isso é quase nada.
   ret = doesNothing();

   return ret;
}
Linking
=======
library.lib(library.obj) : warning LNK4049: locally 
 defined symbol __invalid_parameter_noinfo imported

Running
=======
O sistema no pode executar o programa especificado.

Depends
=======
Error: The Side-by-Side configuration information in "blablabla\CONSOLE.EXE" 
 contains errors.
Falha na inicialização do aplicativo devido a configuração incorreta.
A reinstalação do aplicativo pode resolver o problema (14001).

Agora sim, a mutação fez efeito! Temos um aplicativo que não depende da DLL de runtime, mas que no meio das n LIBs que ele utiliza existe uma configurada com a dependência. Ignorando a msvcrt.lib e um warning na compilação encontramos uma mensagem de erro um tanto exdrúxula.

Até agora, a maneira que eu tenho utilizado para rastrear esse problema é não ignorar a msvcrt e ir tirando as dependências das LIBs pouco a pouco, até que ocorra o erro de símbolo duplicado. Algo assim:

MSVCRT.lib(ti_inst.obj) : error LNK2005: "private: __thiscall 
 type_info::type_info(class type_info const &)" (??0type_info@@AAE@ABV0@@Z) 
 already defined in LIBCMT.lib(typinfo.obj)
MSVCRT.lib(ti_inst.obj) : error LNK2005: "private: class type_info & __thiscall 
 type_info::operator=(class type_info const &)" (??4type_info@@AAEAAV0@ABV0@@Z) 
 already defined in LIBCMT.lib(typinfo.obj)
LINK : warning LNK4098: defaultlib 'MSVCRT' conflicts with use of other libs; 
 use /NODEFAULTLIB:library
Blablabla\console.exe : fatal error LNK1169: one or more multiply defined symbols found

Se você tiver realmente 3 * 10 ^ 666 de LIBs, boa sorte =).