In einem aktuellen MonoTouch-Projekt habe ich für die Anzeige einer Bildergalerie das kleine externe Framework EGOPhotoView/EGOPhotoViewController eingebunden. Nach der Implementierung konnte ich das Projekt nicht mehr kompilieren. Es kam im Output immer folgender Fehler:
Undefined symbols:
"__Unwind_Resume", referenced from:
+[EGOCache currentCache] in libPhotoViewerKitMobile.a(EGOCache.o)
-[EGOCache saveCacheDictionary] in libPhotoViewerKitMobile.a(EGOCache.o)
+[EGOImageLoader sharedImageLoader] in libPhotoViewerKitMobile.a(EGOImageLoader.o)
ld: symbol(s) not found
collect2: ld returned 1 exit status
Process exited with code 1, command:
Abhilfe hat hier folgende Einstellung geschaffen:
- Projekteigenschaften öffnen
- Build => IPhone Build auswählen
- Im Reiter "General" die "Additional mtouch arguments" erweitern um -gcc_flags "-cxx -lgcc_eh"
Tipp:
Um erweiterte Ausgabemeldungen (Output) zu bekommen, muss man einfach in die arguments noch die Parameter -v -v -v aufnehmen.
Donnerstag, 11. August 2011
Mittwoch, 10. August 2011
Zufalls-Datensätze direkt per SQL auslesen
Manchmal benötigt man schnell und einfach einen einzelnen, beliebigen Datensatz aus einer Datenbank/Tabelle. Wo mir dieses bislang sehr nützlich erschien, war zum Beispiel beim Einlesen von Zufallsbildern auf mobilen Geräten. Um hier keinen unnötigen Overhead wie das Erstellen von Objekt-Id-Listen zu erzeugen, ist es manchmal hilfreich einfach nur einen Zufallswert direkt aus der Datenbank geliefert zu bekommen. Dieses kann man ganz einfach wie folgt realisieren.
MS-SQL-Server:
SQLite:
MS-SQL-Server:
SELECT TOP 1 * FROM Testtabelle
ORDER BY NEWID();
SQLite:
SELECT * FROM Testtabelle ORDER BY RANDOM() LIMIT 1;
Dienstag, 19. Juli 2011
Windows Mobile - .Net Compact Framework Exceptions anzeigen
Bei einem aktuellen Projekt arbeite ich an einer Anwendung für ein Windows CE 5.0 Gerät. Um genau zu sein handelt es sich um einen Scanner vom Typ Casio DT-X7.
Bei der Entwicklung habe ich festgestellt, dass meine Fehlerprotokolle (Logging der Exceptions in eine Textdatei) nicht korrekt geschrieben werden können. Und zwar wurde immer folgende Meldung protokolliert, egal welcher Fehler aufgetreten ist:
"An error message is available for this exception but cannot be displayed because these messages are optional and are not currently installed on this device. Please install ‘NETCFv35.Messages.EN.wm.cab’ for Windows Mobile 5.0 and above or ‘NETCFv35.Messages.EN.cab’ for other platforms. Restart the application to see the message."
Folglich habe ich mich, ganz wie in der Meldung beschrieben, daran gemacht die angegeben CAB zu installieren. Hierzu habe ich die folgende Datei von meinem PC (Windows Mobile SDK muss installiert sein) auf das mobile Gerät kopiert:
C:\Programme\Microsoft.NET\SDK\CompactFramework\v3.5\WindowsCE\Diagnostics\NETCFv35.Messages.EN.cab
Nach dem Kopieren habe ich die Datei auf dem mobilen Gerät gestartet und erfolgreich installiert.
Hier gilt es zu beachten, dass es im genannten Verzeichnis auch eine Datei "NETCFv35.Messages.EN.wm.cab" gibt. Diese Datei ist ausschließlich für Geräte mit Windows Mobile. Die Datei ohne ".wm" im Namen ist für alle weiteren Systeme wie z. B. Windows CE.
Verwendet man versehentlich die falsche Datei, so kommt eine Fehlermeldung vor der Installation wie z. B. folgende:
The file "NETCFv35.Messages.EN.wm.cab" is not a valid Windows CE Setup file.
In diesem Fall empfehle ich einfach noch einmal genau hinzuschauen und ggf. auch einmal das Bestriebssystem auf dem mobilen Gerät zu überprüfen. Manchmal hat man vielleicht doch etwas verwechselt oder übersehen.
Wurde aber alles korrekt installiert, so sollten auch die Fehlermeldungen wieder korrekt angezeigt/protokolliert werden.
Bei mir war dieses aber nicht der Fall. So musste ich weitersuchen.
Hierfür habe ich mir die kostenfreien Windows Mobile Power Toys von Microsoft installiert und mit dem MobileLogger geschaut, ob es noch irgendwelche Probleme gibt. Und ich bin direkt fündig geworden. Der Logger zeigt mir die Meldung:
"Failed to load [System.SR, Version=3.5.0.0, Culture=neutral, PublicKeyToken=969DB8053D3322AC]"
Das Problem hier ist, dass eine im CAB enthaltene Datei unter anderem Namen vorliegt, als sie eigentlich referenziert ist. Und zwar handelt es sich hierbei um die Datei "SYCCFA~1.001". Als Referenz wird der Name "System.SR" erwartet. Helfen kann man sich hier ganz einfach, in dem man die CAB-Datei entpackt, die Datei in "System.SR.dll" umbenennt und diese anschließend im sein Windows Mobile Projekt als Referenz einbindet.
Jetzt wurden sogar bei mir alle Fehlermeldungen korrekt ausgegeben.
Bei der Entwicklung habe ich festgestellt, dass meine Fehlerprotokolle (Logging der Exceptions in eine Textdatei) nicht korrekt geschrieben werden können. Und zwar wurde immer folgende Meldung protokolliert, egal welcher Fehler aufgetreten ist:
"An error message is available for this exception but cannot be displayed because these messages are optional and are not currently installed on this device. Please install ‘NETCFv35.Messages.EN.wm.cab’ for Windows Mobile 5.0 and above or ‘NETCFv35.Messages.EN.cab’ for other platforms. Restart the application to see the message."
Folglich habe ich mich, ganz wie in der Meldung beschrieben, daran gemacht die angegeben CAB zu installieren. Hierzu habe ich die folgende Datei von meinem PC (Windows Mobile SDK muss installiert sein) auf das mobile Gerät kopiert:
C:\Programme\Microsoft.NET\SDK\CompactFramework\v3.5\WindowsCE\Diagnostics\NETCFv35.Messages.EN.cab
Nach dem Kopieren habe ich die Datei auf dem mobilen Gerät gestartet und erfolgreich installiert.
Hier gilt es zu beachten, dass es im genannten Verzeichnis auch eine Datei "NETCFv35.Messages.EN.wm.cab" gibt. Diese Datei ist ausschließlich für Geräte mit Windows Mobile. Die Datei ohne ".wm" im Namen ist für alle weiteren Systeme wie z. B. Windows CE.
Verwendet man versehentlich die falsche Datei, so kommt eine Fehlermeldung vor der Installation wie z. B. folgende:
The file "NETCFv35.Messages.EN.wm.cab" is not a valid Windows CE Setup file.
In diesem Fall empfehle ich einfach noch einmal genau hinzuschauen und ggf. auch einmal das Bestriebssystem auf dem mobilen Gerät zu überprüfen. Manchmal hat man vielleicht doch etwas verwechselt oder übersehen.
Wurde aber alles korrekt installiert, so sollten auch die Fehlermeldungen wieder korrekt angezeigt/protokolliert werden.
Bei mir war dieses aber nicht der Fall. So musste ich weitersuchen.
Hierfür habe ich mir die kostenfreien Windows Mobile Power Toys von Microsoft installiert und mit dem MobileLogger geschaut, ob es noch irgendwelche Probleme gibt. Und ich bin direkt fündig geworden. Der Logger zeigt mir die Meldung:
"Failed to load [System.SR, Version=3.5.0.0, Culture=neutral, PublicKeyToken=969DB8053D3322AC]"
Das Problem hier ist, dass eine im CAB enthaltene Datei unter anderem Namen vorliegt, als sie eigentlich referenziert ist. Und zwar handelt es sich hierbei um die Datei "SYCCFA~1.001". Als Referenz wird der Name "System.SR" erwartet. Helfen kann man sich hier ganz einfach, in dem man die CAB-Datei entpackt, die Datei in "System.SR.dll" umbenennt und diese anschließend im sein Windows Mobile Projekt als Referenz einbindet.
Jetzt wurden sogar bei mir alle Fehlermeldungen korrekt ausgegeben.
Donnerstag, 14. Juli 2011
MonoTouch: UITextView mit zentriertem Inhalt bei der Bearbeitung
In einer IPhone-App hatte ich gerade das Problem, dass ich eine UITextView (Textbox) implementiert habe, die im Bearbeitungsmodus (nach Klick) immer den beinhalteten Text außerhalb des sichtbaren Bereichs verschoben hat. Das heißt, man musste jedes Mal scrollen um den Text zu sehen.
So sieht das ganze dann im Einsatz aus:
Dieses liegt daran, dass die UITextView standardmäßig einen unteren "Rahmen" von 32 Pixel einfügt. Da auch standardmäßig das Autoscrolling aktiv ist, wird immer die letzte Zeile fokussiert. Somit rutsch der eigentlich Inhalt/Text soweit nach oben, dass er nicht mehr sichtbar ist.
Abhilfe schafft hier die kleine Property "ContentInset".
Codebespiel:
UITextView textView = new UITextView(new RectangleF(10, 10, 280, 30));
textView.ContentInset = new UIEdgeInsets(0, 0, 0, 0);
Und so sieht das Ganze dann mit der gesetzten Property aus:
So sieht das ganze dann im Einsatz aus:
Dieses liegt daran, dass die UITextView standardmäßig einen unteren "Rahmen" von 32 Pixel einfügt. Da auch standardmäßig das Autoscrolling aktiv ist, wird immer die letzte Zeile fokussiert. Somit rutsch der eigentlich Inhalt/Text soweit nach oben, dass er nicht mehr sichtbar ist.
Abhilfe schafft hier die kleine Property "ContentInset".
Codebespiel:
UITextView textView = new UITextView(new RectangleF(10, 10, 280, 30));
textView.ContentInset = new UIEdgeInsets(0, 0, 0, 0);
Und so sieht das Ganze dann mit der gesetzten Property aus:
Freitag, 8. Juli 2011
SQLite BulkInsert-Alternative - Kleiner Code, große Wirkung
Heute bin ich auf ein Performance-Problem beim Füllen einer SQLite Datenbank gestoßen. Und zwar nutze ich für eine mobile Anwendung eine Datenbank, welche sich täglich komplett inhaltlich verändern kann. Diese Datenbank wird dann per REST Webservice aus einem ASP.Net Web heruntergeladen.
Die Datenbank beinhaltet zwei Tabellen mit jeweils zehn Spalten. Da es bei SQLite kein Bulk-Insert wie z.B. beim SQL-Server gibt, habe ich zuerst versucht die ca. 40.000 Datensätze nacheinander ganz einfach per INSERT einzufügen. Dieses war mir aber deutlich zu langsam, so dass ich nach optimaleren Alternativen gesucht habe. Und ich bin auch recht schnell fündig geworden. Hierfür konnte ich sogar meine bestehende Programmierung fast komplett behalten. Ich musste lediglich einen kleinen Teil umstellen und um ein paar Zeilen Code erweitern.
Hier ist erst einmal zur Übersicht der Aufbau der ersten Tabelle.
CREATE TABLE Artikel(Artikelnummer VARCHAR(255) PRIMARY KEY ,EAN VARCHAR(255),Artikelbezeichnung1 VARCHAR(255),Artikelbezeichnung2 VARCHAR(255),Hersteller VARCHAR(255),EmpfVK FLOAT,Verfuegbarkeit INTEGER, Gewicht FLOAT, Status_NichtVerfuegbar BOOLEAN,Artikelinfo TEXT);
Das Befüllen der Tabelle sieht dann in meine Programmierung wie folgt aus.
1:
2: SQLiteConnection connection = new
3: SQLiteConnection();
4:
5: SQLiteCommand command = new
6: SQLiteCommand(connection);
7: command.CommandText = "INSERT INTO Artikel
8: VALUES
9: (@Artikelnummer,@EAN,@Artikelbezeichnung1,@Artikelbezeichnung2,@Hersteller,@EmpfVK,@Verfuegbarkeit,@Gewicht,@Status_NichtVerfuegbar,@Artikelinfo);";
10: command.Prepare();
11:
12:
13: SQLiteTransaction tr = connection.BeginTransaction();
14: command.Transaction = tr;
15:
16:
17: for (int
18: i = 0; i < listArtikel.Count; i++)
19: {
20: command.Parameters.Add(new SQLiteParameter("@Artikelnummer", listArtikel[i].Artikelnummer));
21: command.Parameters.Add(new SQLiteParameter("@EAN",
22: listArtikel[i].EAN));
23: command.Parameters.Add(new SQLiteParameter("@Artikelbezeichnung1",
24: listArtikel[i].Artikelbezeichnung1));
25: command.Parameters.Add(new SQLiteParameter("@Artikelbezeichnung2",
26: listArtikel[i].Artikelbezeichnung2));
27: command.Parameters.Add(new SQLiteParameter("@Hersteller",
28: listArtikel[i].Hersteller));
29: command.Parameters.Add(new SQLiteParameter("@EmpfVK",
30: listArtikel[i].EmpfVK));
31: command.Parameters.Add(new SQLiteParameter("@Verfuegbarkeit", listArtikel[i].Verfuegbarkeit));
32: command.Parameters.Add(new SQLiteParameter("@Gewicht",
33: listArtikel[i].Gewicht));
34: command.Parameters.Add(new SQLiteParameter("@Status_NichtVerfuegbar",
35: listArtikel[i].Status_NichtVerfuegbar));
36: command.Parameters.Add(new SQLiteParameter("@Artikelinfo",
37: listArtikel[i].Artikelinfo));
38:
39:
40: command.ExecuteNonQuery();
41:
42:
43: command.Parameters.Clear();
44: }
45:
46:
47: tr.Commit();
48: command.Dispose();
Das Wichtigste an dieser Programmierung ist, dass sich die Parameter-Zuweisung sowie die Ausführung (ExecuteNonQuery) innerhalb der gestarteten Transaktion liegt. Außerdem sollte das eigentliche SQL-Statement (Command.Text) außerhalb der Schleife und nicht bei jedem Durchlauf zugewiesen werden.
Ein weiterer wichtiger Punkt ist das Aufbereiten des SQL-Statements mit command.Prepare().
Mir hat die Implementierung der Transaktion eine Zeitersparnis von fast 90% gebracht. Ich denke das ist ein sehr erstaunliches Ergebnis.
Samstag, 2. Juli 2011
Base64-Strings encodieren und decodieren
Gerade wenn man Web-Applikationen entwickelt und mit Datenübertragungen zu tun hat, wird man regelmäßig mit sogenannten Base64-Strings konfrontiert. Wenn man dieses zum ersten Mal hört, fragt man sich wahrscheinlich, was das eigentlich ist.
Dabei ist das alles eigentlich ganz einfach. Base64 ist eine Kodierung für 8-Bit-Binärdaten wie zum Beispiel Bilder, PDF-Dokumente oder Zip-Archive. Bei einer Base64-Kodierung werden die Binärdaten zum Beispiel eines Bildes in einen lesbaren ACII-String umgewandelt. Daher ist auch oft die Rede von einem Base64-String. Erkennen kann man eine solche Zeichenkette daran, dass keine Sonderzeichen außer "+", "/" und "=" vorhanden sind und der String immer mit einem "="-Zeichen endet.
Setzt man das ganze nun in der Programmierung (C# / .Net) um, könnte es wie folgt aussehen.
Daten encodieren / String in Base64-String umwandeln
// Erstelltes ByteArray in einen Base64 kodierten String umwandeln und zurückgeben
Daten decodieren / Base64-String in String umwandeln
// Erstelltes ByteArray aus dem Base64-String in einen decodierten (nicht kodierten) String umwandeln und zurückgeben
Dabei ist das alles eigentlich ganz einfach. Base64 ist eine Kodierung für 8-Bit-Binärdaten wie zum Beispiel Bilder, PDF-Dokumente oder Zip-Archive. Bei einer Base64-Kodierung werden die Binärdaten zum Beispiel eines Bildes in einen lesbaren ACII-String umgewandelt. Daher ist auch oft die Rede von einem Base64-String. Erkennen kann man eine solche Zeichenkette daran, dass keine Sonderzeichen außer "+", "/" und "=" vorhanden sind und der String immer mit einem "="-Zeichen endet.
Setzt man das ganze nun in der Programmierung (C# / .Net) um, könnte es wie folgt aussehen.
Daten encodieren / String in Base64-String umwandeln
public string base64Encode(string dataToEncode)
{
// Übergebenen String in ein ByteArray umwandeln
byte[] bytesToEncode = System.Text.Encoding.UTF8.GetBytes(dataToEncode);
// Erstelltes ByteArray in einen Base64 kodierten String umwandeln und zurückgeben
return Convert.ToBase64String(bytesToEncode);
}
public string base64Decode(string dataToDecode)
{
// Ein Objekt der Klasse ASCIIEncoding instanziieren
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
// Übergebenen String in ein ByteArray umwandeln
byte[] bytesToDecode = Convert.FromBase64String(dataToDecode);
// Erstelltes ByteArray aus dem Base64-String in einen decodierten (nicht kodierten) String umwandeln und zurückgeben
return encoding.GetString(bytesToDecode);
}
Donnerstag, 30. Juni 2011
Microsoft SQL-Server Verbindungsserver einrichten
Mit folgendem, simplen SQL-Statement würde man Daten der Tabelle "Maschinen" aus der Datenbank "FlorianTestDB" von einem Server "Testserver2" auf einen anderen Server "Testserver1" in die gleichnamige Datenbank/Tabelle kopieren.
INSERT INTO [Testserver1].[FlorianTestDB].[dbo].[Maschinen]
SELECT * FROM [Testserver2].[FlorianTestDB].[dbo].[Maschinen]
GO
Voraussetzung für eine erfolgreiche Übertragung ist, dass der Zielserver als sogenannter Verbindungsserver auf dem Quellserver eingerichtet ist. Ist dieses nicht der Fall, so kommt folgende Meldung.
Meldung 7202, Ebene 11, Status 2, Zeile 1
Server 'Testserver1' konnte in 'sys.servers' nicht gefunden werden. Stellen Sie sicher, dass der richtige Servername angegeben wurde. Führen Sie ggf. die gespeicherte Prozedur 'sp_addlinkedserver' aus, um den Server in 'sys.servers' hinzuzufügen.
Abhilfe schafft man, indem man den Verbindungsserver auf einfache Art und Weise anlegt. Hierzu muss man eine Verbindung mit dem Quellserver herstellen und folgenden Befehl ausführen, um den Zielserver als Verbindungsserver einzurichten.
EXEC sp_addlinkedserver 'Testserver1'
GO
Als Ergebnis sollte folgendes zurückkommen.
Befehl(e) wurde(n) erfolgreich abgeschlossen.
Führt man das SQL-Statement zum Kopieren der Daten nun erneut aus, so sollte es erfolgreich ausgeführt werden.
Eine kleine Einschränkung hierbei gibt es jedoch. Fügt man den Verbindungsserver wie oben beschrieben hinzu, so ist es notwendig, dass auf beiden Servern der aktuell verwendete Benutzer identisch eingerichtet ist.
Möchte man dieses nicht, benutzt unterschiedliche Passwörter oder verschiedene Benutzer, so ist es am einfachsten den Verbindungsserver über das SQL-Server-Management-Studio anzulegen.
Diese Vorgehensweise geht für die SQL-Server-Versionen 2000, 2005 und 2008
Diese Vorgehensweise geht für die SQL-Server-Versionen 2000, 2005 und 2008
Abonnieren
Posts (Atom)