Access generiert falsche Summen (Forms/Report)

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.

In C# einen String parsen

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;
        }

Beispiel:


string foo1 = "foo1#foo2#foo3#hallo"; // der Ausgangsstring
string teil1 = Parse("#", foo1, 1); // enth&auml;lt: "foo1"
string teil2 = Parse("#", foo1, 2); // enth&auml;lt: "foo2"
string teil4 = Parse("#", foo1, 4); // enth&auml;lt: "hallo"
string leer = Parse("#", foo1, 42); // enth&auml;lt: "" (leer)

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.

InkaOffice Password Recovery Tool

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.
  • Download InkaOffice Password Recovery Tool v1.0.0

    Großhochzeit: C#, Delphi, Assembler und COM

    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…

    Folgenden Code fand ich im SwissDelphiCenter:
    http://www.swissdelphicenter.ch/de/showcode.php?id=2049
    (Ein Instring-Algorithmus von Vanja Fuckar, 100% inline-ASM)

    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:

    
    [System.Runtime.InteropServices.DllImport("InString_dll.dll", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall, CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
    private static extern int InString(int StartPosition, string Source, string Pattern);
    
    

    Aufgerufen wird diese externe statische Funktion wie üblich z.B. mit:

    
    Console.WriteLine(InString(0, "Testautotesttesttest", "auto").ToString());
    
    

    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.

    C#.NET: Winamp-Titel auslesen

    Winamp-Titel auslesen in C#

    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!

    
            [System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
            public static extern IntPtr FindWindow(string strClassName, int nptWindowName);
            [System.Runtime.InteropServices.DllImport("user32.dll")]
            public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, long lParam);
            [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
            static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
            [System.Runtime.InteropServices.DllImport("kernel32.dll")]
            static extern IntPtr OpenProcess(UInt32 dwDesiredAccess, Boolean bInheritHandle, UInt32 dwProcessId);
            [System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
            static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [System.Runtime.InteropServices.Out] byte[] lpBuffer, UIntPtr nSize, IntPtr lpNumberOfBytesRead);
            [System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)]
            private static extern bool CloseHandle(IntPtr handle);
    
            private static string GetWinampTitle()
            {
                const uint WM_USER = 0x0400;
                const uint PROCESS_ALL_ACCESS = 0x000F0000 | 0x00100000 | 0xFFF;
    
                uint ProcessHandle;
                byte[] dat2 = new byte[500];
                IntPtr temp = (IntPtr) 0;
    
                IntPtr hwndWinamp = FindWindow("Winamp v1.x", 0);
                IntPtr MPointer = SendMessage(hwndWinamp, WM_USER, SendMessage(hwndWinamp, WM_USER, (IntPtr) 0, 125), 212);
                GetWindowThreadProcessId(hwndWinamp, out ProcessHandle);
                hwndWinamp = OpenProcess(PROCESS_ALL_ACCESS,false,ProcessHandle);
                ReadProcessMemory(hwndWinamp, MPointer, dat2, (UIntPtr) 500, temp);
                CloseHandle(hwndWinamp);
                return Convert.ToString(System.Text.Encoding.ASCII.GetString(dat2));
            }
    
    

    Code39 Barcodes über ASP.NET (VB) ausgeben

    Code39 über ASP.NET ausgeben
    Code39 über ASP.NET ausgeben

    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 = "*" &amp; Barcode.ToUpper &amp; "*"
    
    '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:

    
    <%@ Page Language="VB" ContentType="image/jpeg" AutoEventWireup="true" CodeFile="barcode.aspx.vb" Inherits="barcode" %>
    
    

    Aufrufen kann man das Dokument anschließend über den lokalen Webserver oder einen (externen) IIS. Der Aufruf vom Bild oben lautet:

    http://localhost:12384/barcode.aspx?barcode=1234567890&width=400&plain=1

    Softwareempfehlung: redgate ANTS Profiler

    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.