Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Este estudo de caso de portabilidade foi projetado para dar uma ideia de como é um projeto de portabilidade típico, os tipos de problemas que você pode encontrar e algumas dicas e truques gerais para resolver problemas de portabilidade. Não pretende ser um guia definitivo para a portabilidade, uma vez que a experiência de portabilidade de um projeto depende muito das especificidades do código.
Espião ++
Spy++ é uma ferramenta de diagnóstico GUI amplamente utilizada para a área de trabalho do Windows que fornece todos os tipos de informações sobre os elementos da interface do usuário na área de trabalho do Windows. Ele mostra a hierarquia completa de janelas e fornece acesso a metadados sobre cada janela e controle. Esta aplicação útil tem sido incluída no Visual Studio por muitos anos. Encontramos uma versão antiga dele que foi compilada pela última vez no Visual C++ 6.0 e a portamos para o Visual Studio 2015. A experiência para Visual Studio 2017 ou Visual Studio 2019 deve ser quase idêntica.
Consideramos esse caso típico para portar aplicativos da área de trabalho do Windows que usam MFC e a API do Win32, especialmente para projetos antigos que não foram atualizados com cada versão do Visual C++ desde o Visual C++ 6.0.
Passo 1. Convertendo o arquivo de projeto
O arquivo de projeto, dois arquivos .dsw antigos do Visual C++ 6.0, convertido facilmente sem problemas que exigem mais atenção. Um projeto é o aplicativo Spy++. O outro é SpyHk, escrito em C, uma DLL de suporte. Projetos mais complexos podem não ser atualizados tão facilmente, como discutido aqui.
Depois de atualizar os dois projetos, nossa solução ficou assim:
Temos dois projetos, um com um grande número de arquivos C++ e outro com uma DLL escrita em C.
Passo 2. Problemas com o arquivo de cabeçalho
Ao criar um projeto recém-convertido, uma das primeiras coisas que você encontrará com frequência é que os arquivos de cabeçalho que seu projeto usa não são encontrados.
Um dos arquivos que não puderam ser encontrados no Spy++ foi verstamp.h. A partir de uma pesquisa na Internet, determinamos que isso veio de um DAO SDK, uma tecnologia de dados obsoleta. Queríamos descobrir quais símbolos estavam sendo usados a partir desse arquivo de cabeçalho, para ver se esse arquivo era realmente necessário ou se esses símbolos eram definidos em outro lugar, então comentamos a declaração do arquivo de cabeçalho e recompilamos. Acontece que há apenas um símbolo que é necessário, VER_FILEFLAGSMASK.
1>C:\Program Files (x86)\Windows Kits\8.1\Include\shared\common.ver(212): error RC2104: undefined keyword or key name: VER_FILEFLAGSMASK
A maneira mais fácil de encontrar um símbolo nos arquivos de inclusão disponíveis é usar Localizar em arquivos (Ctrl+Shift+F) e especificar diretórios de inclusão do Visual C++. Encontrámo-lo em ntverp.h. Substituímos o verstamp.h include por ntverp.h e este erro desapareceu.
Passo 3. Configuração do Ficheiro de Saída do Linker
Projetos mais antigos às vezes têm arquivos colocados em locais não convencionais que podem causar problemas após a atualização. Nesse caso, temos que adicionar $(SolutionDir) ao caminho Include nas propriedades do projeto para garantir que o Visual Studio possa encontrar alguns arquivos de cabeçalho que são colocados lá, em vez de em uma das pastas do projeto.
MSBuild reclama que a propriedade Link.OutputFile não corresponde aos valores TargetPath e TargetName , emitindo MSB8012.
warning MSB8012: TargetPath(...\spyxx\spyxxhk\.\..\Debug\SpyxxHk.dll) does not match the Linker's OutputFile property value (...\spyxx\Debug\SpyHk55.dll). This may cause your project to build incorrectly. To correct this, please make sure that $(OutDir), $(TargetName) and $(TargetExt) property values match the value specified in %(Link.OutputFile).warning MSB8012: TargetName(SpyxxHk) does not match the Linker's OutputFile property value (SpyHk55). This may cause your project to build incorrectly. To correct this, please make sure that $(OutDir), $(TargetName) and $(TargetExt) property values match the value specified in %(Link.OutputFile).
Link.OutputFile é a saída de compilação (EXE, DLL, por exemplo), e normalmente é construído a partir de $(TargetDir)$(TargetName)$(TargetExt), dando o caminho, nome do arquivo e extensão. Este é um erro comum ao migrar projetos da antiga ferramenta de compilação do Visual C++ (vcbuild.exe) para a nova ferramenta de compilação (MSBuild.exe). Como a alteração da ferramenta de compilação ocorreu no Visual Studio 2010, você pode encontrar esse problema sempre que migrar um projeto anterior a 2010 para uma versão 2010 ou posterior. O problema básico é que o assistente de migração de projeto não atualiza o valor Link.OutputFile , pois nem sempre é possível determinar qual deve ser seu valor com base nas outras configurações do projeto. Portanto, você geralmente tem que configurá-lo manualmente. Para obter mais detalhes, consulte esta postagem no blog do Visual C++.
Nesse caso, a propriedade Link.OutputFile no projeto convertido foi definida como .\Debug\Spyxx.exe and .\Release\Spyxx.exe para o projeto Spy++, dependendo da configuração. A melhor aposta é simplesmente substituir esses valores codificados por $(TargetDir)$(TargetName)$(TargetExt) para todas as configurações. Se isso não funcionar, você pode personalizar a partir daí ou alterar as propriedades na seção Geral onde esses valores são definidos (as propriedades são Diretório de saída, Nome do destino e Extensão de destino. Lembre-se de que, se a propriedade que você está visualizando usa macros, você pode escolher Editar na lista suspensa para exibir uma caixa de diálogo que mostra a cadeia de caracteres final com as substituições de macro feitas. Você pode visualizar todas as macros disponíveis e seus valores atuais escolhendo o botão Macros .
Passo 4. Atualizando a versão de destino do Windows
O próximo erro indica que a versão WINVER não é mais suportada no MFC. WINVER para Windows XP é 0x0501.
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxv_w32.h(40): fatal error C1189: #error: MFC does not support WINVER less than 0x0501. Please change the definition of WINVER in your project properties or precompiled header.
O Windows XP não é mais suportado pela Microsoft, portanto, mesmo que o direcionamento seja permitido no Visual Studio, você deve eliminar gradualmente o suporte para ele em seus aplicativos e incentivar seus usuários a adotarem novas versões do Windows.
Para se livrar do erro, defina WINVER atualizando a configuração Propriedades do projeto para a versão mais baixa do Windows que você deseja segmentar no momento. Encontre uma tabela de valores para várias versões do Windows aqui.
O arquivo stdafx.h continha algumas dessas definições de macro.
#define WINVER 0x0500 // these defines are set so that we get the
#define _WIN32_WINNT 0x0500 // maximum set of message/flag definitions,
#define _WIN32_IE 0x0400 // from both winuser.h and commctrl.h.
Vamos definir WINVER para o Windows 7. É mais fácil ler o código mais tarde se você usar a macro para Windows 7 (_WIN32_WINNT_WIN7), em vez do valor em si (0x0601).
#define WINVER _WINNT_WIN32_WIN7 // Minimum targeted Windows version is Windows 7
Passo 5. Erros do vinculador
Com essas alterações, o projeto SpyHk (DLL) é compilado, mas produz um erro de vinculação.
LINK : warning LNK4216: Exported entry point _DLLEntryPoint@12
O ponto de entrada para uma DLL não deve ser exportado. O ponto de entrada destina-se apenas a ser chamado pelo carregador quando a DLL é carregada pela primeira vez na memória, portanto, não deve estar na tabela de exportação, que é para outros chamadores. Só precisamos de nos certificar de que não tem a diretiva __declspec(dllexport) anexada a ela. Em spyxxhk.c, temos de o remover de dois locais: a declaração e a definição de DLLEntryPoint. Nunca fez sentido usar esta diretiva, mas as versões anteriores do linker e do compilador não a sinalizavam como problema. As versões mais recentes do vinculador dão um aviso.
// deleted __declspec(dllexport)
BOOL WINAPI DLLEntryPoint(HINSTANCE hinstDLL,DWORD fdwReason, LPVOID lpvReserved);
O projeto C DLL, SpyHK.dll, agora cria e vincula sem erro.
Passo 6. Arquivos de cabeçalho mais desatualizados
Neste ponto, começamos a trabalhar no principal projeto executável, Spyxx.
Alguns outros arquivos de inclusão não puderam ser encontrados: ctl3d.h e penwin.h. Embora possa ser útil pesquisar na Internet para tentar identificar o que incluiu o cabeçalho, às vezes as informações não são tão úteis. Descobrimos que ctl3d.h fazia parte do Exchange Development Kit e fornecia suporte para um determinado estilo de controles no Windows 95, e penwin.h se relaciona com Window Pen Computing, uma API obsoleta. Neste caso, simplesmente comentamos a #include linha e lidamos com os símbolos indefinidos como fizemos com verstamp.h. Tudo o que se relaciona com Controles 3D ou Pen Computing foi removido do projeto.
Dado um projeto com muitos erros de compilação que você está eliminando gradualmente, não é realista encontrar todos os usos de uma API desatualizada imediatamente quando você remove a #include diretiva. Não detetamos isso imediatamente, mas num momento posterior nos deparámos com um erro que WM_DLGBORDER estava indefinido. Na verdade, é apenas um dos muitos símbolos indefinidos que vêm de ctl3d.h. Depois de determinarmos que ele está relacionado a uma API desatualizada, removemos todas as referências em código a ela.
Passo 7. Atualizando o código iostreams antigo
O próximo erro é comum com o código C++ antigo que usa iostreams.
mstream.h(40): fatal error C1083: Cannot open include file: 'iostream.h': No such file or directory
O problema é que a antiga biblioteca iostreams foi removida e substituída. Temos de substituir os antigos iostreams pelas normas mais recentes.
#include <iostream.h>
#include <strstrea.h>
#include <iomanip.h>
Estas são as atualizações incluídas:
#include <iostream>
#include <sstream>
#include <iomanip>
Com esta mudança, temos problemas com ostrstream, que já não é utilizado. O substituto apropriado é o 'ostringstream'. Tentamos adicionar um typedef for ostrstream para evitar modificar muito o código, pelo menos como um começo.
typedef std::basic_ostringstream<TCHAR> ostrstream;
Atualmente, o projeto é construído usando MBCS (Multi-byte Character set), por isso char é o tipo de dados de caracteres apropriado. No entanto, para permitir uma atualização mais fácil do código para UTF-16 Unicode, atualizamos isso para TCHAR, que resolve para char ou wchar_t dependendo se a propriedade Character set nas configurações do projeto está definida como MBCS ou Unicode.
Algumas outras partes do código precisam ser atualizadas. Substituímos a classe ios base por ios_base, e substituímos ostream is por basic_ostream<T>. Acrescentamos dois novos typedefs, e esta seção compila.
typedef std::basic_ostream<TCHAR> ostream;
typedef ios_base ios;
Usar esses typedefs é apenas uma solução temporária. Para uma solução mais permanente, poderíamos atualizar cada referência à API renomeada ou desatualizada.
Aqui está o próximo erro.
error C2039: 'freeze': is not a member of 'std::basic_stringbuf<char,std::char_traits<char>,std::allocator<char>>'
A próxima questão é que basic_stringbuf não tem um freeze método. O freeze método é usado para evitar um vazamento de memória no antigo ostream. Não precisamos disso agora que estamos usando o novo ostringstream. Podemos eliminar a chamada para freeze.
//rdbuf()->freeze(0);
Os dois erros seguintes ocorreram em linhas adjacentes. O primeiro reclama do uso de ends, que é o manipulador de E/S da antiga biblioteca iostream que adiciona um terminador nulo a uma string. O segundo desses erros explica que a saída do método str não pode ser atribuída a um ponteiro não-const.
// Null terminate the string in the buffer and
// get a pointer to it.
//
*this << ends;
LPSTR psz = str();
2>mstream.cpp(167): error C2065: 'ends': undeclared identifier2>mstream.cpp(168): error C2440: 'initializing': cannot convert from 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>' to 'LPSTR'
Com a nova biblioteca de fluxo, ends não é necessário, uma vez que a string é sempre terminada em nulo, permitindo que essa linha seja removida. Para a segunda questão, o problema é que agora str() não retorna um ponteiro para uma matriz de caracteres de uma string; retorna o tipo std::string. A solução para o segundo é alterar o tipo para LPCSTR e usar o c_str() método para solicitar o ponteiro.
//*this << ends;
LPCTSTR psz = str().c_str();
Um erro que nos intrigou por um tempo ocorreu neste código.
MOUT << _T(" chUser:'") << chUser
<< _T("' (") << (INT)(UCHAR)chUser << _T(')');
A macro MOUT resolve-se para *g_pmout, que é um objeto do tipo mstream. A mstream classe é derivada da classe de cadeia de caracteres de saída padrão, std::basic_ostream<TCHAR>. No entanto, com _T em torno da cadeia de caracteres literal, que colocamos em preparação para converter em Unicode, a resolução de sobrecarga para o operador << falha com a seguinte mensagem de erro:
1>winmsgs.cpp(4612): error C2666: 'mstream::operator <<': 2 overloads have similar conversions
1> c:\source\spyxx\spyxx\mstream.h(120): note: could be 'mstream &mstream::operator <<(ios &(__cdecl *)(ios &))'
1> c:\source\spyxx\spyxx\mstream.h(118): note: or 'mstream &mstream::operator <<(ostream &(__cdecl *)(ostream &))'
1> c:\source\spyxx\spyxx\mstream.h(116): note: or 'mstream &mstream::operator <<(ostrstream &(__cdecl *)(ostrstream &))'
1> c:\source\spyxx\spyxx\mstream.h(114): note: or 'mstream &mstream::operator <<(mstream &(__cdecl *)(mstream &))'
1> c:\source\spyxx\spyxx\mstream.h(109): note: or 'mstream &mstream::operator <<(LPTSTR)'
1> c:\source\spyxx\spyxx\mstream.h(104): note: or 'mstream &mstream::operator <<(TCHAR)'
1> c:\source\spyxx\spyxx\mstream.h(102): note: or 'mstream &mstream::operator <<(DWORD)'
1> c:\source\spyxx\spyxx\mstream.h(101): note: or 'mstream &mstream::operator <<(WORD)'
1> c:\source\spyxx\spyxx\mstream.h(100): note: or 'mstream &mstream::operator <<(BYTE)'
1> c:\source\spyxx\spyxx\mstream.h(95): note: or 'mstream &mstream::operator <<(long)'
1> c:\source\spyxx\spyxx\mstream.h(90): note: or 'mstream &mstream::operator <<(unsigned int)'
1> c:\source\spyxx\spyxx\mstream.h(85): note: or 'mstream &mstream::operator <<(int)'
1> c:\source\spyxx\spyxx\mstream.h(83): note: or 'mstream &mstream::operator <<(HWND)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(1132): note: or 'CDumpContext &operator <<(CDumpContext &,COleSafeArray &)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(1044): note: or 'CArchive &operator <<(CArchive &,ATL::COleDateTimeSpan)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(1042): note: or 'CDumpContext &operator <<(CDumpContext &,ATL::COleDateTimeSpan)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(1037): note: or 'CArchive &operator <<(CArchive &,ATL::COleDateTime)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(1035): note: or 'CDumpContext &operator <<(CDumpContext &,ATL::COleDateTime)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(1030): note: or 'CArchive &operator <<(CArchive &,COleCurrency)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(1028): note: or 'CDumpContext &operator <<(CDumpContext &,COleCurrency)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(955): note: or 'CArchive &operator <<(CArchive &,ATL::CComBSTR)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(951): note: or 'CArchive &operator <<(CArchive &,COleVariant)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxdisp.h(949): note: or 'CDumpContext &operator <<(CDumpContext &,COleVariant)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxwin.h(248): note: or 'CArchive &operator <<(CArchive &,const RECT &)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxwin.h(247): note: or 'CArchive &operator <<(CArchive &,POINT)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxwin.h(246): note: or 'CArchive &operator <<(CArchive &,SIZE)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxwin.h(242): note: or 'CDumpContext &operator <<(CDumpContext &,const RECT &)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxwin.h(241): note: or 'CDumpContext &operator <<(CDumpContext &,POINT)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afxwin.h(240): note: or 'CDumpContext &operator <<(CDumpContext &,SIZE)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afx.h(1639): note: or 'CArchive &operator <<(CArchive &,const CObject *)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afx.h(1425): note: or 'CArchive &operator <<(CArchive &,ATL::CTime)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afx.h(1423): note: or 'CDumpContext &operator <<(CDumpContext &,ATL::CTime)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afx.h(1418): note: or 'CArchive &operator <<(CArchive &,ATL::CTimeSpan)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include\afx.h(1416): note: or 'CDumpContext &operator <<(CDumpContext &,ATL::CTimeSpan)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\ostream(694): note: or 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::operator <<<wchar_t,std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,const char *)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\ostream(741): note: or 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::operator <<<wchar_t,std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,char)'
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\ostream(866): note: or 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::operator <<<wchar_t,std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,const _Elem *)'
1> with
1> [
1> _Elem=wchar_t
1> ]
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\ostream(983): note: or 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::operator <<<wchar_t,std::char_traits<wchar_t>,wchar_t[10]>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &&,const _Ty (&))'
1> with
1> [
1> _Ty=wchar_t [10]
1> ]
1> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\ostream(1021): note: or 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::operator <<<wchar_t,std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,const std::error_code &)'
1> winmsgs.cpp(4612): note: while trying to match the argument list '(CMsgStream, const wchar_t [10])'
Existem tantas definições de operador << que esse tipo de erro pode ser intimidante. Depois de olhar mais de perto para as sobrecargas disponíveis, podemos ver que a maioria delas é irrelevante, e olhando mais de perto para a mstream definição de classe, identificamos a seguinte função que achamos que deve ser chamada neste caso.
mstream& operator<<(LPTSTR psz)
{
return (mstream&)ostrstream::operator<<(psz);
}
A razão pela qual ele não é chamado é porque a cadeia de caracteres literal tem o tipo const wchar_t[10] como você pode ver na última linha dessa mensagem de erro longa, portanto, a conversão para um ponteiro não-const não é automática. No entanto, esse operador não deve modificar o parâmetro de entrada, portanto, o tipo de parâmetro mais apropriado é LPCTSTR (const char* ao compilar como MBCS e const wchar_t* como Unicode), não LPTSTR (char* ao compilar como MBCS e wchar_t* como Unicode). Fazer essa alteração corrige esse erro.
Esse tipo de conversão era permitido sob o compilador mais antigo e menos rigoroso, mas alterações de conformidade mais recentes exigem um código mais correto.
Passo 8. As conversões mais rigorosas do compilador
Também recebemos muitos erros como os seguintes:
error C2440: 'static_cast': cannot convert from 'UINT (__thiscall CHotLinkCtrl::* )(CPoint)' to 'LRESULT (__thiscall CWnd::* )(CPoint)'
O erro ocorre em um mapa de mensagens que é simplesmente uma macro:
BEGIN_MESSAGE_MAP(CFindToolIcon, CWnd)
// other messages omitted...
ON_WM_NCHITTEST() // Error occurs on this line.
END_MESSAGE_MAP()
Indo para a definição desta macro, vemos que ela faz referência à função OnNcHitTest.
#define ON_WM_NCHITTEST() \
{ WM_NCHITTEST, 0, 0, 0, AfxSig_l_p, \
(AFX_PMSG)(AFX_PMSGW) \
(static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(CPoint) > (&ThisClass :: OnNcHitTest)) },
O problema tem a ver com a incompatibilidade no ponteiro para tipo de função membro. O problema não é a conversão de CHotLinkCtrl como um tipo de classe para CWnd como o tipo de classe, já que essa é uma conversão de derivado para base válida. O problema é o tipo de retorno: UINT vs. LRESULT. LRESULT se resolve em LONG_PTR, que é um ponteiro de 64 bits ou um ponteiro de 32 bits, dependendo do tipo binário de destino; portanto, UINT não se converte neste tipo. Isso não é incomum ao atualizar código escrito antes de 2005, uma vez que o tipo de retorno de muitos métodos de mapa de mensagens mudou de UINT para LRESULT no Visual Studio 2005 como parte das alterações de compatibilidade de 64 bits. Alteramos o tipo de retorno de UINT no seguinte código para LRESULT:
afx_msg UINT OnNcHitTest(CPoint point);
Após a alteração temos o seguinte código:
afx_msg LRESULT OnNcHitTest(CPoint point);
Como há cerca de dez ocorrências dessa função, todas em classes diferentes derivadas de CWnd, é útil usar Go to Definition (Keyboard: F12) e Go to Declaration (Keyboard: Ctrl+F12) quando o cursor estiver na função no editor para localizá-las e navegar até elas a partir da janela da ferramenta Find Symbol . Ir para a definição é geralmente o mais útil dos dois. Vá para Declaração encontrará declarações diferentes da declaração da classe definida, como declarações de classes amigas ou referências de encaminhamento.
Passo 9. Alterações no MFC
O próximo erro também está relacionado a um tipo de declaração alterado e também ocorre em uma macro.
error C2440: 'static_cast': cannot convert from 'void (__thiscall CFindWindowDlg::* )(BOOL,HTASK)' to 'void (__thiscall CWnd::* )(BOOL,DWORD)'
A questão é que o segundo parâmetro de CWnd::OnActivateApp mudou de HTASK para DWORD. Essa alteração ocorreu na versão 2002 do Visual Studio, Visual Studio .NET.
afx_msg void OnActivateApp(BOOL bActive, HTASK hTask);
Temos que atualizar as declarações de OnActivateApp em classes derivadas de acordo com a seguinte maneira:
afx_msg void OnActivateApp(BOOL bActive, DWORD dwThreadId);
Neste ponto, somos capazes de compilar o projeto. No entanto, existem alguns avisos que precisam ser resolvidos, e há aspetos opcionais da atualização, como a conversão de MBCS para Unicode ou a melhoria da segurança através do uso das funções seguras do CRT.
Passo 10. Lidando com avisos do compilador
Para obter uma lista completa de avisos, deve executar um Rebuild All na solução em vez de uma compilação comum, para garantir que tudo o que foi compilado anteriormente seja recompilado, já que só recebe relatórios de aviso da compilação atual. A outra questão é aceitar o nível de alerta atual ou usar um nível de aviso mais alto. Ao migrar uma grande quantidade de código, especialmente código antigo, usar um nível de aviso mais alto pode ser apropriado. Você também pode querer começar com o nível de aviso padrão e, em seguida, aumentar o nível de aviso para obter todos os avisos. Se você usar /Wall, receberá alguns avisos nos arquivos de cabeçalho do sistema; por isso, muitas pessoas usam /W4 para obter o máximo número de avisos possíveis no seu código, sem receber avisos para os cabeçalhos do sistema. Se quiser que os avisos apareçam como erros, adicione a /WX opção. Essas configurações estão na seção C/C++ da caixa de diálogo Propriedades do projeto .
Um dos métodos na CSpyApp classe produz um aviso sobre uma função que não é mais suportada.
void SetDialogBkColor() {CWinApp::SetDialogBkColor(::GetSysColor(COLOR_BTNFACE));}
O aviso é o seguinte.
warning C4996: 'CWinApp::SetDialogBkColor': CWinApp::SetDialogBkColor is no longer supported. Instead, handle WM_CTLCOLORDLG in your dialog
A mensagem WM_CTLCOLORDLG já foi tratada no código Spy++, então a única alteração necessária foi excluir quaisquer referências ao SetDialogBkColor, que não é mais necessário.
O próximo aviso foi simples de corrigir, comentando o nome da variável. Recebemos o seguinte aviso:
warning C4456: declaration of 'lpszBuffer' hides previous local declaration
O código que produz isso envolve uma macro.
DECODEPARM(CB_GETLBTEXT)
{
P2WPOUT();
P2LPOUTPTRSTR;
P2IFDATA()
{
PARM(lpszBuffer, PPACK_STRINGORD, ED2);
INDENT();
P2IFISORD(lpszBuffer)
{
P2OUTORD(lpszBuffer);
}
else
{
PARM(lpszBuffer, LPTSTR, ED2);
P2OUTS(lpszBuffer);
}
}
}
O uso intenso de macros como neste código tende a tornar o código mais difícil de manter. Neste caso, as macros incluem as declarações das variáveis. O PARM macro é definido da seguinte forma:
#define PARM(var, type, src)type var = (type)src
Portanto, a variável é declarada lpszBuffer duas vezes na mesma função. Não é tão simples corrigir isso como seria se o código não estivesse usando macros (basta remover a declaração de segundo tipo). Como está, temos a escolha infeliz de ter que decidir se reescrever o código de macro como código comum (uma tarefa tediosa e possivelmente propensa a erros) ou desativar o aviso.
Neste caso, optamos por desativar o aviso. Podemos fazer isso adicionando um pragma da seguinte forma:
#pragma warning(disable : 4456)
Ao desativar um aviso, convém restringir o efeito de desativação apenas ao código que produz o aviso, para evitar a supressão do aviso quando ele pode fornecer informações úteis. Adicionamos código para restaurar o aviso logo após a linha que o produz, ou melhor ainda, já que esse aviso ocorre em uma macro, use a palavra-chave __pragma , que funciona em macros (#pragma não funciona em macros).
#define PARM(var, type, src)__pragma(warning(disable : 4456)) \
type var = (type)src \
__pragma(warning(default : 4456))
O próximo aviso requer algumas revisões de código. A API GetVersion do Win32 (e GetVersionEx) foi preterida.
warning C4996: 'GetVersion': was declared deprecated
O código a seguir mostra como a versão é obtida.
// check Windows version and set m_bIsWindows9x/m_bIsWindows4x/m_bIsWindows5x flags accordingly.
DWORD dwWindowsVersion = GetVersion();
Isso é seguido por um monte de código que examina o valor dwWindowsVersion para determinar se estamos executando no Windows 95 e qual versão do Windows NT. Como tudo isso está desatualizado, removemos o código e lidamos com quaisquer referências a essas variáveis.
O artigo Alterações de versão do sistema operacional no Windows 8.1 e Windows Server 2012 R2 explica a situação.
Há métodos na classe que consultam a versão do CSpyApp sistema operacional: IsWindows9x, IsWindows4xe IsWindows5x. Um bom ponto de partida é assumir que as versões do Windows que pretendemos suportar (Windows 7 e posterior) estão todas próximas do Windows NT 5 no que diz respeito às tecnologias utilizadas por esta aplicação mais antiga. Os usos desses métodos foram para lidar com as limitações dos sistemas operacionais mais antigos. Então alterámos esses métodos para retornarem VERDADEIRO para IsWindows5x e FALSO para os restantes.
BOOL IsWindows9x() {/*return(m_bIsWindows9x);*/ return FALSE; }
BOOL IsWindows4x() {/*return(m_bIsWindows4x);*/ return FALSE; }
BOOL IsWindows5x() {/*return(m_bIsWindows5x);*/ return TRUE; }
Isso deixou apenas alguns lugares onde as variáveis internas foram usadas diretamente. Uma vez que removemos essas variáveis, surgem alguns erros que devem ser tratados de forma explícita.
error C2065: 'm_bIsWindows9x': undeclared identifier
void CSpyApp::OnUpdateSpyProcesses(CCmdUI *pCmdUI)
{
pCmdUI->Enable(m_bIsWindows9x || hToolhelp32 != NULL);
}
Poderíamos substituir isso por uma chamada de método ou simplesmente passar TRUE e remover o antigo caso especial para o Windows 9x.
void CSpyApp::OnUpdateSpyProcesses(CCmdUI *pCmdUI)
{
pCmdUI->Enable(TRUE /*!m_bIsWindows9x || hToolhelp32 != NULL*/);
}
O aviso final no nível padrão (3) tem a ver com um campo de bits.
treectl.cpp(1656): warning C4463: overflow; assigning 1 to bit-field that can only hold values from -1 to 0
O código que aciona isso é o seguinte.
m_bStdMouse = TRUE;
A declaração de m_bStdMouse indica que é um bitfield.
class CTreeListBox : public CListBox
{
DECLARE_DYNCREATE(CTreeListBox)
CTreeListBox();
private:
int ItemFromPoint(const CPoint& point);
class CTreeCtl* m_pTree;
BOOL m_bGotMouseDown : 1;
BOOL m_bDeferedDeselection : 1;
BOOL m_bStdMouse : 1;
Esse código foi escrito antes do tipo bool embutido ser suportado no Visual C++. Nesse código, BOOL era um typedef para int. O tipo int é do tipo signed, e a representação de bits de um signed int é usar o primeiro bit como um bit de sinal, de modo que um campo de bits do tipo int poderia ser interpretado como representando 0 ou -1, o que provavelmente não era a intenção.
Você não conseguiria perceber olhando para o código porque estes são bitfields. A intenção era manter o tamanho do objeto pequeno ou existe algum lugar onde o layout binário do objeto é usado? Nós mudamos estes para membros comuns do BOOL, uma vez que não vimos nenhuma razão para o uso de um bitfield. Não é garantido que o uso de campos de bits para manter o tamanho de um objeto pequeno funcione. Depende de como o compilador estabelece o tipo.
Você pode se perguntar se usar o tipo bool padrão ao longo do texto seria útil. Muitos dos padrões de código antigos, como o tipo BOOL, foram inventados para resolver problemas que mais tarde foram resolvidos em C++ padrão, portanto, mudar de BOOL para o bool tipo interno é apenas um exemplo dessa mudança que você considera fazer depois de obter seu código inicialmente executado na nova versão.
Depois de lidarmos com todos os avisos que aparecem no nível padrão (nível 3), mudamos para o nível 4 para capturar alguns avisos adicionais. O primeiro a aparecer foi o seguinte:
warning C4100: 'nTab': unreferenced formal parameter
O código que produziu este aviso foi o seguinte.
virtual void OnSelectTab(int nTab) {};
Isso parece inofensivo o suficiente, mas como queríamos uma compilação limpa com /W4 e /WX definidos, simplesmente comentámos o nome da variável, deixando-o para legibilidade.
virtual void OnSelectTab(int /*nTab*/) {};
Outros avisos que recebemos foram úteis para a limpeza geral do código. Há uma série de conversões implícitas de int ou unsigned int para WORD (que é um typedef para unsigned short). Estes envolvem uma possível perda de dados. Nestes casos, adicionámos um elenco ao WORD.
Outro aviso de nível 4 que recebemos para este código foi:
warning C4211: nonstandard extension used: redefined extern to static
O problema ocorre quando uma variável foi declarada externpela primeira vez e, posteriormente, declarada static. O significado desses dois especificadores de classe de armazenamento é mutuamente exclusivo, mas isso é permitido como uma extensão da Microsoft. Se você quisesse que o código fosse portátil para outros compiladores, ou se quisesse compilá-lo com /Za (compatibilidade ANSI), alteraria as declarações para ter especificadores de classe de armazenamento correspondentes.
Passo 11. Portabilidade de MBCS para Unicode
Note que no mundo Windows, quando dizemos Unicode, geralmente queremos dizer UTF-16. Outros sistemas operacionais, como o Linux, usam UTF-8, mas o Windows geralmente não. A versão MBCS do MFC foi preterida no Visual Studio 2013 e 2015, mas não é mais preterida no Visual Studio 2017. Se estiver usando o Visual Studio 2013 ou 2015, antes de executar a etapa para realmente portar o código MBCS para UTF-16 Unicode, talvez queiramos eliminar temporariamente os avisos de que o MBCS foi preterido, a fim de fazer outro trabalho ou adiar a portabilidade até um momento conveniente. O código atual usa MBCS e para continuar com isso precisamos instalar a versão ANSI / MBCS do MFC. A biblioteca MFC bastante grande não faz parte do desenvolvimento padrão do Visual Studio Desktop com instalação C++ , portanto, ela deve ser selecionada entre os componentes opcionais no instalador. Consulte MFC MBCS DLL Add-on. Depois de baixar isso e reiniciar o Visual Studio, você pode compilar e vincular com a versão MBCS do MFC, mas para se livrar dos avisos sobre MBCS se você estiver usando o Visual Studio 2013 ou 2015, você também deve adicionar NO_WARN_MBCS_MFC_DEPRECATION à sua lista de macros predefinidas na seção Pré-processador das propriedades do projeto, ou no início do seu arquivo de cabeçalho stdafx.h ou outro arquivo de cabeçalho comum.
Agora temos alguns erros de linker.
fatal error LNK1181: cannot open input file 'mfc42d.lib'
LNK1181 ocorre porque uma versão desatualizada da biblioteca estática do MFC está incluída na entrada do vinculador. Isso não é mais necessário, pois podemos vincular o MFC dinamicamente, então só precisamos remover todas as bibliotecas estáticas do MFC da propriedade Input na seção Linker das propriedades do projeto. Este projeto também está usando a /NODEFAULTLIB opção e, em vez disso, lista todas as dependências da biblioteca.
msvcrtd.lib;msvcirtd.lib;kernel32.lib;user32.lib;gdi32.lib;advapi32.lib;Debug\SpyHk55.lib;%(AdditionalDependencies)
Agora, vamos realmente atualizar o antigo código MBCS (Multi-byte Character set) para Unicode. Uma vez que este é um aplicativo do Windows, intimamente ligado à plataforma de desktop do Windows, vamos portá-lo para UTF-16 Unicode que o Windows usa. Se você estiver escrevendo código entre plataformas ou portando um aplicativo do Windows para outra plataforma, convém considerar a portabilidade para UTF-8, que é amplamente usado em outros sistemas operacionais.
Portando para UTF-16 Unicode, devemos decidir se ainda queremos a opção de compilar para MBCS ou não. Se quisermos ter a opção de suportar MBCS, devemos usar a macro TCHAR como o tipo de caractere, que se resolve para char ou wchar_t, dependendo de se _MBCS ou _UNICODE está definido durante a compilação. Mudar para TCHAR e as versões TCHAR de várias APIs em vez de wchar_t e suas APIs associadas significa que você pode voltar para uma versão MBCS do seu código simplesmente definindo _MBCS macro em vez de _UNICODE. Além do TCHAR, existe uma variedade de versões do TCHAR, como typedefs, macros e funções amplamente utilizadas. Por exemplo, LPCTSTR em vez de LPCSTR, e assim por diante. Na caixa de diálogo de propriedades do projeto, em Propriedades de Configuração, na seção Geral , altere a propriedade Conjunto de Caracteres de Usar Conjunto de Caracteres MBCS para Usar Conjunto de Caracteres Unicode. Essa configuração afeta qual macro é predefinida durante a compilação. Há uma macro UNICODE e uma macro _UNICODE. A propriedade do projeto afeta ambos consistentemente. Os cabeçalhos do Windows usam UNICODE onde cabeçalhos do Visual C++ como MFC usam _UNICODE, mas quando um é definido, o outro é sempre definido.
Existe um bom guia para a portabilidade do MBCS para UTF-16 Unicode usando TCHAR. Escolhemos este caminho. Primeiro, alteramos a propriedade Character set para Use Unicode Character set e reconstruímos o projeto.
Alguns lugares no código já estavam usando TCHAR, aparentemente em antecipação de eventualmente suportar Unicode. Alguns não foram. Procurámos instâncias de CHAR, que é um typedef para char, e substituímos muitas por TCHAR. Além disso, nós procuramos sizeof(CHAR). Sempre que mudávamos de CHAR para TCHAR, geralmente tínhamos que mudar para sizeof(TCHAR) uma vez que isso era frequentemente usado para determinar o número de caracteres em uma cadeia de caracteres. Usar o tipo errado aqui não produz um erro de compilador, por isso vale a pena prestar um pouco de atenção a este caso.
Este tipo de erro é muito comum logo após mudar para Unicode.
error C2664: 'int wsprintfW(LPWSTR,LPCWSTR,...)': cannot convert argument 1 from 'CHAR [16]' to 'LPWSTR'
Aqui está um exemplo de código que produz isso:
wsprintf(szTmp, "%d.%2.2d.%4.4d", rmj, rmm, rup);
Colocamos _T em torno do string literal para remover o erro.
wsprintf(szTmp, _T("%d.%2.2d.%4.4d"), rmj, rmm, rup);
A macro _T tem o efeito de fazer uma compilação literal de cadeia de caracteres como uma char cadeia de caracteres ou uma wchar_t cadeia de caracteres, dependendo da configuração de MBCS ou UNICODE. Para substituir todas as cadeias de caracteres por _T no Visual Studio, primeiro abra a caixa Substituição Rápida (Teclado: Ctrl+F) ou a caixa Substituir em arquivos (Teclado: Ctrl+Shift+H) e escolha a caixa de seleção Usar expressões regulares . Insira ((\".*?\")|('.+?')) como o texto de pesquisa e _T($1) como o texto de substituição. Se você já tiver a macro _T em torno de algumas cadeias de caracteres, este procedimento a adicionará novamente e também poderá encontrar casos em que você não deseja _T, como quando você usa #include, por isso é melhor usar Substituir Próximo em vez de Substituir Tudo.
Esta função em particular, wsprintf, é realmente definida nos cabeçalhos do Windows, e a documentação para ela recomenda que ela não seja usada, devido a uma possível saturação do buffer. Nenhum tamanho é dado para o szTmp buffer, portanto, não há nenhuma maneira para a função verificar se o buffer pode conter todos os dados a serem gravados nele. Consulte a próxima seção sobre a portabilidade para o CRT seguro, na qual corrigimos outros problemas semelhantes. Acabamos substituindo por _stprintf_s.
Outro erro comum que você verá na conversão para Unicode é este.
error C2440: '=': cannot convert from 'char *' to 'TCHAR *'
O código que o produz é o seguinte:
pParentNode->m_szText = new char[strTitle.GetLength() + 1];
_tcscpy(pParentNode->m_szText, strTitle);
Embora a _tcscpy função tenha sido usada, que é a função strcpy TCHAR para copiar uma cadeia de caracteres, o buffer alocado era um char buffer. Isso é facilmente alterado para TCHAR.
pParentNode->m_szText = new TCHAR[strTitle.GetLength() + 1];
_tcscpy(pParentNode->m_szText, strTitle);
Da mesma forma, mudámos LPSTR (Long Pointer para STRing) e LPCSTR (Long Pointer para Constant STRing) para LPTSTR (Long Pointer para TCHAR STRing) e LPCTSTR (Long Pointer para Constant TCHAR STRing), respectivamente, quando necessário devido a um erro de compilador. Optamos por não fazer essas substituições usando a busca global e a substituição, porque cada situação tinha que ser examinada individualmente. Em alguns casos, a char versão é desejada, como ao processar certas mensagens do Windows que usam estruturas do Windows que têm o sufixo A . Na API do Windows, o sufixo A significa ASCII ou ANSI (e também se aplica ao MBCS), e o sufixo W significa caracteres largos, ou UTF-16 Unicode. Este padrão de nomenclatura é usado nos cabeçalhos do Windows, mas também o seguimos no código Spy++ quando tivemos que adicionar uma versão Unicode de uma função que já estava definida em apenas uma versão MBCS.
Em alguns casos, tivemos que substituir um tipo para usar uma versão que resolve corretamente (WNDCLASS em vez de WNDCLASSA, por exemplo).
Em muitos casos, tivemos que usar a versão genérica (macro) de uma API do Win32 como GetClassName (em vez de GetClassNameA). Na instrução switch do manipulador de mensagens, algumas mensagens são específicas do MBCS ou Unicode, nesses casos, tivemos que alterar o código para chamar explicitamente a versão do MBCS, porque substituímos as funções genericamente nomeadas por funções específicas A e W e adicionamos uma macro para o nome genérico que resolve para o nome A ou W correto com base na definição de UNICODE. Em muitas partes do código, quando mudamos para definir _UNICODE, a versão W agora é escolhida mesmo quando a versão A é o que se quer.
Há alguns locais onde tiveram de ser tomadas medidas especiais. Qualquer uso de WideCharToMultiByte ou MultiByteToWideChar pode exigir um olhar mais atento. Aqui está um exemplo onde WideCharToMultiByte estava sendo usado.
BOOL C3dDialogTemplate::GetFont(CString& strFace, WORD& nFontSize)
{
ASSERT(m_hTemplate != NULL);
DLGTEMPLATE* pTemplate = (DLGTEMPLATE*)GlobalLock(m_hTemplate);
if ((pTemplate->style & DS_SETFONT) == 0)
{
GlobalUnlock(m_hTemplate);
return FALSE;
}
BYTE* pb = GetFontSizeField(pTemplate);
nFontSize = *(WORD*)pb;
pb += sizeof (WORD);
WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)pb, -1,
strFace.GetBufferSetLength(LF_FACESIZE), LF_FACESIZE, NULL, NULL);
strFace.ReleaseBuffer();
GlobalUnlock(m_hTemplate);
return TRUE;
}
Para resolver isso, tivemos que entender que a razão pela qual isso foi feito foi copiar uma cadeia de caracteres larga representando o nome de uma fonte no buffer interno de um CString, strFace. Isso exigia um código ligeiramente diferente para cadeias de caracteres multibyte CString como para cadeias de caracteres CString largas, então adicionamos um #ifdef neste caso.
#ifdef _MBCS
WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)pb, -1,
strFace.GetBufferSetLength(LF_FACESIZE), LF_FACESIZE, NULL, NULL);
strFace.ReleaseBuffer();
#else
wcscpy(strFace.GetBufferSetLength(LF_FACESIZE), (LPCWSTR)pb);
strFace.ReleaseBuffer();
#endif
É claro que, em vez de wcscpy devermos realmente usar wcscpy_s, a versão mais segura. A próxima seção aborda isso.
Como uma verificação no nosso trabalho, devemos redefinir o conjunto de caracteres para usar conjunto de caracteres multibyte e certificar-nos de que o código ainda compila usando MBCS, assim como Unicode. Escusado será dizer que um teste completo deve ser executado no aplicativo recompilado após todas essas alterações.
Em nosso trabalho com esta solução Spy++, levou cerca de dois dias úteis para um desenvolvedor C++ médio converter o código para Unicode. Isso não incluiu o tempo para repetir o teste.
Passo 12. Migração para usar o CRT Seguro
Portar o código para usar as versões seguras (as versões com o sufixo _s) das funções CRT é o próximo passo. Neste caso, a estratégia geral é substituir a função pela versão _s e, em seguida, geralmente, adicionar os parâmetros adicionais necessários de tamanho de buffer. Em muitos casos, isso é simples, uma vez que o tamanho é conhecido. Em outros casos, onde o tamanho não está imediatamente disponível, é necessário adicionar parâmetros adicionais à função que está usando a função CRT, ou talvez examinar o uso do buffer de destino e ver quais são os limites de tamanho apropriados.
Visual C++ fornece um truque para tornar mais fácil obter código seguro sem adicionar tantos parâmetros de tamanho, e isso é usando as sobrecargas de modelo. Como essas sobrecargas são modelos, elas só estão disponíveis ao compilar como C++, não como C. Spyxxhk é um projeto C, então o truque não funcionará para isso. No entanto, Spyxx não é e podemos usar o truque. O truque é adicionar uma linha como esta em um lugar onde ela será compilada em todos os arquivos do projeto, como em stdafx.h:
#define _CRT_SECURE_TEMPLATE_OVERLOADS 1
Quando defines que, sempre que o buffer é um array, em vez de um ponteiro simples, o seu tamanho é inferido a partir do tipo de array e isso é usado como o parâmetro de tamanho, sem que precises fornecê-lo. Isso ajuda a reduzir a complexidade de reescrever o código. Você ainda tem que substituir o nome da função com a versão _s , mas isso muitas vezes pode ser feito por uma operação de pesquisa e substituição.
Os valores de retorno de algumas funções foram alterados. Por exemplo, _itoa_s (e _itow_s a macro _itot_s) retorna um código de erro (errno_t), em vez da cadeia de caracteres. Então, nesses casos, você tem que mover a chamada de _itoa_s para uma linha separada e substituí-la pelo identificador do buffer.
Alguns dos casos comuns: ao mudar memcpy para memcpy_s, frequentemente adicionamos o tamanho da estrutura que está a ser copiada. Da mesma forma, para a maioria das cadeias de caracteres e buffers, o tamanho da matriz ou buffer é facilmente determinado a partir da declaração do buffer ou localizando onde o buffer foi originalmente alocado. Para algumas situações, você precisa determinar o tamanho de um buffer realmente disponível e, se essa informação não estiver disponível no escopo da função que você está modificando, ela deve ser adicionada como um parâmetro adicional e o código de chamada deve ser modificado para fornecer as informações.
Com essas técnicas, levou cerca de meio dia para converter o código para usar as funções CRT seguras. Se você optar por não sobrecarregar o modelo e adicionar os parâmetros de tamanho manualmente, provavelmente levaria duas ou três vezes mais tempo.
Passo 13. /Zc:forScope- foi preterido
Desde o Visual C++ 6.0, o compilador está em conformidade com o padrão atual, que limita o escopo das variáveis declaradas em um loop ao escopo do loop. A opção do compilador /Zc:forScope (Force Conformance for Loop Scope nas propriedades do projeto) controla se isso é ou não relatado como um erro. Devemos atualizar nosso código para estar em conformidade e adicionar declarações fora do loop. Para evitar fazer as alterações de código, você pode alterar essa configuração na seção Language das propriedades do projeto C++ para No (/Zc:forScope-). No entanto, tenha em mente que /Zc:forScope- pode ser removido em uma versão futura do Visual C++, portanto, eventualmente, seu código precisará ser alterado para estar em conformidade com o padrão.
Esses problemas são relativamente fáceis de corrigir, mas dependendo do seu código, isso pode afetar muito código. Aqui está um problema típico.
int CPerfTextDataBase::NumStrings(LPCTSTR mszStrings) const
{
for (int n = 0; mszStrings[0] != 0; n++)
mszStrings = _tcschr(mszStrings, 0) + 1;
return(n);
}
O código acima produz o erro:
'n': undeclared identifier
Isso ocorre porque o compilador preteriu uma opção de compilador que permitia código que não está mais em conformidade com o padrão C++. No padrão, declarar uma variável dentro de um loop restringe seu escopo apenas ao loop, de modo que a prática comum de usar um contador de loop fora do loop requer que a declaração do contador também seja movida para fora do loop, como no seguinte código revisado:
int CPerfTextDataBase::NumStrings(LPCTSTR mszStrings) const
{
int n;
for (n = 0; mszStrings[0] != 0; n++)
mszStrings = _tcschr(mszStrings, 0) + 1;
return(n);
}
Resumo
A portabilidade do Spy++ do código original do Visual C++ 6.0 para o compilador mais recente levou cerca de 20 horas de tempo de codificação ao longo de cerca de uma semana. Atualizamos diretamente por meio de oito versões do produto do Visual Studio 6.0 para o Visual Studio 2015. Esta é agora a abordagem recomendada para todas as atualizações em projetos grandes e pequenos.
Ver também
Portabilidade e atualização: exemplos e estudos de caso
Estudo de caso anterior: COM Spy