Reverse-String – Unicode-Aware

In vielen Beispielen wird für den Einstieg in die String-Klasse von .NET das “umdrehen” eines Strings aufgeführt. Da der String “immutable” ist, geht dies ja nicht direkt und man muss i.d.R. den Umweg über ein Array gehen. Der Code dafür schaut meistens so aus (C++/CLI):

  String^ ReverseString(String^ text)
  {
    array<Char> ^ charArray = text->ToCharArray();
    Array::Reverse(charArray);
    return gcnew String(charArray);
  }

Das Beispiel ist zwar einfach, aber es ist dann doch wieder zu einfach… es beachtet nämlich nur Zeichen, die aus genau einem Char bestehen. Im Unicode gibt es aber sher viele Zeichen (Glyph), die aus mehr als einem Char bestehen. So gibt es diverse Combining-Characters.

So kann man das “ä” als \u00e4 (sog. precomposed character) schreiben oder auch als ‘a’ + \u0308 (combining character).

Verwendet man die zweite Schreibweise, so geht das umdrehen des Strings, welche rein Char-basiert ist, schief! Dreht man dann z.B. das Wort “absa?gen” (mit combining chartacter) um, so kommt plötztlich “neg?asba” heraus, da das ? plötzlich dem “g” zugesprochen wird!

Deshalb sollte man für ein Unicode-Aweare “Reverse” immer jedes gesamte Text-Element verwenden. Und siehe da, .NET bietet hierfür natürlich auch die passenden Helferklassen 😉

String^ ReverseStringUnicodeAware(String^ text)
{
  System::Globalization::TextElementEnumerator ^enumerator = System::Globalization::StringInfo::GetTextElementEnumerator(text);
  auto elements = gcnew System::Collections::Generic::List<String^>();
  while (enumerator->MoveNext())
    elements->Add(enumerator->GetTextElement());
  elements->Reverse();
  return String::Concat(elements);
}

Es gibt da auch noch im VB.NET Namespace die Methode “Microsoft::VisualBasic::Strings::StrReverse“. Ich rate aber dringend davon ab diese zu verwenden, da diese nur ein Array::Reverse macht und somit genau die falsche Methode verwendet! (dies scheint aber wohl ein Bug zu sein, da die Implementierung dies wohl richtig machen sollte)

Hier das vollständige Beispiel:

String^ ReverseString(String^ text)
{
  array<Char> ^ charArray = text->ToCharArray();
  Array::Reverse(charArray);
  return gcnew String(charArray);
}

String^ ReverseStringUnicodeAware(String^ text)
{
  System::Globalization::TextElementEnumerator ^enumerator = System::Globalization::StringInfo::GetTextElementEnumerator(text);
  auto elements = gcnew System::Collections::Generic::List<String^>();
  while (enumerator->MoveNext())
    elements->Add(enumerator->GetTextElement());
  elements->Reverse();
  return String::Concat(elements);
}

int main()
{
  // Simple example:
  String^ str = "Hallo";
  System::Windows::Forms::MessageBox::Show("'" + str + "' => '" + ReverseString(str) + "'");

  // Combining characters: (WRONG)!
  String^ str2 = gcnew String(gcnew array<Char> { 'a', 'b', 's', 'a', 0x0308, 'g', 'e', 'n' });  // absägen
  System::Windows::Forms::MessageBox::Show("'" + str2 + "' => '" + ReverseString(str2) + "'");  // WRONG!

  // Combining characters: (CORRECT)
  System::Windows::Forms::MessageBox::Show("'" + str2 + "' => '" + ReverseStringUnicodeAware(str2) + "'");  // CORRECT!
}

Siehe auch mein (alter) Artikel über Unicode: Einführung in Codepages und Unicode

VS2012 Update 1 verfügbar

Ab sofort ist das erste Update für Visual Studio 2012 verfügbar!
Es bringt für VC++ primär die Unterstützung für XP-SP3 mit, was ein großer Fortschritt ist.

Download: Visual Studio 2012 Update 1 – Multi-language
Offline-Installation: How to: Install Visual Studio 2012 Update 1 Offline

Mehr Infos zu VS2012 Update 1:

Nur als kleiner Hinweis: Die Unterstützung für XP-SP3 betrifft nur VC++ 2012, nicht aber .NET-Framework 4.5! Dies ist immer noch auf Vista und höher begrenzt!

Ändern des .NET TargetFrameworks in VS2012 für C++/CLI Projekte

Leider ist es immer noch nicht möglich in VS2012 die .NET-Version für C++/CLI Projekte direkt anzupassen. Schon in VS2010 war dazu eine Änderung in der .vcproj-Datei notwendig. Das gleiche muss man jetzt in VS2012, wenn man eine andere Version verwenden will:

 <PropertyGroup Label="Globals">
    <ProjectGuid>...</ProjectGuid>
    <Keyword>...</Keyword>
    <RootNamespace>CPP_VS2012</RootNamespace>
    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
  </PropertyGroup>

Die Werte für “TargetFrameworkVersion” können sein:

  • v2.0
  • v3.0
  • v3.5
  • v4.0
  • v4.5

Breaking changes in VC2012

Jetzt gibt es eine Liste der “Breaking changes in VC 2012“. Darunter gibt es auch den Folgenden Eintrag:

The following project templates no longer exist:

  • Windows Forms Application
  • Windows Forms Control Library

Although we recommend that you do not create Windows Forms applications in C++/CLI, maintenance of existing C++/CLI UI applications is supported. If you have to create a Windows Forms application, or any other .NET UI application, use C# or Visual Basic. Use C++/CLI for interoperability purposes only.

So langsam fühle ich mich bestätigt, dass es gut war vor C++/CLI als Sprache für WinForms zu warnen (http://blog.kalmbach-software.de/de/2010/03/05/ccli-und-winforms-macht-keinen-sinn/).

XmlSerializer: Hidden features (xxxSpecified)

Der XmlSerializer ist ein sehr schönes und mächtiges Werkzeug und wird von mir oft verwendet. Heute bin ich über ein Feature gestolpert, was mir noch nicht bewusst war: Man kann einzelne public Members dynamisch steuern, ob diese serialisiert werden sollen oder nicht.

Das ganze funktioniert dann, wenn es ein public Field oder Property gibt, welches genau gleich heissen muss wie das danamisch zu serialisierendes Element nur mit der Endung “Specified”. Also:

  public class Foo
  {
    public string Value;

    [XmlIgnore] 
    public bool ValueSpecified; // Dieses gibt an, ob das obige Field serialisiert wird
  }

Gesteuert wird dies dann über das “ValueSpecified” Field:

using System;
using System.IO;
using System.Xml.Serialization;

namespace ConsoleApplication_VS2010
{
  public class Foo
  {
    public string Value;

    [XmlIgnore] 
    public bool ValueSpecified; // Dieses gibt an, ob das obige Field serialisiert wird

    public override string ToString()
    {
      return string.Format("Value: '{0}', ValueSpecified: {1}", Value ?? "(null)", ValueSpecified);
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      var f = new Foo();
      f.Value = "Text";

      using(var s = new StringWriter())
      {
        var ser = new XmlSerializer(typeof (Foo));
        ser.Serialize(s, f);
        var str = s.ToString();
        Console.WriteLine(str);
        using(var sr = new StringReader(str))
        {
          Console.WriteLine(ser.Deserialize(sr));
        }
      }
      Console.WriteLine();

      f.ValueSpecified = true;
      using (var s = new StringWriter())
      {
        var ser = new XmlSerializer(typeof(Foo));
        ser.Serialize(s, f);
        var str = s.ToString();
        Console.WriteLine(str);
        using (var sr = new StringReader(str))
        {
          Console.WriteLine(ser.Deserialize(sr));
        }
      }
    }
  }
}

Die Ausgabe ist dann (Anm.: ?xml header entfernt):

<Foo />
Value: '(null)', ValueSpecified: False

<Foo>
  <Value>Text</Value>
</Foo>
Value: 'Text', ValueSpecified: True

Fazit: Wieder etwas dazugelernt…

Microsoft C++ Days in Deutschland!

C++ ist noch lange nicht tot! Microsoft hat in den letzten Jahren wieder mehr Resourcen in diesen Bereich investiert. Um wieder mehr auf diesen Bereich aufmerksam zu machen bzw. sich mal weider zu Informieren, was es alles gibt, solltest Du unbedingt auf einen der C++ Days gehen. Das beste dabei ist, das Ganze ist kostenlos! Also, gleich Anmelden:

  • 2.2.2012 14:00- 18:00 Berlin (ausgebucht)
  • 7.2.2012 14:00- 18:00 Bad Homburg (noch freie Plätze!)
  • 13.2.2012 14:00- 18:00 Karlsruhe (noch freie Plätze!)
  • 5.3.2012 14:00- 18:00 Köln (ausgebucht)

Mehr Infos unter:
http://blogs.msdn.com/b/cbinder/archive/2011/12/29/c-entwickler-uptodate-microsoft-c-day-2012.aspx

Infos über VC2011 (Developer Preview)

Seit kurzem sind die Hilfeseiten für VS2011 online.

Dabei sind auch die Neuigkeiten für VC 2011 aufgeführt.

Der Blick ist wirklich lohnentswert.

Das neue VS kann man sich auch schon zusammen mit Windows 8 runterladen:
http://msdn.microsoft.com/en-us/windows/apps/br229516

Wer es in einer VM installieren möchte muss das neue VMWare 8 nehmen, da es in VMWare 7.1.4 wohl nicht geht (siehe hier ).

XmlSerializer verwenden mit abgeleiteten Klassen ohne “xsi:type” im XML zu verwenden

Lange habe ich gerätselt, wie man verhindern kann, dass der XmlSerializer bei abgeleiteten Klassen immer nur als Element-Name die Basisklasse verwendet und dann im Attribute “xsi:type” den jeweiligen richtigen Typ reinschreibt/ausliest. Das sieht im XML immer sehr unschön aus:

<Liste>
  <BasisKlasse xsi:type="Ableitung01" />
  <BasisKlasse xsi:type="Ableitung02" />
</Liste>

Viel schöner wäre es ja, wenn direkt die abgeleitete Klasse in der Datei stehen würde:

<Liste>
  <Ableitung01 />
  <Ableitung02 />
</Liste>

Bisher hab ich sowas immer gemacht, indem ich das “XmlInclude” Attribut an die Root-Klasse rangehängt habe, um dem XmlSerializer mitzuteilen, welche Klassen er noch berücksichtigen soll. Heute hat mir nun ein Kollege ganz nebenbei eine XML-Datei gezeigt, die genau das hat, was ich schon lange suche… Der Trick ist einfach nur, das “XmlArrayItem” Attribut an dem jeweiligen Property der Liste dranzuhängen… damit werden die Listen-Einträge dann ohne “xsi:type” serialisiert und bekommen den lesbaren Namen 😉

Hier ein kurzes Beispiel wie sowas aussieht:

using System.Collections.Generic;
using System.Xml.Serialization;

namespace ConsoleApplication_VS2010
{
  public abstract class BaseObject {}

  public class Special01 : BaseObject {}
  public class Special02 : BaseObject {}

  public class Root
  {
    readonly List<BaseObject> _Objects = new List<BaseObject>();

    [XmlArrayItem(typeof(Special01))]
    [XmlArrayItem(typeof(Special02))]
    public List<BaseObject> Objects
    { get { return _Objects; } }
  }

  class Program
  {
    static void Main(string[] args)
    {
      var r = new Root();
      r.Objects.Add(new Special01());
      r.Objects.Add(new Special02());
      r.Objects.Add(new Special01());

      using (var sw = new System.IO.StringWriter())
      {
        var ser = new XmlSerializer(typeof (Root));
        ser.Serialize(sw, r);
        System.Console.WriteLine(sw.ToString());
      }

    }
  }
}

Dies ergibt dann:

<?xml version="1.0" encoding="utf-16"?>
<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://ww
w.w3.org/2001/XMLSchema">
  <Objects>
    <Special01 />
    <Special02 />
    <Special01 />
  </Objects>
</Root>

VS2010 SP1 – Achtung bei installiertem Windows SDK v7.1!

Wer neben der Installation von VS2010 (Express oder Professional) auch noch das separate Windows SDK v7.1 installiert hat, der sollte das VS2010 SP1 nicht installieren!
Wenn man das SP1 installiert so werden die Compiler und C++ Libraries der IA64/x64 Komponenten gelöscht!
Es scheint so, als ob die Premium und Ultimate-Versionen davon nicht betroffen sind.

Mehr dazu siehe:
Visual Studio 2010 Service Pack 1 and Windows SDK for Windows 7 and .NET Framework 4 Issue

Update (2011-03-31):
Es gibt wohl ein Hotfix dazu. Siehe:
Microsoft Visual C++ 2010 Service Pack 1 Compiler Update for the Windows SDK 7.1

Edit- and Continue mit “Multitargeting” in VS2010

Mit VS2010 wurde für VC++ das “Multi-Targeting” eingeführt. Damit kann man mit VS2010 entwickeln, aber trotzdem noch z.B. die Compiler/Linker von VS2008 verwenden. Das funktioniert auch ganz gut… Jetzt ist aber einem Kollegen von mir aufgefallen, dass bei einem Projekt wo dies so eingestellt ist, das Edit- and Continue im Debug-Mode wohl nicht mehr funktioniert.

Das scheint also leider ein Bug zu sein. Wenn das Toolset “v100” ist, so geht alles; wenn man es umstellt auf “v90” so geht das Edit- and C>ontinue nicht mehr. Sehr Schade… das erschwert natürlich die Entwicklung etwas wenn man sich mal daran gewöhnt hat 😉

Hier der Link zu dem Eintrag auf Connect:
https://connect.microsoft.com/VisualStudio/feedback/details/647971/

RegFree COM Activation und Pfad zu einem anderen Verzeichnis / Unit-Tests

Ich hatte hier das Problem, dass wir in einen VIsual Studio Unit Test eine COM-DLL verwenden müssen. Diesen wollen wir aber nicht auf dem Build-Server registrieren. Um dies zu umgehen, hab ich mittels CreateActCtx / ActivateCtx / usw. eine Klasse gemacht, mit der man ein Manifest zur Laufzeit aktivieren kann. Jetzt war aber das Hauptproblem, dass die Unit-Tests ja von einer Applikation ausgeführt werden, auf dessen Verzeichnis wir keinen Zugriff haben. Im Internet hab ich aber immer nur Beispiele gefunden, wo die COM-DLL im gleichen Verzeichnis wie die EXE lag. Dies geht hier aber nicht, sondern die DLL muss mit einem “DeployItem” auch in das entsprechende Out-Verzeichnis kopiert werden und von dort muss diese COM-DLL dann geladen werden. D.h. das Manifest muss auf diese Datei zeigen.
Ich hab das nicht hinbekommen…. mein erster Versuch war das “lpAssemblyDirectory” Feld in der ACTCTX Struktur zu setzen. Damit hab ich es aber nicht hinbekommen; hab alles erdenkliche versucht… er hat immer gemeldet, dass er die Datei nicht finden kann…
Dann hab ich es in der Manifest-Datei selber probiert. Dort hab ich es nur hinbekommen, wenn der <file name="NameDer.dll"> auf den relativen Pfad geändert hab, also: <file name="test\NameDer.dll">.
Aber sobald ich einen vollständigen Pfad angegeben hab, ging es nicht mehr… ich bin fast verzweifelt…
Dann endlich kam ich drauf (keine Ahnung wie): Man muss den ganzen Pfad angeben, aber für jeden Slash jeweils *zwei* einfügen! Also:
<file name="C:\\Temp\\Test\\NameDer.dll">
Und siehe da: Es funktioniert, auch wenn die DLL nicht im entsprechenden EXE-Verzeichnis liegt.

Somit können wir jetzt Unit Tests erzeugen, welche eigentlich eine Registrierung von COM-DLLs auf dem Server verlangt hätten.

Mehr Infos dazu siehe:
https://cfx.svn.codeplex.com/svn/Visual%20Studio%202008/CSRegFreeCOMClient/Program.cs
http://www.mazecomputer.com/sxs/help/sxsapi2.htm

Ändern des .NET TargetFrameworks in VS2010 für C++/CLI Projekte

Leider gibt es in VS2010 bei C++/CLI Projekten keine Möglichkeite durch die Projekt-Eigenschaften einzustellen, welche .NET Version bei einem C++/CLI Projekt (/clr) verwendet werden soll.
Die einzige Möglichkeit ist es, dies direkt in der *.vcxproj-Datei vorzunehmen. Dazu sind Folgende Schritte notwendig:

  1. Rechst-Klick auf das entsprechende Projekt im Solution Explorer und dann auf “Unload project” klicken
  2. Dann nochmals ein Rechts-Klick auf das entladene Projekt im Solution Explorer und “Edit .vcxproj” auswählen
  3. In the Projekt XML Datei nach dem Knoten suchen
  4. In diesem Knoten nach dem Unterknoten such (wenn er nicht existiert muss man ihn hinzufüügen)
  5. Der innere Text des Knotens definiert nun das TargetFramework. Es kann die Werte v2.0,v3.0, v3.5 oder v4.0 annehmen
  6. Speichere die vcxproj Datei und schliesse sie
  7. Dann nochmals ein Rechts-Klick auf das entladene Projekt im Solution Explorer und “Reload Project” auswählen

Beispiel:

  <PropertyGroup Label="Globals">
    <ProjectGuid>{089A9EBF-5149-462A-BC7E-2B1B59DE123C}</ProjectGuid>
    <Keyword>Win32Proj</Keyword>
    <RootNamespace>CPP_VS2010</RootNamespace>
    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
  </PropertyGroup>

Böse Falle mit ??-operator

Heute ab ich längere Zeit nach einem Bug gesucht. Der Folgende Code wollte einfach nicht die korrekte Differenz ausrechenen:

  int? geliefert = 3;
  int? aktuell = 2;

  int diff = geliefert??0 - aktuell??0;

Bis ich endlich draufkam, dass das Minus ja nie ausgewertet wird, da einfach der erste Wert zurückgeliefert wird…

Also gut, wieder was gelernt, wenn man den ??-operator verwendet. Hab das ganze in Klammer gepackt, dann ging es wunderbar…

int diff = (geliefert ?? 0) - (aktuell ?? 0);

VS2010 SP1-beta verfügbar

Die Beta Version des VS2010 SP1 ist nun für alle Verfügbar. Wie immer gilt der Hinweis, dass man dies nicht auf einem Produktivsystem installieren sollte. Wobei diesmal aber auch eine “Go Live” Lizenz dabei ist; d.h. man darf es auch produktiv verteilen!

Das C++ Produkt-Team hat auch noch detailiertere Infos, was sich im C++ Bereich getan hat:
VS2010 SP1 Beta: What’s in It for C++ Developers
Die Hauptneuigkeit dürfte wohl sein, dass es wieder einen lokalen Help-Viewer gibt…

Und wer noch gleich Feedback geben will, kann das hier tun:
Visual Studio 2010 Service Pack 1 Beta Survey

Sprachumschaltung und WPF

Wer mit .NET “aufgewachsen” ist, der kennt das System.Threading.Thread.CurrentThread.CurrentCulture bzw. CurrentUICulture. Damit kann man z.B. in WinForms-Anwendungen die Formatierungen der Ausgaben beeinflussen. Diese Culture wird auch in den meisten Objekten verwendet wenn man “ToString()” aufruft. Bestes Beispiel dafür ist DateTime. Intern wird hier die aktuelle Culture (CurrentCulture) des aktuellen Threads verwendet.

Dieses ganze Konzept ist aber nicht so ganz WPF kompatibel. Dazu gibt es zwei Gründe: Erstens basiert bei WPF (fast) alles auf DependencyProperties, welche Vererbung und auch Änderungsbenachtrichtigungen schon automatisch unterstützen. Auch spielen die Converter eine große Rolle beim Anzeigen von Daten. Dabei wird immer eine Culture mitgegeben in den Konvertierungsmethoden mitgegeben. AUs diesem Grunde spielt die “Thread.CurrentThread.CurrentCulture” in WPF keine Rolle mehr!

Die Sprache wird in WPF also auch über ein DependencyProperty umgeschaltet: FrameworkElement.Language

Das kann man einfach testen indem man z.B. ein DateTime-Property an an TextBlock bindet. Dabei wird die Datum/Uhrzeit immer in en-us angezeigt! Egal welche Sprache nun das Betriebssystem oder den Benutzer hat. Ob dies ein Bug oder Feature ist, sei mal dahingestellt.

Um also beim Starten der Applikation die Sprache auf die aktuelle Sprache zu setzen, muss man für jedes TopLevel-Fenster das Language Property korrekt initialisieren, am besten im Konstruktor:

this.Language = 
  System.Windows.Markup.XmlLanguage.GetLanguage(
    System.Threading.Thread.CurrentThread.CurrentUICulture.Name
  );

Will man eine Sprachumschaltung dynamisch machen, so könnte man auch ein Binding auf dieses Language-Property machen. Dazu muss man aber wissen, dass ja jedes Binding auch eine Culture hat. Da aber die Culture aus dem vererbeten “Language” DependencyProperty kommt beisst sich hier die Katze in den Schwanz… aus diesem Grund scheitert auch ein Binding der Form:

Language={Binding MyCurrentCulture.Name}

mit der Fehlermeldung (InnerException):
Binding for property 'Language' cannot use the target element's Language for conversion; if a culture is required, ConverterCulture must be explicitly specified on the Binding.
Wer diese Meldung genau liest erkennt, dass man für den Converter die Culture explizit vorgeben muss! Damit muss der Bindning Ausdruck z.B. wie folgt aussehen:

Language={Binding MyCurrentCulture.Name,
  ConverterCulture={x:Static glob:CultureInfo.InvariantCulture}}

Dabei ist der Namespace “glob”:

xmlns:glob="clr-namespace:System.Globalization;assembly=mscorlib"

Damit wird nun das Binding möglich.
Und natürlich wird jedes Binding nochmals ausgeführt (bzw. der passende Converter aufgerufen), bei welcher die Language vererbt wurde (was eigentlich überall der Fall ist).
Man sieht also auch: Die Anzeige wird nicht nicht bei Werteänderung aktualisiert, sondern auch bei “Language-Property” Änderung 😉

Anbei mal ein ganz kleines Testprojekt, welches das Umschalten der Language zur Laufzeit anhand dem DateTime aufzeigt:
WpfCulture.zip

TFS 2010 – Hotfix Rollup ist verfügbar

Für den TFS 2010 gibt es schon diverse “Hotfix Rollups”. Aktuell ist der September 2010 Hotfix Rollup verfügbar. Er behebt einige Fehler im TFS 2010.
Mehr Infos siehe:
A hotfix rollup (build 10.0.30319.341) is available for Team Foundation Server 2010

Der Hotfix kann hier runtergeladen werden:
KB983504 – TFS 2010: Important Post-RTM Releases – Home
oder auch hier:
TFS 2010: Important Post-RTM Releases

TFS2010 – Verwaltung von Alerts

Mit den TFS Power Tools kann man ja immerhin seine eigenen Alerts verwalten. Leider ist es damit als Admin aber auch nicht möglich andere Alerts zu verwalten bzw. anzuzeigen oder zu ändern.
Auch habe ich bei mir den Effekt, dass E-Mail Alerts welche ich für andere angelegt habe, nicht gesendet werden…

Um den Verlauf der Benachrichtigung zu sehen, kann man direkt auf dem Data-Tier in der SQL-Datenbank Folgende Abfrage ausführen:

SELECT TOP 10 [HistoryId],[JobSource],[JobId],[QueueTime],[StartTime],[EndTime],[AgentId]
,[Result],[ResultMessage],[QueuedReasons],[QueueFlags]
  FROM [Tfs_Configuration].[dbo].[tbl_JobHistory]
  WHERE [JobId] = 'A4804DCF-4BB6-4109-B61C-E59C2E8A9FF7'
  ORDER BY [StartTime] DESC

Um zu sehen, was für Alerts alles eingerichtet sind kann man die Folgende Abfrage ausführen:

SELECT TOP 50 [Id],[EventType],[Expression],[SubscriberId]
,[Schedule],[DeliveryType],[Address],[Classification]
  FROM [Tfs_DefaultCollection].[dbo].[tbl_EventSubscription]

Das “DefaultCollection” muss man durch die entsprechende TFS-Collection ersetzen.