Jak nastavit čas ve Windows CE z VB.NET
Václav Bárta ve čtvrtek při programování pro .NET Compact Framework (což je .NET Framework pro PocketPC a jiná zařízení s Windows CE) narazil na velmi nepříjemný rozdíl mezi C# a VB.NET. Konkrétně se jedná o neschopnost VB.NET volat funkce importované z unmanaged dynamické knihovny, pokud jsou jejich parametry ukazatelé a my přikážeme Basicu místo nich předávat struktury - VB.NET to umí. Jenže v Compact Framework to prostě nefunguje a výsledkem je pád se výjimkou NotSupportedException. Velká části Windows API je proto nepřístupná, mimo jiné i funkce SetSystemTime z coredll.dll. Z C# se postižené funkce nejspíš díky ukazatelům a možnosti označit kód jako unsafe dají volat bez problémů.
Přestože VB.NET nemám rád (hlavně z iracionálních důvodů), odmítl jsem se smířit s nemožností nastavit z tohoto jazyka systémový čas. Půjde to i bez ukazatelů! Součástí namespacu System.Runtime.InteropServices je přece třída Marshal a ta poskytuje metodu StructureToPtr, která dokáže zkopírovat obsah instance objektu do bloku paměti alokovaného třeba z heapu (český výraz hromada mi nikdy nepřirostl k srdci) a odkazovaného typem IntPtr, a dokonce nechybí ani v .NET CF! Kdyby v tom nebyl jeden drobný háček - v .NET CF chybí ve třídě Marshal metody na alokování paměti - řešení by bylo na světě. Ale budiž. Když neumí pamět alokovat Marshal, můžeme použít funkce API LocalAlloc a LocalFree (ano, ty se dají úspěšně volat z VB.NET - žádný jejich parametr není ukazatel na strukturu). Zkusil jsem to tedy takto.
A výsledek? Funguje to! Pomocí metody TimeCf.SetSystemTime se dá nastavit systémový čas na PocketPC i z VB.NET. Toto řešení je vhodné i pro C#, protože odpadá nutnost označit kód jako unsafe.
Import System
Import System.Runtime.InteropServices
Public Class TimeCf
Private Class SystemTime
Public Year As Short
Public Month As Short
Public DayOfWeek As Short
Public Day As Short
Public Hour As Short
Public Minute As Short
Public Second As Short
Public MilliSecond As Short
End Class
Private Declare Function SetSystemTime Lib "coredll.dll" _
(ByVal st As IntPtr) As Integer
Private Declare Function GetLastError Lib "coredll.dll" _
() As Integer
Private Declare Function LocalAlloc Lib "coredll.dll" _
(ByVal Flags As Integer, ByVal Bytes As Integer) As IntPtr
Private Declare Function LocalFree Lib "coredll.dll" _
(ByVal Ptr As IntPtr) As IntPtr
Public Shared Sub SetSystemTime(ByVal dt As DateTime)
Dim systime As New SystemTime
systime.Year = dt.Year
systime.Month = dt.Month
systime.DayOfWeek = 0
systime.Day = dt.Day
systime.Hour = dt.Hour
systime.Minute = dt.Minute
systime.Second = dt.Second
systime.MilliSecond = dt.Millisecond
Dim bytes As Integer = Marshal.SizeOf(systime)
Dim ptr As IntPtr = LocalAlloc(0, bytes)
Try
Marshal.StructureToPtr(systime, ptr, False)
If SetSystemTime(ptr) = 0 Then
Dim e As Integer = GetLastError()
Dim m As String
m = String.Format("Cannot set system time (error {0}).", e)
Throw New Exception(m)
End If
Finally
LocalFree(ptr)
End Try
End Sub
End Class