Introdução
Periodicamente recebo e-mails de desenvolvedores pedindo ajuda para implementar funções do Microsoft Win32 em suas aplicações. Nos idos do Visual Basic 6.0, a procura por funções de API era ainda maior. Isso mudou porque o .NET Framework, desde sua versão 1.0, implementou diversas funções que antes só conseguíamos utilizar através da API Microsoft Win32. O Microsoft Win32 expõe diversas funções através de um conjunto de bibliotecas. As bibliotecas mais utilizadas e suas finalidades são apresentadas na Tabela 1.
| Biblioteca |
Descrição |
| Gdi32.dll |
Graphics Device Interface (GDI). Esta biblioteca implementa funções para dispositivos de saída, gráficos e gerenciamento de fontes. |
| Kernel32.dll |
Funções de baixo nível, como gerenciamento de memória e manipulação de recursos e dispositivos do sistema. |
| User32.dll |
Funções de gerenciamento do Windows como tratamento de mensagens, timers, menus, etc. |
Tabela 1: Bibliotecas do Microsoft Win32
Microsoft Win32 API e o .NET Framework
Muitos desenvolvedores perguntam: “Por que temos que codificar funções que já estão implementadas no Windows? Por que o .NET Framework não provê essas funcionalidades?”. Na verdade uma boa parte das funções do Win32, foi portada para o .NET, mas o conjunto completo dessas funções é muito grande, e muitas funções foram deixadas de fora. A Microsoft disponibilizou um documento que estabelece o mapeamento das funções do Win32 com as classes do .NET, esse documento está disponível na seguinte URL: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/win32map.asp.
Platform Invoke (P/Invoke)
Platform Invocation Services (PInvoke) permite que uma aplicação, implementada com código gerenciado (Manage Code), possa fazer chamadas para funções encapsuladas em bibliotecas (DLL ) implementadas com código não-gerenciado (Unmanage Code). Através do P/Invoke, com o uso correto de alguns atributos e parâmetros, podemos invocar qualquer função do Microsoft Win32 ou de outra biblioteca. O fragmento de código abaixo apresenta uma chamada para a função externa CascadeWindows, armazenada na biblioteca User32.dll.
[DllImport("user32.dll")]
static extern ushort CascadeWindows(IntPtr hwndParent, uint wHow, IntPtr lpRect, uint cKids, IntPtr [] lpKids);
Observe que o atributo contendo a declaração DllImport(“biblioteca.dll”) deve preceder a declaração do método ou função que deve ser obrigatoriamente definido como static extern.
Não perca tempo, Seja produtivo!
É muito importante que o desenvolvedor utilize funções externas somente quando necessário. Sugiro uma boa pesquisa nas classes do .NET Framework antes de optar pela API do Win32. Não perca seu tempo tentando implementar uma função de API quando ela já está disponível no .NET Framework.
Unmanage Code vs. Segurança
Outro ponto a ser considerado quando se planeja utilizar funções de código não-gerenciado, refere-se à segurança. Você deve certificar-se de que o usuário da sua aplicação ou o seu componente possui os privilégios necessários para executar código não-gerenciado.
Exemplos Práticos
Neste artigo, vou apresentar quatro funções do Microsoft Win32. Estas funções não foram portadas para o .NET Framework e sua aplicabilidade prática é relativamente simples. A Tabela 2 apresenta as funções que serão utilizadas no aplicativo de exemplo.
| Função |
Biblioteca |
Descrição |
| SetLocalTime |
Kernel32.dll |
Esta função permite que você altere a data e hora do Windows. |
| Beep |
Kernel32.dll |
Esta função emite um Beep sonoro que pode ter sua freqüência e duração definidas programaticamente. |
| MessageBeep |
User32.dll |
Esta função executa os sons associados aos tipos de mensagem do Windows (Exclamação, Pergunta, Erro, etc). |
| GetDiskFreeSpace |
Kernel32.dll |
Esta função permite obter informações específicas sobre uma unidade de disco (setores, clusters, espaço total e espaço livre). Existe uma função bastante próxima desta chamada GetDiskFreeSpaceEx que retorna apenas o espaço livre de uma unidade de disco, porém esta função já foi incorporada ao .NET Framework através do Namespace System.IO. |
Tabela 2: Funções do Win32 utilizadas no exemplo.
Referência ao Namespace
Para utilizarmos funções externas, como as funções do Microsoft Win32, precisamos estabelecer uma referência ao Namespace System.Runtime.InteropServices. A linha de código abaixo apresenta a referência necessária.
using System.Runtime.InteropServices;
Alterando Data e Hora do Sistema com a função SetLocalTime
O Quadro 1 apresenta o código necessário para implementar a função SetLocalTime. Esta função é disponibilizada através da biblioteca kernel32.dll. Observe que a declaração do método externo é acompanhada do atributo DllImport(“kernel32.dll”). Para esta função o desenvolvedor precisa criar a estrutura SystemTime. Note que esta estrutura também possui um atributo chamado StructLayout. Este atributo controla o layout de um objeto quando é passado para o ambiente não-gerenciado, o parâmetro LayoutKind é representado por uma Enumeração. Os membros desta Enumeração são apresentados na Tabela 3.
| Membro |
Descrição |
| Auto |
O runtime escolhe automaticamente o layout apropriado para os objetos quando são expostos. Contudo este método não deve ser utilizado para expor objetos ao ambiente não-gerenciado, isso geraria uma Exception. |
| Explicit |
A posição precisa de cada membro do objeto em ambiente não-gerenciado deve ser explicitamente especificada através da propriedade FieldOffsetAttribute. |
\
| Sequential |
Os membros do objeto são apresentados sequencialmente, na mesma ordem que serão expostos ao ambiente não-gerenciado. |
Tabela 3: A Enumeração LayoutKind.
#region SetLocalTime Function
[DllImport("kernel32.dll")]
public static extern bool SetLocalTime(ref SystemTime sysTime);
public static void SetNewDateTime(DateTime dt)
{
SystemTime sysTime = new SystemTime();
sysTime.wYear = (ushort)dt.Year;
sysTime.wMonth = (ushort)dt.Month;
sysTime.wDay = (ushort)dt.Day;
sysTime.wHour = (ushort)dt.Hour;
sysTime.wMinute = (ushort)dt.Minute;
sysTime.wSecond = (ushort)dt.Second;
sysTime.wMiliseconds = (ushort)dt.Millisecond;
SetLocalTime(ref sysTime);
}
[StructLayout(LayoutKind.Sequential)]
public struct SystemTime
{
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMiliseconds;
}
private void cmdSetLocalTime_Click(object sender, EventArgs e)
{
DateTime myDate = new DateTime(dtpDate.Value.Year,
dtpDate.Value.Month,
dtpDate.Value.Day,
dtpTime.Value.Hour,
dtpTime.Value.Minute,
dtpTime.Value.Second);
SetNewDateTime(myDate);
}
private void frmSetLocalTime_Load(object sender, EventArgs e)
{
dtpDate.Value = System.DateTime.Today;
dtpTime.Value = System.DateTime.Now;
}
private void tmrLocal_Tick(object sender, EventArgs e)
{
lblDateTime.Text = DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToLongTimeString();
}
#endregion
Quadro 1: Implementando a função SetLocalTime
Emitindo Beep Sonoros no C#
Esta função é bastante simples e útil em algumas situações. Com a função Beep exposta pela biblioteca kernel32.dll, você pode emitir um aviso sonoro, controlando sua freqüência e duração. Semelhante à função Beep encontrada no Visual Basic. O Quadro 2 apresenta o código necessário para sua implementação.
#region Beep Function
[DllImport("kernel32.dll")]
public static extern bool Beep(int frequency, int duration);
private void cmdBeep_Click(object sender, EventArgs e)
{
Beep((int)upFrequency.Value, (int)upDuration.Value);
}
public enum BeepType
{
SimpleBeep = -1,
IconAsterisk = 0x00000040,
IconExclamation = 0x00000030,
IconHand = 0x00000010,
IconQuestion = 0x00000020,
Ok = 0x00000000,
}
#endregion
Quadro 2: A função Beep
Sons do Windows com a função MessageBeep
Semelhante ao exemplo anterior, a função MessageBeep, exposta pela bilbioteca user32.dll, emite avisos sonoros de acordo com o tipo de mensagem que você quer exibir. Os mesmos tipos que são definidos quando você codifica uma Caixa de Mensagem. Você pode executar um aviso sonoro simples, um aviso de exclamação ou de interrogação, e assim por diante. Os sons acionados por esta função são os mesmos configurados pelo usuário nas propriedades de som do Painel de Controle. Desta forma, se não houver som configurado para interrogação, por exemplo, a função não será executada. Observe o código desta implementação no Quadro 3.
#region MessageBeep Function
[DllImport("user32.dll")]
public static extern bool MessageBeep(BeepType beepType);
private void cmdMessageBeep_Click(object sender, EventArgs e)
{
switch (cboBeepType.Text)
{
case "Ok":
MessageBeep(BeepType.Ok);
break;
case "SimpleBeep":
MessageBeep(BeepType.SimpleBeep);
break;
case "IconAsterisk":
MessageBeep(BeepType.IconAsterisk);
break;
case "IconExclamation":
MessageBeep(BeepType.IconExclamation);
break;
case "IconQuestion":
MessageBeep(BeepType.IconQuestion);
break;
case "IconHand":
MessageBeep(BeepType.IconHand);
break;
default:
MessageBeep(BeepType.Ok);
break;
}
}
#endregion
Quadro 3: A função MessageBeep
Verificando o Espaço em Disco com a função GetDiskFreeSpace
Esta função, exposta pela biblioteca kernel32.dll, permite ao desenvolvedor verificar o espaço total e espaço livre de uma unidade de disco. As informações fornecidas por esta função incluem: Número de Setores por Cluster, Número de Bytes por Setor, Número de Clusters Livres e o Número Total de Clusters. Agora, se você quer saber apenas o espaço livre em uma unidade de disco, você pode utilizar a classe System.Io.DriveInfo do .NET Framework 2.0 para obter essa informação. Essa classe provê a mesma funcionalidade exposta pela função semelhante chamada GetDiskFreeSpaceEx do Win32. O Quadro 4 apresenta o código para implementação desta função.
#region GetDiskFreeSpace Function
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool GetDiskFreeSpace(string lpRootPathName,
out uint lpSectorsPerCluster,
out uint lpBytesPerSector,
out uint lpNumberOfFreeClusters,
out uint lpTotalNumberOfClusters);
private void cmdGetDiskFreeSpace_Click(object sender, EventArgs e)
{
uint SectorsPerCluster;
uint BytesPerSector;
uint NumberOfFreeClusters;
uint TotalNumberOfClusters;
GetDiskFreeSpace("C:\\", out SectorsPerCluster, out BytesPerSector,
out NumberOfFreeClusters, out TotalNumberOfClusters);
txtSectorsPerCluster.Text = SectorsPerCluster.ToString();
txtBytesPerSector.Text = BytesPerSector.ToString();
txtNumberOfFreeClusters.Text = NumberOfFreeClusters.ToString();
txtTotalNumberOfClusters.Text = TotalNumberOfClusters.ToString();
long Bytes = (long)NumberOfFreeClusters * SectorsPerCluster * BytesPerSector;
txtFreeSpaceInBytes.Text = Bytes.ToString();
decimal KiloBytes = (decimal)Bytes / 1024;
txtFreeSpaceInKilobytes.Text = KiloBytes.ToString();
decimal MegaBytes = (decimal)KiloBytes / 1024;
txtFreeSpaceInMegabytes.Text = MegaBytes.ToString();
decimal GigaBytes = (decimal)MegaBytes / 1024;
txtFreeSpaceInGigabytes.Text = GigaBytes.ToString();
}
#endregion
Quadro 4: A função GetDiskFreeSpace
Conclusão
Neste artigo você conheceu um pouco mais sobre o Microsoft Win32 e aprendeu como utilizar suas funções através do Microsoft Visual C# e o P/Invoke. Visite os links de referência. Consulte as classes do .NET Framework antes de recorrer ao uso de funções externas, não tente re-inventar a roda! Boa programação!