Die vorherigen Artikel über Shared Memory in verwaltetem Code waren alle sehr abstrakt gehalten um mit jedem möglichen .NET Objekt umgehen zu können. Heute will ich eine praktische Anwendung zeigen die genau diese Technologie, aber weniger abstrakt, nutzt. Manche unter euch kennen und nutzen SpeedFan zum überwachen der diversen Sensoren des jeweiligen Computers. Wie ich schon in einem der früheren Artikel über shared Memory erwähnt habe, wird in der nicht verwalteten Welt reger Gebrauch von gemeinsam genutzten Speicher gemacht. SpeedFan stellt eben so ein Segment mit den relevanten Daten zur Verfügung. Das wirklich schwierige am verarbeiten eines solchen shared Memory Segment ist eine verlässliche Dokumentation in welcher Form die Daten im shared Memory Segment denn vorliegen. Im Falle von SpeedFan wird eine Strukture, bestehend aus verschiedenen unsigned short intWerten sowie einigen signed int[32] Arrays verwendet. In diesem Forenpost wird die Nutzung dieser Struktur diskutiert. Hier das Listing der von SpeedFan verwendeten Struktur:
/////////////////////////////////////////////////
/// SpeedFan specific declarations
/// Thanks to : Alfredo Milani Comparetti
// Shared memory name and handles
TCHAR szName[]=TEXT("SFSharedMemory_ALM");
HANDLE hMapFile;
LPCTSTR pBuf;
bool isOk = false;
// Strucure of the shared block
#pragma pack(push, 1)
typedef struct TSharedMem_
{
unsigned short int version;
unsigned short int flags;
signed int MemSize;
HANDLE handle;
unsigned short int NumTemps;
unsigned short int NumFans;
unsigned short int NumVolts;
signed int temps[32];
signed int fans[32];
signed int volts[32];
}TSharedMem;
#pragma pack(pop)
Wie immer liegt hier der Teufel im Detail. Sehr wichtig, und bestimmt gerne überlesen, ist die Anweisung: #pragma pack(push, 1). Wer sie gleich von Anfang an beachtet, erspart sich fiel Ärger. Doch dazu später mehr. Als erstes wird die C++ Struktur in verwaltetem Code nachgebildet. Hier vorab das Listing der Struktur:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct SFData
{
public ushort version;
public ushort flags;
public int memSize;
public IntPtr handle;
public ushort numTemps;
public ushort numFans;
public ushort numVolts;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public int[] temps;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public int[] fans;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public int[] volts;
}Wie im obigen Listing zu sehen ist, wird im StructLayoutAttribute das Feld Pack mit dem Wert 1 verwendet um die gleiche Komprimierung und Ausrichtung im Speicher zu verwenden mit der die Struktur von SpeedFan erstellt wurde. Ohne diese Compileranweisung im C++ Code würde z.B. der VC++ Compiler standardmäßig den Wert 8 verwenden. Wenn diese Information fehlt, würden die Werte nach dem Marshalling in der verwalteten Struktur nicht den Werten entsprechen die ursprünglich in die C++ Struktur geschrieben wurden. Umständliche Umrechnungen oder gar falsche Daten währen die Folge. Zu beachten ist unbedingt auch die Länge der Integer Arrays aus der C++ Struktur. Beim Marshalling der Arrays muss unbedingt das Feld SizeConst mit dem Wert aus der C++ Struktur, in diesem Fall 32, belegt werden. Wie schon eingangs erwähnt, eine verlässliche und vollständige Dokumentation ist meist das schwierigste beim verarbeiten von fremd generierten Daten. Nachdem die Struktur erstellt wurde, kann das lesen aus dem shared Memory Segment in die erzeugte Struktur beginnen. Das ist dieses mal wesentlich einfacher, schon fast trivial, als bei den abstrakten shared Memory Operationen. Da aus der Dokumentation der Namen des shared Memory Segment bekannt ist, kann einfach mit OpenFileMapping ein Handle zum benannten FileMapping Objekt erzeugt werden. Mit diesem Handle wird mittels MapViewOfFile ein Zeiger auf das shared Memory Segment erzeugt. Da die Struktur im Speicher bekannt ist und auch ein Zeiger auf diese vorliegt, kann nun einfach mit der Marshal.PtrToStructure-Methode der Speicherinhalt in die Struktur kopiert werden. Hier die Umsetzung in Code:
public SFData GetData()
{
if (this.sharedMemoryPointer == IntPtr.Zero)
{
throw new SharedMemoryException(
"Auf das shared Memory Segment kann nicht zugegriffen werden.");
}
return (SFData)Marshal.PtrToStructure(
this.sharedMemoryPointer,
typeof(SFData));
}Nachdem die Daten jetzt in einer verwalteten Struktur vorliegen, kann mit ihnen wie gewohnt verfahren werden.
Der Gedanke hinter der Umsetzung dieses Projekts war eine freie Bibliothek für alle möglichen Anwendungen zu schaffen. Ich weis dass schon einige Gadgets erstellt haben die das Shared Memory Segment von SpeedFan nutzen. Diese Gadgets oder Anwendungen unterliegen meist einem Copyright und machen damit eigene Anpassungen nahezu unmöglich. Meine Interessen liegen nicht beim erstellen von grafischen Anwendungen. Außerdem habe habe kein Händchen für das Oberflächendesign. Doch was ich bis jetzt so an Gadgets und grafischen Tools gesehen habe, gibt es anscheinend genug Leute die Spaß daran haben grafische Oberflächen zu bauen. Genau diese Leute will ich mit dieser, und vielleicht noch weiteren Bibliotheken, ansprechen. Sowohl die Bibliothek als auch der Quellcode unterliegen der MIT-Lizenz und sind somit fast uneingeschränkt nutzbar. Lediglich ein Verweis auf den Original Code muss vorhanden sein.
Gerade um die Bibliothek in Gadgets zu verwenden ist die Klasse, welche die Werte aus dem Shared Memory Segment liefert, für COM sichtbar und dadurch mit JavaScript zu verwenden.
[ComVisible(true),
Guid("8a31250b-7c0f-469e-b874-5f53c81728f7"),
ProgId("SpeedFanConnector.SFConnector"),
ClassInterface(ClassInterfaceType.None)]
public partial class SFConnector : IDisposableEine Dokumentation der API ist als chm-Datei vorhanden. Zum Testen der Bibliothek auf dem eigenen Computer liegt eine Konsolenanwendung bei die, einfach vorab ausgeführt, schon mögliche Probleme oder die einwandfreie Funktion zeigen kann. Hier zwei Screen-Shots. Einmal von der Konsolenanwendung und zum anderen von SpeedFan um die Funktion der Bibliothek zu zeigen.


Wie hier zu sehen ist, sollten alle Werte die im SpeedFan Dialog zu sehen sind auch in der Konsolenanwendung in der richtigen Reihenfolge gezeigt werden. Es wird vorausgesetzt das SpeedFan Installiert ist und ausgeführt wird. ich habe mit Absicht darauf verzichtet SpeedFan aus der Bibliothek zu starten. Unter XP ginge das ja noch einwandfrei, doch unter Vista könnte es Probleme mit der UAC geben. Statt dessen habe ich die zwei statischen Eigenschaften SpeedFanAlreadyRunning und SpeedFanPath eingebaut. Sie sind deshalb statisch um bereits vor der Initialisierung prüfen zu können ob SpeedFan überhaupt installiert ist, bzw. gerade ausgeführt wird. Sollte eine Initialisierung der Klasse SFConnector erfolgen ohne das SpeedFan ausgeführt wird, führt dies unweigerlich zu einer SharedMemoryException da das shared Memory Segment von SpeedFan nicht verfügbar ist.
Die Eigenschaft SpeedFanPath sucht in der Registrierung das Installationsverzeichnis von SpeedFan und prüft anschließend ob die Datei speedfan.exe im angegebenen Verzeichnis vorhanden ist. Wird sie gefunden wird der komplette Pfad zurück gegeben. Wird entweder der Pfad in der Registrierung oder die Datei nicht gefunden, wird null zurückgegeben. Die Eigenschaft SpeedFanAlreadyRunning prüft ob der Prozess ausgeführt wird. Ist dies der Fall wird true zurückgegeben, anderenfalls false. Es wird allerdings auch false zurückgegeben falls keine ausreichende Berechtigung vorliegt um die laufenden Prozesse abzufragen, da eine evtl. auftretende SecurityException intern behandelt wird.
Wer Interesse hat, kann sich die Bibliothek herunterladen. Außer der Konsolenanwendung ist noch die Komplette Dokumentation der API enthalten. Zum besseren Verständnis der Arbeitsweise, habe ich auch alle privaten Member sowie alle internen Klassen dokumentiert. Über Rückmeldungen würde ich mich freuen. Kritik ist ausdrücklich erwünscht.
SpeedFanConnector
Sollte Interesse bestehen, kann ich auch gerne den Quellcode bereitstellen. Einfach kurze Mail senden.
Wenn ihnen der Artikel gefallen hat oder er für sie hilfreich war, bitte "kicken" sie ihn.
