Ein Feature das mit großer Wahrscheinlichkeit in zukünftigen Versionen von .NET noch implementiert werden wird, ist (managed/inline-) Assembler in seine Projekte einzubinden. In einigen Hochsprachen mit “echtem” Compiler ist dies bereits seit vielen Jahren Standard: C++, TurboPascal, FreePascal, Delphi, freeBASIC, usw.
Zudem findet man in alten mächtigen Source-Code-Archiven oft bis zur Perversion optimierte Algorithmen, die der Autor bereits in Inline-ASM implementiert hat. Es wäre doch zu schade diese nicht weiter nutzen zu können…
Delphi erlaubt es mit sehr wenig Aufwand einen Quelltext direkt als COM-Programmbibliothek zu kompilieren. Heraus kommt eine dll-Datei (hier: “InString_dll.dll”) die ich nun in mein .NET-Projekt einbinde mittels:
Einen Haken hat das (vor)kompilieren von Inline-Assembler: Es gibt keine Gewährleistung, dass diese Programmbibliothek auch auf anderen Prozessorarchitekturen bzw. -familien ausgeführt werden kann, als auf der sie kompiliert wurde.
Exemplarisch habe ich dafür die .NET-Anwendung auf einem AMD Athlon X2 64bit ausgeführt: der JIT-Debugger spuckt eine Exception aus, die mir eine ungültige Typenumwandlung bestätigt.
Auf einem Intel Pentium 4 HT wiederum lief der Quelltext einwandfrei.
Anmerkung dazu: Die Programmbibliothek habe ich auf einer Intel Pentium M kompiliert, die bekannterweise nicht aus der Familie der NetBurst-Architekturen stammt.
Nachtrag: Auf einem Pentium Xeon (NetBurst) lief die Anwendung ebenfalls einwandfrei.
Möchte man mit C# wie in Delphi den aktuellen Winamp-Titel auslesen (siehe vorheriger Artikel), kann man sich prinzipiell den selben Methoden bedienen. Der Zugriff auf die WinAPI gestaltet sich allerdings etwas trickiger. Dieser Snippet darf privat und kommerziell verwendet werden. Ich würde mich in beiden Fällen sehr über Feedback freuen!
Um mit Delphi den aktuellen Winamp-Titel auszulesen benötigt man nur eine kleine Funktion. Der Zugriff erfolgt über die WinAPI.
function GetWinampFilename(): String;
var
hwndWinamp, ProcessHandle: THandle;
dat2: array[0..500] of Char;
temp, MPointer: Cardinal;
begin
//Bitte nicht wundern, die Fensterbezeichnung ist korrekt und funktioniert bei allen Winamp-Versionen:
hwndWinamp:=FindWindow('Winamp v1.x',nil);
MPointer:= SendMessage(hwndWinamp,WM_USER,SendMessage(hwndWinamp,WM_USER,0 , 125), 212);
GetWindowThreadProcessId(hwndWinamp,ProcessHandle);
hwndWinamp:= OpenProcess(PROCESS_ALL_ACCESS,False,ProcessHandle);
ReadProcessMemory(hwndWinamp, Pointer(MPointer), @dat2,500,temp);
CloseHandle(hwndWinamp);
Result:= String(dat2);
end;
Ein großes Manko an Delphi (bis zur .NET-isierung) war/ist eine fehlende Funktion für das zuverlässige und schnelle Parsing von Strings. Folgende Funktion begleitet mich bereits seit 2002 durch fast alle Delphi-Projekte:
function Parse(Char, S: string; Count: Integer): string;
var
I: Integer;
T: string;
begin
if S[Length(S)] <> Char then
S := S + Char;
for I := 1 to Count do
begin
T := Copy(S, 0, Pos(Char, S) - 1);
S := Copy(S, Pos(Char, S) + 1, Length(S));
end;
Result := T;
end;
Den folgenden Quelltext habe ich vor etwa 2 Jahren in seiner ursprünglichen Form in einem VBA-Forum gefunden und an VB.NET angepasst um in einer ASP.NET-Anwendung on-the-fly Barcodes auszugeben.
Vorteil von Code39 ist seine große Druck- und Lesetoleranz. Zwischenzeitlich hatte ich den Sourcecode auch einmal in C# umgeschrieben. Dieser ist allerdings bei einem Festplattendefekt verloren gegangen.
Private Function MD_Barcode39(ByVal Barcode As String, ByVal PaintObj As Object, _
ByVal mLeft As Single, ByVal mTop As Single, _
ByVal mWidth As Single, ByVal mHeight As Single)
Dim Nbar As Single, Wbar As Single, Qbar As Single, NextBar As Single
Dim CountX As Single, CountY As Single
Dim Parts As Single, Pix As Single, BarCodePlus As String
Dim Stripes As String, BarType As String
Dim Mx As Single, my As Single, Sx As Single, Sy As Single
Const Nratio = 20, Wratio = 55, Qratio = 35
Dim g As System.Drawing.Graphics
Dim pB As New System.Drawing.SolidBrush(System.Drawing.Color.Black)
Dim pW As New System.Drawing.SolidBrush(System.Drawing.Color.White)
Dim color As System.Drawing.SolidBrush
'Get control size and location properties.
Sx = mLeft
Sy = mTop
Mx = mWidth
my = mHeight
g = PaintObj
'Calculate actual and relative pixels values.
Parts = (Barcode.Length + 2) * ((6 * Nratio) + (3 * Wratio) + (1 * Qratio))
Pix = (Mx / Parts)
Nbar = (20 * Pix)
Wbar = (55 * Pix)
Qbar = (35 * Pix)
'Initialize bar index and color.
NextBar = Sx
color = pW
'Pad each end of string with start/stop characters.
BarCodePlus = "*" & Barcode.ToUpper & "*"
'Walk through each character of the barcode contents.
For CountX = 0 To BarCodePlus.Length - 1
'Get Barcode 1/0 string for indexed character.
Stripes = MD_BC39(BarCodePlus.Substring(CountX, 1))
For CountY = 0 To 8
'For each 1/0, draw a wide/narrow bar.
BarType = Stripes.Substring(CountY, 1)
'Toggle the color (black/white).
If color Is pW Then
color = pB
Else
color = pW
End If
Select Case BarType
Case "1"
'Draw a wide bar.
g.FillRectangle(color, NextBar, Sy, Wbar + NextBar, my + Sy)
NextBar = NextBar + Wbar
Case "0"
'Draw a narrow bar.
g.FillRectangle(color, NextBar, Sy, Nbar + NextBar, my + Sy)
NextBar = NextBar + Nbar
End Select
Next CountY
'Toggle the color (black/white).
If color Is pW Then
color = pB
Else
color = pW
End If
'Draw intermediate "quiet" bar.
g.FillRectangle(color, NextBar, Sy, Qbar + NextBar, my + Sy)
NextBar = NextBar + Qbar
Next CountX
End Function
Function MD_BC39(ByVal CharCode As String) As String
Dim BC39(90) As String
BC39(32) = "011000100" ' space
BC39(36) = "010101000" ' $
BC39(37) = "000101010" ' %
BC39(42) = "010010100" ' * Start/Stop
BC39(43) = "010001010" ' +
BC39(45) = "010000101" ' |
BC39(46) = "110000100" ' .
BC39(47) = "010100010" ' /
BC39(48) = "000110100" ' 0
BC39(49) = "100100001" ' 1
BC39(50) = "001100001" ' 2
BC39(51) = "101100000" ' 3
BC39(52) = "000110001" ' 4
BC39(53) = "100110000" ' 5
BC39(54) = "001110000" ' 6
BC39(55) = "000100101" ' 7
BC39(56) = "100100100" ' 8
BC39(57) = "001100100" ' 9
BC39(65) = "100001001" ' A
BC39(66) = "001001001" ' B
BC39(67) = "101001000" ' C
BC39(68) = "000011001" ' D
BC39(69) = "100011000" ' E
BC39(70) = "001011000" ' F
BC39(71) = "000001101" ' G
BC39(72) = "100001100" ' H
BC39(73) = "001001100" ' I
BC39(74) = "000011100" ' J
BC39(75) = "100000011" ' K
BC39(76) = "001000011" ' L
BC39(77) = "101000010" ' M
BC39(78) = "000010011" ' N
BC39(79) = "100010010" ' O
BC39(80) = "001010010" ' P
BC39(81) = "000000111" ' Q
BC39(82) = "100000110" ' R
BC39(83) = "001000110" ' S
BC39(84) = "000010110" ' T
BC39(85) = "110000001" ' U
BC39(86) = "011000001" ' V
BC39(87) = "111000000" ' W
BC39(88) = "010010001" ' X
BC39(89) = "110010000" ' Y
BC39(90) = "011010000" ' Z
Return BC39(Asc(CharCode))
End Function
Möchte man wie ich die Barcodes in einer ASP.NET-Anwendung ausgeben, kann folgender (schlampiger) Code als kleine Anregung verwendet werden:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim barcode As String = Request("barcode")
If Len(barcode) = 0 Then barcode = ""
Dim height As String = Request("height")
If (Len(height) = 0) Or (height > 768) Then height = 75
Dim width As String = Request("width")
If (Len(width) = 0) Or (width > 1024) Then width = 250
Dim plain As Boolean = True
Dim xh, xw As Integer
If Len(Request("plain")) <> 0 Then
If Request("plain") = 1 Then
plain = True
xw = width
xh = height
height = height + 15
Else
plain = False
End If
End If
Dim bmp As New Bitmap(width, height, PixelFormat.Format24bppRgb)
Dim g As Graphics = Graphics.FromImage(bmp)
MD_Barcode39(barcode, g, 0, 0, width, height)
If plain = True Then
Dim dispText As String = barcode
Dim dispFont As New Font("arial", 15, FontStyle.Regular, GraphicsUnit.Pixel)
g.FillRectangle(Brushes.White, 0, xh, xw, 15)
g.DrawString(dispText, dispFont, Brushes.Black, New PointF(0, xh))
End If
bmp.Save(Response.OutputStream, ImageFormat.Jpeg)
bmp.Dispose()
Response.End()
End Sub
In die @Page-Direktive muss noch ContentType=”image/jpeg” hinzugefügt werden:
Folgenden Auszug fand ich im Buch “Einführung in die Microcomputer-Technik” (1982) von Adam Osborne (ISBN 3-921 803-12-8):
Die neueste Entwicklung auf dem Gebiet der Halbleiterspeicher sind Blasenspeicher. Blasenspeicher arbeiten mit einem dünnen Film eines magnetisierbaren Materials, das künstlich auf ein nicht-magnetisches Trägermaterial aufgebracht wurde. In diesem magnetischen Film können an den Knotenpunkten eines Gitternetzes mikroskopisch kleine Magnetisierungszonen gebildet werden, deren Polarisierung entgegengesetzt zu der des dünnen Filmmaterials ist, wie folgende Darstellung zeigt:
Blasenspeicher Abb.1
Diese Darstellung zeigt willkürlich Zonen mit Nordpolarisierung in einem magnetischen Film mit Südpolarisierung. Diese Zonen der Nordpolarisierung werden magnetische Blasen genannt; Vorhandensein oder Abwesenheit einer Blase dienen zur Darstellung der Werte 1 oder 0 eines Bit. […]
Desweiteren:
Blasenspeicher
Die weiter oben erwähnten Blasenspeicher arbeiten teilweise seriell und teilweise mit wahlfreiem Zugriff. Die Magnetblasen an den Knotenpunkten der Gitternetze sind jeweils zu Schleifen zusammengefaßt, wie folgende Darstellung zeigt:
Blasenspeicher Abb.2
Die Knotenpunkte der Gitternetze kann man sich als Ablagestellen für Magnetblasen vorstellen. Durch hier nicht dargestellte Anordnungen außerhalb des Gitternetzes werden vorhandene Magnetblasen veranlaßt, auf Schleifenbahnen umzulaufen. Bei diesen Umläufen wandern die Magnetblasen von einem Gitterpunkt zum nächsten und durchlaufen schließlich ein “Fenster”, wie folgende Darstellung zeigt:
Blasenspeicher Abb.3
Zum Zugriff auf jedes einzelne Bit in einer Schleife wird nicht jeder Gitterpunkt auf das Vorhandensein einer Magnetblase abgefragt, sondern der Schleifenumlauf solange fortgesetzt, bis die Magnetblase des gewünschten Gitterpunkts im Fenster steht und gelesen werden kann. Die Adresse eines Bits besteht daher aus der Angabe einer Schleifennummer und der Angabe der Position des Bits innerhalb der Schleife. Ein typischer Blasenspeicher besteht auszahlreichen Schleifen. In einem derartigen Baustein kann jede Schleife wahlfrei angesprochen werden, aber der Zugriff auf die einzelnen Bits (Blasen) innerhalb einer Schleife erfolgt seriell.
[…]
Trotz der Tatsache, daß der Zugriff auf Blasenspeicher teilweise seriell erfolgt, finden Sie dennoch die ungenaue Bezeichnung “Blasen-RAM-Speicher”.
Blasenspeicher werden bis jetzt noch nicht in großem Umfang eingesetzt, da sie nur bei sehr großen Speicherkapazitäten wirtschaftlich herzustellen sind; außerdem sind sie als Speicher teilweise mit seriellem Zugriff relativ langsam. In Mikrocomputern werden daher ROM- und RAM-Speicher mit schnelleren Technologien verwendet. […]
Weitere Informationen können der Wikipedia entnommen werden.
Die letzte Woche war ich häufig damit beschäftigt, “Flaschenhälse” in meinen Quellcodes zu finden. Ein Tool das mir dabei unglaublich weitergeholfen hat nennt sich:
redgate ANTS Profiler
Der ANTS Profiler ist ein proprietäres .NET Tool der Firma Red Gate® Software Ltd. Da die Software im Framework 2.0 entwickelt wurde, benötigt man ebendieses für die Installation. Nach dem Download und der Installation, besitzt das Visual Studio eine Kladde mehr, mit der der ANTS Profiler aufgerufen wird.
Zeitgleich mit dem Aufruf wird die eigene Anwendung gestartet und der Profiler beginnt mit seinen Aufzeichnungen. Da die Anwendung in der Zeit unter “verschärfter Beobachtung” steht, arbeiten Algorithmen in der Regel viel zeitintensiver.
Nach Beendigung der eigenen Anwendung (z.B. durch manuelles Schließen oder automatisches Schließen einer Konsolenanwendung) wertet der Profiler seine Untersuchungen aus und stellt sie folgendermaßen dar:
Im oberen Diagramm wird die Prozessorauslastung und die Lese- und Schreibzugriffe dargestellt. Im Bereich darunter kann man in einem hierarchischen Balkendiagramm sehen welche Klassen/Funktionen die meiste Rechenzeit in Anspruch genommen haben.
Im untersten Fenster ist das, was für den Programmierer allerdings am interessantesten sein dürfte: eine zeilenweise Zeitmessung des Quellcodes. Hier kommen teilweise Flaschenhälse zum Vorschein, die zwar absolut logisch sein mögen, aber aufgrund der täglichen Programmierroutine gerne vernachlässigt werden. Zum Beispiel das generieren neuer Objekte mit “new” in Schleifeniterationen oder verdeckte SQL-Querys in unscheinbaren LINQ-Funktionen (z.B. Count();).
Die Software kostet in der Standardversion ohne Support: 295€ (Stand Februar 2009)
Nachtrag am 04.03.2009:
Soeben hat mich ein freundlicher Mitarbeiter der Firma redgate angerufen und mir angeboten die im Juli 2009 erscheinende neue Version (.NET 3.5) bereits vorab zu testen. Dies habe ich natürlich wahrgenommen und hoffe in den nächsten Tagen ein wenig schlauer zu sein.
Ein Feature das an der aktuellen Fritz!Box 7270 groß bei mir punkten konnte ist das gut funktionierende Callthrough. Bis vor kurzem bestand mein Equipment aus einer betagteren ISDN-Telefonanlage, einem flashed Speedport W500V als ATA und einer Auerswald “ABox” für das Calltrough zwischen VoIP und Festnetz.
Im folgenden HowTo möchte ich erläutern wie ich die Fritzbox und den Asterisk-Server vereinen konnte.
Zuallererst sollte man sich auf dem Asterisk einen neuen SIP-Account für die Fritz!Box anlegen. Meiner Fritz!Box habe ich die MSN 6666 gegeben.
Einstellen der Callthrough-Parameter: PIN eingeben & merken! Optional kann hier auch eingestellt werden, dass nur Anrufe von bestimmten Anrufern entgegengenommen werden sollen. Da ich einen Festnetzinbound auf mehreren VoIP-Nummern klingeln lassen ist der Haken bei mir unabdinglich, da sonst die Fritz!Box ALLE (auch externe!) Anrufe entgegennimmt.
Fritz!Box 7270 Callthrough Settings
Jetzt können die Einstellungen getestet werden: dazu muss mit einem beliebigen SIP-Client die Nummer der Fritz!Box angerufen werden. Es ertönen 4 Töne -> PIN eingeben -> Bestätigungston. Sollte dies nicht auf anhieb funktionieren, sollten folgende Einstellungen überprüft werden: DTMF (am VoIP-Client und an den Settings der Fritz!Box), Callthrough-Sicherheitseinstellungen und “Nur Rufe von folgenden Rufnummern annehmen”.
Da ich es als sehr lästig empfand immer die PIN einzugeben, habe ich diesen Vorgang vom Asterisk-Server automatisieren lassen. Dazu muss folgende Extension in die extension.conf eingetragen werden:
Erklärung: Es ist möglich dem Dial-Befehl ein Makro zu übergeben. In diesem Fall soll er nach Entgegennehmen des Anrufes 0,5 Sekunden warten (w) und nacheinander die PIN “1234” als DTMF-Ton übermitteln. Trägt man dies ein, sollte man kurze Zeit nach dem Wählen der “6666” den Quittungston der Fritz!Box hören. Über eine vorgewählte “0” kann man sich nun Amt holen und über seinen Festnetzanschluß telefonieren.
Natürlich sind auch weitere “Spielereien” mit solchen Makros möglich. Folgende zusätzliche Wahlregel erlaubt es mir nach wählen der “6669” über VoIP mit dem DECT-Telefon verbunden zu werden das an der Fritz!Box angemeldet ist:
Erklärung: Wie oben bereits erklärt wird der PIN “1234” übermittelt, eine Sekunde gewartet (ww) und anschließend der interne Teilnehmer “610” (=DECT) angerufen. Natürlich sind auch Weiterleitungen und Konferenzen damit möglich.
Viel Spaß beim Rumexperimentieren!
This website uses a Hackadelic PlugIn, Hackadelic SEO Table Of Contents 1.7.3.