Aplica-se a:
•
ADO.NET
•
.NET Language Integrated Query (LINQ)
•
SQL Server
Resumo: Eliminar a diferença de impedância entre aplicações e serviços de dados como emissão de relatórios, análise e replicação oferecidos como parte do produto SQL Server aumentando a abstração do nível lógico (relacional) para o nível conceitual (entidade). As pessoas que apenas utilizam o software não reconhecem a dificuldade que é escrevê-lo. O design e o desenvolvimento são as partes divertidas. A parte difícil é fazer com que ele seja executado corretamente (e com a rapidez necessária). Para o programador é como se uma refeição começasse com um delicioso cheesecake duplo de café com creme e terminasse com creme de espinafre. O motivo pelo qual dizemos que a programação é uma arte, e não uma ciência ou disciplina da engenharia, é porque ainda não conseguimos dividi-la em suas etapas constituintes e mecanizá-la. Após fazermos isso com êxito, uma nova escala de possibilidades surge: programas que escrevem programas de linguagens de projeto orientadas a pessoas (PODL), programas para provar a correção do programa e para analisar e suportar a consulta semântica. Até esse ponto, porém, a programação continua sendo uma arte ou pelo menos é isso o que a sabedoria convencional diz. Mas assim como acontece com grande parte da sabedoria convencional, essa analogia não resiste a um exame mais detalhado. A criação de um programa (e já fazemos isso há mais de 20 anos) não é, na verdade, nada parecido com arte – pelo menos não a arte de escrever ficção (e também já fazemos isso há mais de 20 anos). Vou ilustrar essa questão com estes dois fragmentos de programa e prosa. Este primeiro exemplo é parte de um analisador léxico para analisar o Visual C++® 7.1. Ele é assim: i f ( i ndex >= Token ID : : l as tKeyword ( ) ) { s t r i ng token = tokenS t r ing ( i ndex ) ; i f ( ( i sd ig i t ( token [ 0 ] ) ) | | ( token [0 ] == ‘ - ’ ) ) { #i fde f TELL_ME d i sp lay ( " ! ! TokenSe t : : token Ident i f y : " , "a l i t e ra l d ig i t : " , token ) ; #end i f re tu rn Token ID : :TK_L i te ra l ; } } Este segundo exemplo é a abertura de um conto que escrevi há cerca de 27 anos, quando morava em Tucson, no Arizona, lecionava e escrevia prosa: We were her pride of 10, Miss Steward named us: the Prodigal, Phoenix, Benjamin, and perspicacious, pacific Suzanne. Hush, child. Benjamin, be
s t i l l , Miss Steward commanded h im gent People l y. are never just. Ambos os exemplos tiveram uma criação intensiva, ou seja, eu passei muito tempo antes criando a arquitetura geral do trabalho, e os fragmentos refletem esse esforço de criação. O programa tem uma falha de desempenho – eu recupero a entrada de cadeia de caracteres na tabela de cadeia de caracteres associada por valor e não por ponteiro. Se o código precisasse ser produzido, ele exigiria uma revisão óbvia. Isso não tem nada a ver com a correção, mas apenas com o desempenho. Para um programa com código fonte pequeno, a diferença de velocidade não é mensurável e, assim, eu preferi a sintaxe de objeto mais limpa e lenta, uma vez que não gosto dos tokens de manipulação de ponteiros da C/C++. Embora minha prosa não tenha atraído milhões de leitores, aqueles que a leram apreciaram a obra com sua trama emocional sob uma rigorosa estrutura simbólica. Aqueles que não apreciam a prosa tradicional acharam que ela era enganosa, porque não há nada de funcional nela. A finalidade da arte é projetar sentimentos e a realidade de uma vida privada subjetiva em meio aos eventos públicos em um meio plástico em particular – no caso da prosa, as palavras. Obviamente, não há valor nisso, não como o valor intrínseco de um programa. Eu gosto do processo de criação de um programa, e gosto de experimentar o modo como as diferentes partes interagem. Eu gosto de definir abstrações ou famílias de abstrações. Eu as considero meu elenco de personagens. Gosto também de criar interfaces, porque representam as coisas que meus personagens fazem. E é assim como eu realmente escrevo os programas. Eu não gosto de escrever expressões e declarações. Eu gostaria que tivéssemos uma linguagem de programação simbólica na qual eu pensasse melhor e que pudesse memorizar. Honestamente, eu não consigo manter a clareza de expressões e aninhamentos intercalados. Embora eu consiga manter oito seções separadas de texto em minha cabeça e as compare e as ecoe, eu não consigo dar sentido a um programa além das classes que crio e suas interfaces. No excelente artigo de John Markoff sobre a indústria do PC, What the Dormouse Said, ele declara que o objetivo dos visionários iniciais - colocar um computador à disposição de cada pessoa - foi concretizado. Mas ele está errado. Esse objetivo foi concretizado apenas pela metade. O que há de bom em todo esse poder da computação se uma pessoa não pode programá-la? Ainda não realizamos seu sonho porque ainda não descobrimos como tornar a programação uma arte para que todos possam compartilhá-la. Até lá, a programação continua não sendo uma disciplina da ciência, nem um domínio da arte. Por enquanto, a programação é pouco mais do que uma nova alquimia. Ninguém realmente a entende e ninguém ainda transformou uma especificação em ouro verdadeiro. Stanley B. Lippman começou a trabalhar na linguagem C++ junto com seu criador, Bjarne Stroustrup, em1984, na Bell Laboratories. Mais tarde, Stan trabalhou em animação de filmes na Disney e na DreamWorks e atuou como diretor técnico de software no filme Fantasia 2000. Desde então, tem trabalhado como consultor condecorado da JPL e arquiteto na equipe do Visual C++ da Microsoft. P Quando gero uma DLL com suporte de automação usando o Visual C++® 6.0, são geradas algumas funções de registro, mas não para cancelar o registro da minha DLL. Escrevi um DllUnregisterServer que tem esta aparência: STDAPI DllUnregisterServer(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState());
i f ( !CO leOb jec t Fac to r y : :Un reg i s te rA l l ( ) ) re tu rn Resu l t FromScode(SELFREG_E_CLASS) ; return NOERROR; } COleObjectFactory::UnregisterAll retorna TRUE, mas minha DLL ainda está registrada. Como devo escrever o código? Ivan Pavlik R Bem, você esbarrou em uma pequena fenda especial no universo MFC. UnregisterAll parece mesmo ser a função a ser chamada para cancelar o registro de sua DLL. Se olhar no olefact.cpp, onde o UnregisterAll é implementado, você verá que é mais ou menos assim: for (/* pFactory = all factories */) { pFactory->Unregister(); } Isto é, ele faz um loop sobre todas as fábricas de seu módulo e chama o Unregister para cada uma delas. Até aqui, tudo bem. O que o COleObjectFactory::Unregister faz? O código revela a história: BOOL COleObjectFactory::Unregister() { return TRUE; } Opa! Como você pode ver, o COleObjectFactory::Unregister não faz nada! Ele simplesmente retorna TRUE. Isso é muito peculiar, pois o COleObjectFactory::Register registra realmente sua classe COM chamando ::CoRegisterClassObject. Por outro lado, há uma outra função, COleObjectFactory::Revoke, que chama ::CoRevokeClassObject para cancelar o registro de sua classe COM. MFC chama Revoke automaticamente a partir de seu destruidor de COleObjectFactory. Então, o que é que está acontecendo aqui? O problema é uma confusão infeliz na terminologia, que tem origem nos diferentes modos de registrar as classes COM para DLLs e EXEs. Para DLLs, você registra sua classe adicionando chaves ao registro do Windows (CLSID, ProgID e assim por diante). Para EXEs, você precisa chamar CoRegisterClassObject para registrar sua classe no sistema COM no tempo de execução. A questão fica ainda mais confusa pelo fato de que para os EXEs, o oposto de registrar não é cancelar o registro, mas revogar (CoRevokeClassObject). Quando as pessoas de Redmond adicionaram o suporte COM ao MFC, elas fizeram o que puderam para facilitar ao máximo a vida das pessoas, mas nem sempre tiveram muito sucesso. COleObjectFactory::Register tem esta aparência: // in olefact.cpp BOOL COleObjectFactory::Register() { if (!afxContextIsDLL) {
: :CoReg i s te rC lassOb jec t ( . . . , CLSCTX_LOCAL_SERVER , . . ) ; } } Você pode ver de imediato que não faz nada para DLLs; ele só registra EXEs usando CLSCTX_LOCAL_SERVER (contexto = servidor local, EXE em execução na máquina local). Seguindo a C API básica, as pessoas de Redmond usaram o mesmo nome Revoke para a função que cancela o registro do EXE: COleObjectFactory::Revoke (que é chamado automaticamente pelo seu destruidor de fábrica de classes). Então, para o que serve COleObjectFactory::Unregister a função que não faz nada? Talvez as pessoas de Redmond pretendessem fazer com que um dia Register/Unregister funcionassem também para DLLs. Mas, do jeito que está agora, não funcionam. Para registrar seu DLL, você precisa de uma outra função completamente diferente: COleObjectFactory::UpdateRegistry. Essa função usa um argumento booleano que diz ao MFC se você quer registrar ou cancelar o registro de sua classe COM. Há também UpdateRegistryAll, que executa um loop sobre todas as fábricas de classes, chamando UpdateRegistry para cada uma. Então, aqui está o modo adequado para implementar seu DllUnregisterServer:: STDAP I Dl lUnreg i s te rSe rve r (vo id ) { AFX_MANAGE_STATE(AfxGe tS ta t i cModu leS ta te ( ) ) ; re tu rn COleOb jec t Fac to r y : :UpdateReg i s t ryA l l ( FALSE) ? S_OK : SELFREG_E_CLASS ; } DllRegisterServer deve parecer igual, mas você deve passar TRUE em vez de FALSE para UpdateRegistryAll. A implementação padrão para UpdateRegistry faz o que pode para adicionar ou remover as chaves de registro apropriadas em HKCR/CLSID–InprocServer32, ProgID, Insertable, ThreadingModel e assim por diante, mas, às vezes, você precisará de outras chaves específicas para sua classe COM. Por exemplo, pode ser preciso registrar categorias, que são um tipo de extensão da antiga chave "Insertable" (o que significa que sua classe COM pode ser ignorada em uma forma no modo de design). Nesse caso, você precisa ignorar o UpdateRegistry para adicionar ou remover suas próprias chaves. Há tempos, nos exemplares de novembro e dezembro de 1999 do Microsoft Systems Journal (agora chamados de MSDN®Magazine), mostrei como construir um Band Object para Internet Explorer usando a interface Active Template Library (ATL) IRegistrar. (Band Objects precisam ser registrados como uma categoria especial CATID_DeskBand.) IRegistrar é uma ferramenta muito boa que permite que você escreva um script de registro (arquivo .RGS) para adicionar suas entradas ao registro, em vez de chamar funções de registro como RegOpenKey, RegSetValue e outras mais. A Figura 1 mostra um script típico. Como você pode ver, IRegistrar permite que você defina variáveis como %ClassName% e %ThreadingModel% e depois as substitua com valores reais no tempo de execução. Com IRegistrar,
você nunca mais terá de chamar a API de registro. Você pode escrever um script que se pareça mais com as entradas reais do registro e pode, inclusive, usar o mesmo script para registrar ou cancelar o registro de sua DLL. Isso mesmo, o IRegistrar é inteligente o bastante para cancelar o registro. O IRegistrar não está documentado oficialmente em lugar algum que eu pudesse encontrar, mas o código está aqui (encontre-o em atliface.h). Se ainda não está usando o IRegistrar para registrar e cancelar o registro de suas COM DLLs, você deveria mesmo experimentar — isso vai poupar-lhe muito trabalho. Para obter detalhes consulte meu artigo no exemplar de novembro de 1999 . P Meu aplicativo principal tem um menu normal com um submenu de comandos Editar (Recortar, Copiar, Colar etc.). Gostaria de exibir o submenu Editar como um menu de contexto quando o usuário clica com o botão direito na janela principal. O problema é como obter esse submenu a partir do menu principal. Parece que não existe nenhuma ID de comando associada a um submenu. Posso usar o índice começando em zero, mas devido à personalização o menu Editar nem sempre será o segundo menu. Não posso procurar o texto "Editar" porque damos suporte a múltiplos idiomas e a palavra verdadeira pode mudar. Como posso localizar o submenu Editar em todos esses casos? Brian Manlin R Bem, deixe-me salientar que o menu Editar deveria ser o segundo submenu, se você tiver um. As diretrizes de GUI do Official Windows® necessitam que os três primeiros itens de menu sejam Arquivo, Editar, Exibir, nessa ordem — para receberem suporte. Consulte Windows Interface Guidelines for Software Design (em inglês) (Microsoft Press®, 1995). Dito isso, posso ajudá-lo a encontrar seu submenu Editar ou qualquer outro submenu desejado. Você está correto: não existe ID de comando para um submenu. Isso acontece porque internamente o Windows usa o campo de ID de comando para armazenar o HMENU de submenu, se o item de menu é um submenu. Não se preocupe, supondo que o submenu sempre mantenha um comando específico (por exemplo Editar | Recortar) e supondo que o comando sempre tenha a mesma ID (por exemplo, ID_EDIT_CUT), é bem fácil escrever uma função que localize o submenu que contém um determinado comando. A Figura 2 mostra o código. CSubmenuFinder atua de fato como um namespace para armazenar a função estática FindCommandID, que chama a si mesma recursivamente para uma busca detalhada no menu e em todos os submenus por um item cuja ID de comando corresponda à procura. Esse segmento busca no menu principal um submenu que contenha um item de menu cuja ID de comando seja ID_EDIT_CUT e retorna o submenu, se localizado. Escrevi um pequeno programa denominado EdMenu para testar o CSubmenuFinder. O EdMenu usa o código anterior para manipular WM_CONTEXTMENU de modo a exibir um submenu editar exatamente igual ao do menu principal, quando o usuário clica com o botão direito na janela principal. Acredite em mim quando digo que o CSubmenuFinder localiza o menu editar onde quer que ele esteja dentro do menu principal. Na verdade, eu o testei quando mudei temporariamente o menu Editar para adiante do menu Exibir em uma de minhas criações. Experimente você mesmo, faça download da fonte. P Como converto MFC CString em String em C++ gerenciado? Por exemplo, tenho o seguinte código em C++: vo id GetS t r ing (CS t r ing& msg) { msg = / / bu i ld a s t r ing }
Como posso reescrever essa função usando C++ gerenciado e substituindo o parâmetro CString por uma String gerenciada? O problema é que GetString muda a CString do chamador e eu quero fazer a mesma coisa usando uma String gerenciada. Sumit Prakash R Bem, a resposta depende de você estar usando a nova sintaxe C++/CLI ou as antigas extensões gerenciadas. Os dois tipos de sintaxe podem ser confusos, mas não tenha medo, estou aqui para resolver sua confusão de códigos. Vamos começar pela nova sintaxe, pois estamos em 2006. O truque para acertar a sintaxe é lembrar de duas coisas. Primeiro, em C++/CLI, os objetos gerenciados usam chapéus. Assim, CString torna-se String^. Segundo, lembre-se de que o equivalente gerenciado à referência (&) é a referência de rastreamento, que em C++/CLI é %. Sei, você mal pode enfiar ^ em seu cérebro, mas, com alguma prática, % também logo vai parecer normal. Então, as novas funções ficam assim: / / us ing C++/CL I vo id GetS t r ing (S t r ing^% msg) { msg = / / bu i ld a s t r ing } Isso faz sentido? Para mostrar que funciona de fato, escrevi um pequeno programa strnet.cpp (veja a Figura 3). Ele implementa duas classes, CFoo1 e CFoo2, cada uma com um membro GetName. A primeira usa CString& e a segunda usa String^%. Se você compilar e executar esse programa (usando /clr é claro), verá que nos dois casos — o que usa CString e o que usa String — o objeto passado (nativo ou gerenciado) é modificado pela função de membro. Por falar nisso, sempre use uma referência de rastreamento (%) para objetos gerenciados, em vez de usar uma referência nativa (&, que também fará compilação) porque a referência de rastreamento rastreará a referência mesmo se o coletor de lixo mover o objeto dentro de sua pilha gerenciada. Se você está usando a sintaxe no estilo antigo (/clr:oldSyntax), como pode ainda viver na Idade Média? Nada tema, você pode usar uma referência mesmo que seja antiga. Se você está usando extensões gerenciadas, lembre-se apenas de que os objetos gerenciados são __gc pointers e assim CString transforma-se em String __gc *. E, como o compilador já sabe que String é um tipo gerenciado, você nem vai precisar do __gc; um ponteiro simples (*) bastará. A referência é a mesma. Então, a conversão da sintaxe de estilo antigo fica assim: / / __gc omi t ted vo id GetS t r ing (S t r ing*& msg) { msg = / / bu i ld a s t r ing } Entendeu? Faz todo o sentido. Sempre que tiver dificuldade em descobrir a sintaxe C++, a solução será voltar ao básico. P Recentemente gerei um aplicativo de console CLR com o Visual Studio® 2005. Reparei que o Visual Studio criou uma função principal como esta:
i n t main (a r ray<Sys tem: :S t r ing ^> ^args ) { ... re tu rn 0 ; } Isso parece ser uma mudança da antiga argc/argv com que estou familiarizado no C/C++. Quando tentei acessar args[0], pensando que seria o nome do arquivo (como em C/C++), descobri que args[0] não é o nome do arquivo, mas o primeiro parâmetro da linha de comando. O que aconteceu com o nome do arquivo? Pode me explicar a razão desta mudança? Jason Landrew R Ah, o mundo moderno em que nos divertimos! Nada é igual ao que era, não é verdade? Vivemos com argc/argv desde que a linguagem C foi desenvolvida em 1972 e agora as pessoas de Redmond vieram e mudaram tudo. Talvez eles quisessem ter certeza de que todos saberiam como usar a nova matriz de sintaxe. Uma razão para a nova sintaxe é gerar um programa que compile com /clr:safe, que é a última palavra em segurança de código. É como trancar seu programa dentro de uma sala limpa — um daqueles ambientes estéreis cheios de equipamentos de cromo, em que é preciso entrar através de uma câmara de vácuo de pressão negativa, usando capacete e botas especiais. Quando você aciona o comutador /clr:safe, o compilador impõe todos os tipos de restrições trabalhosas. Não é possível usar tipos nativos. Não é possível usar globais. Você não pode chamar funções não gerenciadas. Em resumo, você não pode fazer nada divertido. E por que você se sujeitaria a tanto sofrimento? Para ter certeza total e absoluta de que o código está seguro. Em termos técnicos, /clr:safe gera código "verificável", o que significa que o CLR (Common Language Runtime) pode verificar que seu código não viola configurações de segurança, tais como tentar acessar um arquivo quando as configurações de segurança do usuário não permitem. Um dos muitos itens da lista proibida do /clr:safe é ponteiros nativos — ou seja, ponteiros. ("Nativo" é redundante, pois não se pode ter um ponteiro de tipo gerenciado.) Como os ponteiros não são permitidos com o /clr:safe, a antiga declaração argv simplesmente não funciona. Assim, as pessoas de Redmond mudaram a declaração para usar uma matriz String^. E como as matrizes conhecem seu comprimento, não há necessidade de argc. A biblioteca de tempo de execução proporciona o código de inicialização apropriado para criar e inicializar a matriz de arg antes de chamar sua função principal. Se você não planeja usar /clr:safe, sempre é possível escrever um principal que use a antiga assinatura argc/argv . Isso explica o motivo de se usar uma matriz gerenciada, mas e o args[0], por que ele é o parâmetro do primeiro comando e não o nome do arquivo? Não posso falar pelas pessoas de Redmond, mas talvez tenham achado que faria mais sentido omitir o nome do arquivo, talvez pensassem que você não precisaria disso. De qualquer modo, não importa muito porque é fácil obter o nome do arquivo. Tenho certeza de que existem muitos modos de fazer isso, mas o mais óbvio e centrado em CLR (compatível com /clr:safe) em que posso pensar é usar a classe de System::Environment. Ela apresenta o seguinte método: s ta t i c ar ray<St r ing^>^ GetCommandL ineArgs ( ) Ao contrário do parâmetro args fornecido para o método principal, a matriz retornada por Environment::GetCommandLineArgs na verdade começa com o nome do arquivo executável.
Finalmente, por que você desejaria saber o nome de seu próprio arquivo EXE? Afinal de contas, se você está escrevendo o programa, supostamente sabe como ele se chama. Um motivo seria gerar uma mensagem de ajuda que contenha o nome do programa, sem codificá-lo. A mensagem de ajuda poderia ser mais ou menos assim: FooF i l e - - Tu rns every word i n your f i l e to " foo " usage : FooF i l e [ /n :
] f i l e spec f i l e spec = the f i l e s you want to change = change on ly the f i r s t occu r rences Aqui, FooFile é o nome do programa. Eu poderia simplesmente escrever "FooFile" em meu texto de ajuda, mas aprendi há muito tempo que muitas vezes renomeio programas ou copio códigos de um programa para outro. Obtendo o nome do programa a partir do próprio programa (por meio de argv ou Environment::GetCommandLineArgs), minhas mensagens de ajuda sempre exibem o nome correto, mesmo que eu renomeie o arquivo ou copie meu código em outro programa. Boa programação! Envie perguntas e comentários para Paul no endereço: [email protected]. Paul DiLascia é um consultor de software independente e um designer de interfaces de usuário e aplicativos Web. Ele é o autor de Windows++: Writing Reusable Windows Code in C++ (AddisonWesley, 1992). Em seu tempo livre, Paul desenvolve a PixieLib, uma biblioteca de classes do MFC disponível em seu site, http://www.dilascia.com/.
C++ foi a linguagem contemplada com a maior quantidade de modificações no Visual Studio .NET 2003, a próxima versão do Visual Studio .NET. As linguagens mais recomendadas para o desenvolvimento na plataforma .NET são sem dúvida o Visual Basic e o C#. Isto fica claro ao se constatar que tanto a documentação como os exemplos são sempre fornecidos nas das linguagens e, em muitos casos, apenas elas. Efetivamente, o C# e o VB são produtivos e fáceis de aprender e usar. Como fica então o C++? O C++ passa a ser oficialmente considerada uma linguagem mais de "baixo nível" - se é que você já não achava isto antes. Dentro desta filosofia, o C++ do Visual Studio .NET possui alguns um recurso único: É a única linguagem capaz de gerar código "não-gerenciado", ou seja, código Win32 como era desenvolvido antes da plataforma .NET. Isto continua sendo necessário, notavelmente na criação de "device-drivers". Além dito, é possível criar executáveis contendo código gerenciado e não-gerenciado, algo interessante na criação de programas que devem interagir com os dois mundos, como as próprias ferramentas de desenvolvimento. O Visual Studio 2003 traz algumas novidades a mais na linguagem C++: agora é possível criar aplicativos RAD e também gerar "código verificável". Aplicativos RAD O paradigma de programação RAD foi introduzido pelo Visual Basic em 1991 e amplamente copiado por várias outras ferramentas de desenvolvimento. Neste paradigma, colocamos "componentes" sobre
formulários, ajustamos propriedades dos componentes e interceptamos eventos dos componentes em nosso código. Infelizmente o Visual Studio .NET original não trazia suporte a desenvolvimento RAD na linguagem C++. Com o Visual Studio .NET 2003 é possível criar aplicativos "RAD" em C++. Podemos criar tanto "Windows Forms Applications" como "Windows Control Libraries":
Veja a janela principal do projeto C++. Observe que podemos alterar propriedades na janela da direita:
Observe que os componentes possuem diversos eventos, como no caso do Button1 mostrado a seguir:
Os eventos mostrados acima são eventos dos próprios componentes da biblioteca .NET Framework. Veja o código para processar o evento Click do botão:
Código Verificável Todo programa criado pelo compilador VB.NET e também a maioria dos programas criados pelo compilador C# é dito "verificável". Isto quer dizer que o compilador JIT pode, em tempo de execução/compilação verificar e garantir que o programa não faça nenhuma operação que possa comprometer a segurança e integridade do sistema. Pode parecer estranho, mas existem instruções MSIL capazes de abrir brechas na segurança do sistema, como por exemplo, para manuseio direto de ponteiros ou manuseio da pilha da CPU. Estas instruções são necessárias em alguns casos, como por exemplo para que a própria biblioteca chame a API do Windows. Programas que contem estas instruções são ditos "não-verificáveis". Evidentemente é necessário um privilégio especial de segurança para rodar programas não-verificáveis. O compilador C++ do Visual Studio 2002 sempre gera código não-verificável, o que é sem dúvida uma deficiência. Já o Visual Studio 2003 pode gerar código verificável, desde que se façam alguns ajustes de compilação. Criar código verificável exige uma seqüência de ajustes, documentada sob o tópico "Producing Verifiable Components with Managed Extensions for C++":
Conclusão O C++ foi significativamente melhorado no Visual Studio .NET 2003 e com certeza agradará os seus entusiastas, tanto com relação ao uso de recursos RAD como à criação de código verificável.