Wenn in einem Access Report Summen falsch gebildet werden, kann das sehr viele verschiedene Ursachen haben. Meist handelt es sich um Rundungs- und Typenfehler. In dem vorliegenden Fall war die Ursache allerdings viel banaler. Als ich einen Report auf meinem PC generierte stimmte die Summenbildung (hier: 2307t). Ebenso auf dem Rechner eines Kollegen. Erst als wir die Datenbank dann schließlich auf dem Rechner des Kunden installierten, kamen utopische Zahlen heraus. Sie reichten je nach Seleketion von ca. -63000t bis 180000t. Eine Exception wurde zudem nicht ausgeworfen.
(Leider habe ich von dem fehlerhaften Report keinen Screenshot machen können)
Die Lösung war nach einigem rätseln denkbar einfach, denn es handelte sich nicht um einen Fehler im eigentlichen Sinne. Der Kunde hatte nicht die aktuellste Version von Access (? => Info).
Nachdem mein Kollege ihm das Service Pack 3 installiert hatte (Download), funktionierte auch wieder alles. Ergo: Kunde glücklich.
—
Da Access 2003 nun nicht Stand der Technik ist, möchte ich noch kurz anreißen wie sich Versionsunterschiede bei modernen Microsoft Technologien verhalten. Einer der häufigsten Fehler den ich bei sog. “strikten” Kunden zu sehen bekomme ist der folgende:
System.IO.FileNotFoundException: Die Datei oder Assembly System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 oder eine Abhängigkeit davon wurde nicht gefunden. Das System kann die angegebene Datei nicht finden.
Strikt deswegen, weil es Unternehmen gibt, die Service Packs oder Updates im Unternehmensnetzwerk nicht freigeben. Der obige Fehler deutet im Grunde nur daraufhin, dass die “System.Core”, also der Kern des .NET Frameworks in einer bestimmten Version (3.5.0.0) nicht vorliegt.
Das mag für Laien vielleicht verwirrend sein, aber es ist für Windowsentwickler möglich, Anwendung unter verschiedenen .NET Framework Versionen bereitzustellen. So passiert es nicht selten, dass Programme die von der eingesetzten Technologie mit der Version “2.0.0.0” zwar auskämen, trotzdem mit der Ausführungsvoraussetzung “3.5.0.0” erstellt werden.
Schafft die Installation des .NET Frameworks 3.5 (Download) daher keine Abhilfe oder ist es prinzipiell im Unternehmen nicht erlaubt dieses zu upgraden, kann es bei Individualsoftware manchmal ratsam sein, die Entwickler zu bitten, das Projekt mit einem niedrigeren Zielframework (2.0, 3.0, 3.5, später 4.0) bereitzustellen.
Fazit: Microsoft hat also auch hier aus den Fehlern der Vergangenheit gelernt. Ich hätte mir trozdem sehr gewünscht, wenn Access eine Meldung ausgegeben hätte, die daraufhin deutet dass die ausgelieferte Datenbank mit einer späteren Access Version erstellt wurde.
Der folgende Code ist eine Übersetzung aus einer sehr häufig verwendeten Delphi-Funktion. Sie war Grundlage vieler Projekte, da Delphi von Haus aus keine Parsingmethode anbot.
public static string Parse(string Char, string S, int Count)
{
string T = string.Empty;
if (S[S.Length-1].ToString() != Char)
S += Char;
for (int I = 1; I <= Count; I++)
{
if (S.Length == 0) return "";
T = S.Substring(0, S.IndexOf(Char));
S = S.Substring(S.IndexOf(Char)+1);
}
return T;
}
Ich gebe zu, dass das nicht gerade die effizienteste Methode ist einen String zu zerlegen, aber die Routine ist äusserst effektiv und einfach aufgebaut. Fehlverwendung ist praktisch ausgeschlossen.
Today is the first release of my InkaOffice Password Recovery Tool v1.0.0.
Please notice, that this tool requires a valid InkaOffice license to use, due to legal issues.
Usage:
Execute setup.exe
Change directory where have installed the application
Execute “InkaOfficePasswordRecoverTool.exe”
If the application can find your “Settings.xml” configuration file, you get your recovered password directly.
If not so, you need to specify the path where your “Settings.xml” is located. The most times you find it in your local application data (e.g. Windows 7: “C:\Users\YOUR NAME\AppData\Local\Micros-Fidelio\InkaOffice\1.X“. Please notice, that I developed this tool with the main version “1.2”. Other versions should work, too.
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!
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:
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.