C# para programação de sistemas: quando o alto nível encontra o baixo hardware
\n\nQuando se ouve a expressão "programação de sistemas", geralmente vêm à mente imagens de gurus barbudos programando em C ou assembly, com terminais cheios de texto verde. E C# — é "para aplicações empresariais", "para Windows Forms" e "para quem não quer se preocupar com ponteiros". Spoiler: isso é um mito.
\n\nNa verdade, C# é um canivete suíço para o programador de sistemas que quer programar rápido, com segurança, mas quando necessário — mergulhar no coração da memória não gerenciada. Neste artigo, vamos analisar como C# ajuda a resolver tarefas reais de sistemas: desde o trabalho com processos até o acesso direto à memória via WinAPI. E sim, exemplos de código serão mostrados — sem eles não dá.
Por que C# não é apenas um "brinquedo empresarial"?
\n\nProgramação de sistemas em C# não é um oxímoro. A plataforma .NET (especialmente .NET 5+) oferece ao desenvolvedor uma combinação única: desempenho próximo ao código nativo e a conveniência de uma linguagem de alto nível. Você pode escrever drivers, trabalhar com processos, alocar memória manualmente e, ao mesmo tempo, não enlouquecer com o gerenciamento de recursos.
\n\nPrincipais recursos que tornam o C# adequado para programação de sistemas:
\n- \n
- Código unsafe e ponteiros — sim, em C# você pode trabalhar com endereços diretamente, como em C++. \n
- P/Invoke — chamada de qualquer função de bibliotecas nativas (WinAPI, libc, drivers). \n
- Span
e Memory — trabalho seguro com blocos contínuos de memória sem alocações desnecessárias. \n - Native AOT — compilação para código nativo sem JIT e CLR. \n
- Interoperabilidade com C/C++ — incorporação de C# em projetos de sistemas existentes. \n
Se você achava que C# era só sobre web e bancos de dados, prepare-se para se surpreender.
\n\nCódigo unsafe: brincando com fogo, mas sob controle
\n\nA palavra-chave unsafe em C# é como um botão vermelho em um míssil nuclear. Apertou — e você pode fazer tudo: desreferenciar ponteiros, trabalhar com objetos fixos, alocar memória na pilha. Mas a responsabilidade é total.
Suponha que precisamos copiar rapidamente um array de bytes, ignorando as verificações de limites. Em código seguro, usaríamos Array.Copy, mas se estamos escrevendo um utilitário de sistema para trabalhar com um driver, cada microssegundo conta.
using System;\n\nunsafe class MemoryCopy\n{\n public static void FastCopy(byte[] source, byte[] dest, int length)\n {\n fixed (byte* pSrc = source, pDst = dest)\n {\n byte* ps = pSrc;\n byte* pd = pDst;\n for (int i = 0; i < length; i++)\n *pd++ = *ps++;\n }\n }\n}\n\nAqui usamos fixed para "fixar" os arrays gerenciados na memória, para que o coletor de lixo não os mova durante a cópia. O resultado é uma cópia em velocidade próxima ao memcpy em C. Claro, para tarefas cotidianas isso é excessivo, mas para programação de sistemas em C# é indispensável.
Quando o unsafe é justificado?
\n- \n
- Trabalho com pacotes de rede no nível de bytes brutos. \n
- Interação com hardware via DMA. \n
- Implementação de alocadores de memória próprios. \n
- Otimização de trechos críticos de código (por exemplo, processamento de vídeo de câmera). \n
P/Invoke e WinAPI: C# batendo no kernel
\n\nA verdadeira programação de sistemas é impossível sem chamar funções nativas. O C# fornece para isso o mecanismo P/Invoke (Platform Invocation Services). Quer obter uma lista de processos, gerenciar janelas ou ler a memória de outro processo? Pois não.
\n\nVamos considerar um exemplo clássico — obter a lista de processos em execução através da função WinAPI CreateToolhelp32Snapshot.
using System;\nu