Nette Falle im SQL-Server: Der Kompatibilitätsgrad

Nach einem der letzten Updates unserer Software meldete uns ein Kunde einen SQL Fehler, der bei einer bestimmten Operation auftrat. Er setzt den MS-SQL Server 2008 ein.

OK, meine Testumgebung hat drei Server von SQL 2000, über 2005 bis 2008 R2. Keine der Testumgebungen brachte bei der entsprechenden gleichen Operation einen Fehler 😕 Gut oder besser schlecht… Der Kunde bekommt nun eine Fehlermeldung und auch wenn Kunden meistens ja nicht recht haben wenn sie Fehler melden 😀 schaute ich mir dennoch alle SQL Befehle etwas genauer an, die meine Software da auslöste.
In dem entsprechenden Teil meiner wurde nach Benutzerangaben ein relativ komplexer Query durch einen Abfragegenerator zusammengebaut. Darunter fand sich auch der folgende Subquery, als Teil der gesamten Abfrage:

SELECT a.[Id] FROM [tblXYZ] AS a
  WHERE
    (((a..[IdParent] IS NULL
       AND a..[Id] NOT IN
         (SELECT [IdXYZ]
            FROM [tblSomething]
              WHERE [IdParent] IS NOT NULL))))

Unschwer zu sehen werden hier mit dem Alias a zusammen irgendwie zwei Punkte verwendet. Bleibt die Frage warum in meiner Umgebung nun kein Fehler passiert und beim Kunden ein nun Syntax Fehler ausgelöst wird.

Nach einigem Suchen fand ich die Ursache im Kompatibilitätsgrad, den man im Managementstudio unter Datenbank -> Datenbankname -> Eigenschaften -> Optionen je Datenbank separat einstellen kann. Dort sind folgende Einstellungen möglich.

SQL Server 2000 (80)
SQL Server 2005 (90)
SQL Server 2010 (100)

In meiner Testumgebung verwende ich eine Datenbank, die seit den ersten Anfängen unserer Software immer weiter als Testumgebung mit vielen Testdaten dient. Sie wurde erstmals auf einem SQL Server 2000 angelegt. Dann auf einen 2005er und schließlich auf einen SQL Server 2008 R2 umgezogen. Netterweise – oder besser dummerweise – hat sich der SQL Server bei jeder Umstellung die ehemalige Kompatibilität gemerkt. Und man staunt nicht schlecht: Auf einem SQL Server 2000 ist es kein Fehler zwischen Alias und Spaltennamen zwei Punkte zu schreiben. Bei einem SQL Server 2005 oder später ist das sehr wohl ein Syntaxfehler.
Der Fehler lag also doch bei uns – was ja wirklich selten vorkommt 😀 – und wurde trotz genauer Tests nicht entdeckt.

Man merke sich: SQL Server Syntax ist trotz gleicher SQL Server Version eben doch lange nicht das selbe.
Wer also Software auf einem SQL Server testet sollte tunlichst darauf achten welchen Kompatibilitätsgrad er benutzt ❗

Warum unter Vista und Windows 7 C:\Programme nicht genau dasselbe ist wie C:\Program Files

Jeder kennt von Windows Vista und Windows 7 die Junctions „C:\Programme“ oder „C:\Dokumente und Einstellungen“.
Diese Verweise erlauben es auch älteren Programmen die evtl. hardcodierte Verzeichnisnamen haben oder auch Programmen, die die alten Verzeichnisstrukturen von Windows 2000 und XP nutzen korrekt zu arbeiten.

Jetzt könnte man meinen, dass es vollkommen egal ist ob man nun „C:\Programme“ oder „C:\Program Files“ benutzt.
Aber das ist es nicht ❗ Es gibt ein paar ganz feine Unterschiede.

Ein simpler DIR auf der Befehlszeile

C:\>dir C:\Programme
 Datenträger in Laufwerk C: ist C-LAPTOP
 Volumeseriennummer: D483-432C

 Verzeichnis von C:\Programme

Datei nicht gefunden

macht das Problem offensichtlich!
Und was geht hier ab?

Die Antwort liefert ICACLS für die Junction:

C:\>icacls C:\Programme
C:\Programme Jeder:(DENY)(S,RD)
             Jeder:(RX)
             NT-AUTORITÄT\SYSTEM:(F)
             ORDEFINIERT\Administratoren:(F)

Dagegen zeigt ICACLS für das Verzeichnis selbst

C:\>icacls "C:\Program Files"
C:\Program Files NT SERVICE\TrustedInstaller:(F)
                 NT SERVICE\TrustedInstaller:(CI)(IO)(F)
                 NT-AUTORITÄT\SYSTEM:(M)
                 NT-AUTORITÄT\SYSTEM:(OI)(CI)(IO)(F)
                 VORDEFINIERT\Administratoren:(M)
                 VORDEFINIERT\Administratoren:(OI)(CI)(IO)(F)
                 VORDEFINIERT\Benutzer:(RX)
                 VORDEFINIERT\Benutzer:(OI)(CI)(IO)(GR,GE)
                 ERSTELLER-BESITZER:(OI)(CI)(IO)(F)

Während man auf „C:\Program Files“ die Rechte sieht, die man auch erwartet, liegt auf der Junction selbst eine Einschränkung:
Die Berechtigung zum „Ordner Auflisten“ wird verweigert für „Jeder“ ❗
Jeder der neu eingeführten Junctions hat diese Einschränkung.

Meines Erachtens wurde dies gemacht um beim Durchsuchen von Verzeichnisstrukturen nicht unendliche Rekursionen zu erzeugen oder Verzeichnisse oder Dateien doppelt aufzuführen.
Eine Verzeichnis Rekursion ergäbe sich zum Beispiel durch die Junction Anwendungsdaten im Verzeichnis C:\Users\Username\AppData\Local, der exakt wieder auf das Verzeichnis C:\Users\Username\AppData\Local verweist.

VS-Tipps & Tricks: MFC/ATL Tracing selektiv ein und ausschalten

In ATL und MFC steckt ein ziemlich ausgeklügelter Trace-Mechanismus. Wenn man sich das MFC – ATL Trace Tool ansieht kann man zu allen möglichen Kategorien Informationen in Debug Fenster ausgeben lassen.
Alleine die MFC hat 14 verschiedene Trace Kategorien. Darunter besonders interessante wie CommandRouting, AppMsg und WinMsg. Die ATL hat weitere 27 Kategorien.
Es lohnt sich mal einen Blick in dieses Tool und die entsprechenden Ausgaben zu machen. Es gehört zu den oft unbekannten netten Helferlein, die leider mangels Bekanntheit selten benutzt werden.

Um Fehler zu finden und einzugrenzen, sind mir jedoch oft eher zu viele Ausgaben vorhanden, als zu wenige. Zudem finde ich es manchmal unhandlich mit dem Trace-Tool die Nachrichten ab einem bestimmten Moment einzuschalten und wieder auszuschalten.
Ich habe eine kleine Hilfsklasse gebaut,mit der man in jedem Szenario, jederzeit zu einem bestimmten Moment das Tracing im Code ein- und automatisch wieder ausschalten kann.

class CDebugEnableTraceForCategory
{
public:
  CDebugEnableTraceForCategory(ATL::CTraceCategory &category,
           PCSTR pszPrompt=NULL,
           UINT uiLevel=4,
           ATL::ATLTRACESTATUS eStatus=ATL::ATLTRACESTATUS_ENABLED)
    : m_category(category)
    , m_uiSaveLevel(category.GetLevel())
    , m_eSaveStatus(category.GetStatus())
    , m_strPrompt(pszPrompt)
  {
    if (!m_strPrompt.IsEmpty())
      TRACE("%s - Tracelevel %d\n", m_strPrompt.GetString(),uiLevel);
    m_category.SetLevel(uiLevel);
    m_category.SetStatus(eStatus);
  }
  ~CDebugEnableTraceForCategory()
  {
    m_category.SetLevel(m_uiSaveLevel);
    m_category.SetStatus(m_eSaveStatus);
    if (!m_strPrompt.IsEmpty())
      TRACE("%s - Tracelevel %d\n", m_strPrompt.GetString(), m_uiSaveLevel);
  }
private:
  // Data fields
  ATL::CTraceCategory &m_category;
  ATL::ATLTRACESTATUS m_eSaveStatus;
  UINT m_uiSaveLevel;
  CStringA m_strPrompt;
  // no copy operator
  CDebugEnableTraceForCategory(const CDebugEnableTraceForCategory &);
  CDebugEnableTraceForCategory& operator=(const CDebugEnableTraceForCategory &);
};

Mit dieser Klasse kann ich zum Beispiel alle Windows-Nachrichten an MFC Fenster bei einer bestimmen Aktion ausgeben lassen. Und wenn die Aktion fertig ist stopp auch das Tracing wieder. 

Hier als Beispiel um alle Fensternachrichten in der Aktion LoadFrame zu Tracen: 

void CMyApp::InitInstance()
{
...
    CDebugEnableTraceForCategory trace(traceWinMsg,"messages in LoadFrame");
    pMainFrame->LoadFrame(IDR_MAINFRAME,WS_OVERLAPPEDWINDOW);
...
}

Weitere Infos in der MSDN findet man unter ATL::CTraceCategory und ATLTRACE2 http://msdn.microsoft.com/en-us/library/dhxsse89(VS.100).aspx

Breaking-Change: In VC-2010 in der STL wird 0 bzw. NULL nicht mehr gültiger Zeiger akzeptiert

Die STL Implementierung wurde klar hinsichtlich „perfect forwarding“ überarbeitet (Siehe C++0x). Allerdings sind dabei auch einige Sachen reingerutscht die dazu führen, dass sich mancher Code nicht mehr kompilieren lässt.

So führen die nachfolgenen Zeilen zu einem Compiler Fehler:

std::pair<void*,void*> p(0, NULL);
// fails with error C2440: 'initializing' : cannot convert from 'int' to 'void *'

Gleiches negatives Ergebnis erhält man, wenn man die folgenden Zeile kompiliert:

vector<void*> v; 
v.insert(v.begin(),NULL);

Der Hintergrund wird hier in dieser Connect Meldung beleuchtet:
https://connect.microsoft.com/VisualStudio/feedback/details/520043/error-converting-from-null-to-a-pointer-type-in-std-pair?wa=wsignin1.0

Das Problem ist, das 0 (NULL) sich zwar brav in einen Zeiger umwandeln lässt. 0 (NULL) bleibt aber deshalb dennoch vom Typ her ein int ist wird nicht zu einem Zeiger. Die typensichere Implementierung in der STL führt nun dazu, dass 0 (NULL) nicht als void* akzeptiert wird.

Einzige Lösung und auch mein Rat:
Man verwendet nicht mehr 0 oder NULL, wenn ein NULL-Zeiger gemeint ist, sondern nullptr
Wer Code kompatibel zu VC-2008 und früher halten muss, kann ja in seinen Headern den folgende Definition einführen:

#if  _MSC_VER<1600
const int nullptr = 0;
#endif

Mehr zu perfect forwarding liest man hier:
http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1385.htm
http://thbecker.net/articles/rvalue_references/section_07.html

Wie auch aus dem Connect Artikel zu entnehmen ist kann man damit rechnen, dass sich in VC11 alles wieder etwas zurückentwickeln wird. D.h. std::pair und auch die insert Befehle der Container sollen wirde brav 0 aktzeptieren können, wenn Zeiger gemeint sind. Gleiches bekam ich auch über meine Kontakte zur Produktgruppe zu höhren. Aber was VC11 betrifft ist alles sowieso nur Zukunftsmusik und alles noch im dichten Nebel und was wirklich Realität wird  muss man dann wohl erst mal sehen 😉

Nachtrag vom 09.09.2010:
Dravere hat in einem Kommentar auf eine geniale Implementierung für nullptr hingewiesen:
http://www.c-plusplus.de/forum/viewtopic-var-t-is-220511.html
Die ist weitaus besser als meine Definition als const int.

const class
{
public:
  template<class T> operator T*() const {return 0;}
  template<class C, class T> operator T C::*() const {return 0;}
private:
  void operator&() const;
}
nullptr = {};

Bug: VC-2010 MFC CFormView zeichnet Buttons beim Rollen falsch, es erscheinen schwarze Blöcke

In der MFC 10.0 hat sich ein Bug eingeschlichen, der sich unter Windows Vista und Windows 7  bemerkbar macht. Unter Windows XP tritt der Fehler nicht auf. Das Problem tritt in jedem Stil auf, der DWM verwendet. D.h. nicht wenn Windows klassisch ausgewählt wird.

Wenn auf einen CFormView mehrere Buttons liegen und der CFormView gerollt wird, dann kommt es unter Umständen zu Fehlern beim Neuzeichnen von Buttons. Dies schließt alle Button-Formen ein: Check-Buttons, Radio-Buttons und normale Buttons.

Das Ganze sieht nach dem Rollen in etwa so aus:

Der Text einiger Buttons erscheint nach dem Rollen als schwarze Blöcke. Es kann auch vorkommen, dass nur Teile der Buttons falsch gezeichnet werden.

Um das Problem gezielt nachzuvollziehen habe ich ein kleines Sample gebaut. Man kann durch zwei Schalter den CScrollView gezielt nach oben oder unten Rollen. Beim Rollen nach unten und bei bestimmten Fenstergrößen tritt dann der Fehler auf. Ich habe das Main-Window entsprechend beim Start in der Größe angepasst.

Das Problem liegt in einer Implementierung von WM_PRINTCLIENT in CScrollView (CScrollView::OnPrintClient), die ein Double-Buffering verwendet, dass entweder falsch ist oder sich eben mit der Standardimplementierung eines Dialoges beißt. Auf den ersten und zweiten Blick konnte ich in der Implementierung selbst keinen Fehler sehen. Deshalb vermute ich, dass sich dieses Double-Buffering mit dem auch vorhandenen Double-Buffering in den Standardimplementierungen der Dialogklasse beißt bzw. nicht korrekt berücksichtigt, dass auch Child-Windows neu gezeichnet werden müssen.

Die Lösung ist entsprechend einfach:

  • Man fügt einfach einen Handler für WM_PRINTCLIENT in seiner von CFormView abgeleiteten Klasse ein.
  • Dieser Handler ruft dann nicht die Implementierung der Basisklasse CFormView auf, sondern die Implementierung in CView (CView::OnPrintClient).
...
ON_MESSAGE(WM_PRINTCLIENT,&CScrollDialogMFCView::OnPrintClient)
...

LRESULT CScrollDialogMFCView::OnPrintClient( WPARAM wp, LPARAM lp )
{
  // Bypass the CScrollView::OnPrintClient implementation
  return CView::OnPrintClient(wp,lp);
}

Dieser Fehler ist in keiner der vorhergehenden MFC Versionen vorhanden (auch nicht in MFCNext), weil einfach kein entsprechender Handler vorhanden war..

Es stellt sich wohl bei einigen jetzt die Frage warum dieser Handler eingebaut wurde. Auch die Antwort ist einfach:
Seit Windows Vista wird stark von AnimateWindow Gebrauch gemacht und auch DWM intern scheint des öfteren WM_PRINT/WM_PRINTCLIENT zu verwenden. Entsprechend haben fast alle Klassen in der MFC entsprechende Handler ergänzt bekommen.

Das Sample kann man hier herunterladen: ScrollDialogMFC – VS-2010.
Ich habe in der stdafx.h einen define FIX_CSCROLLVIEW_PROBLEM eingebaut mit dem man den Fix einfach aktivieren und deaktivieren kann.

Texte von deutschen Meldungen in Microsoft Produkten auf englisch finden

 Wer kennt das nicht: Da hat man ein deutsches Microsoft-Produkt und bekommt eine Fehlermeldung, auf die Google nichts ausspuckt. Scheinbar ein seltener Fehler. Oder evtl. benutzen viel mehr Anwender die englische Version eines Entwicklerproduktes.

Was nun? Ja wenn man genau wüsste wie die selbe Meldung in englisch lautet, besonders wenn man keine Fehlernummer oder keine ID aus dem Ereignisprotokoll hat…

Die Lösung für MSDN Nutzer ist einfacher als man denkt:
Es gibt die Glossaries, d.h. die entsprechenden Übersetzungstabellen komplett zum Download ❗

In der entsprechenden ZIP-Datei befinden sich 276 csv Dateien mit allen möglichen Produkten. Das schließt die Windows Server Produkte ein, wie auch VisualStudio.
Alleine die Windows 7 Datei umfasst 56 Megabyte an Texten.

Bug: CSimpleMap und CSimpleArray führen bei Verwendung von SetAtIndex zu einem Leak

Wieder einmal ein Bug, der seit VC-2005 bekannt ist und in meinen Augen unmöglich als by design abgetan werden darf.

Das Problem ist simpel, wenn man die Funktion SetAtIndex in den Klassen CSimpleMap und CSimpleArray angewendet wird, dann wird für das bestehende Element in der Map oder im Array kein Destruktor aufgerufen. D.h. eine CSimpleMap<CString,…> bzw. ein CSimpleArray<CString> führt mit der Verwendung von SetAtIndex sofort zu Leaks.

Tückisch in reinen ATL Projekten, weil hier der CRT Heap nicht benutzt wird und die Speicherleaks durch die CRT nicht entdeckt werden, weil der Win32 Heap zum Einsatz kommt.

Hier ist der Bug zu finden, inkl. der in meinen Augen korrekten Implementierung:
https://connect.microsoft.com/VisualStudio/feedback/details/298324/csimplemap-setatindex-do-not-call-a-ditructor

Für mich ist besonders diese Antwort äußerst fragwürdig:

CSimpleMap wasn’t really designed to work for types that needed destruction. The docs state it is limited and you should use CAtlMap instead.

Dieser Satz und dieser Hinweis ist in keiner Doku zu finden. Die Doku liest sich ganz anders:
http://msdn.microsoft.com/en-us/library/d1xc3983(VS.100).aspx

CSimpleMap provides support for a simple mapping array of any given type T, managing an unordered array of key elements and their associated values.

Man kann nur raten SetAtIndex nicht zu verwenden, sondern nur SetAt oder den operator[] ❗

VeriSign ‚Microsoft Authenticode‘ Class 3 Code Signing Digital Certificate für $99,–

Ich war mal wieder auf meiner WinQual Seite um festzustellen ob es im aktuellen Release auffällige Crashes gibt.
Dabei viel mir ein Link ins Auge, der ein VeriSign ‚Microsoft Authenticode‘ Class 3 Code Signing Digital Certificate für nur $99,– anbietet. Bei einem Normalpreis von $499,– ist das kein schlechter Preis!

Hier der Link auf die WinQual Seite:
http://winqual.microsoft.com/help/default.htm#obtaining_a_verisign_class_3_digital_id.htm

und hier der entsprechende Link direkt zu VeriSign:
https://securitycenter.verisign.com/celp/enroll/upsell?promo_code=THEDEAL99&application_locale=VRSN_US&originator=VeriSign:CELP&bundle_id=MSIECS002&enable_options=validity_1

MFC-Next 9.0 > MFC 10.0 denn CMFCRibbonPanel::EnableLaunchButton gibt es nicht mehr

Sehr erfreut waren viele C++ Entwickler darüber das es mit der MFC in VC-2008 weiter ging und MFC-Next veröffentlicht wurde. Das ganze wurde dann fest in VS-2008 SP1 integriert. Normalerweise sind wir es gewohnt, dass zur MFC nur Dinge hinzukommen und nichts wegfällt.

Für die MFC 10.0 aus VS-2010 gilt das diesmal nicht: MFC 10.0 < MFC-Next 9.0!

Irgendwie hat es CMFCRibbonPanel::EnableLaunchButton nicht in die MFC 10.0 geschafft, obwohl die Funktion vollständig in der MFC-Next 9.0 implementiert war. Das soll mal einer verstehen 😕 ich jedenfalls nicht!

Diese Funktion sorgt für den kleinen netten Schalter in einem Panel:

Erstaunlicherweise gibt es diese Funktion nun nicht mehr! Wer also 100% Office-kompatible Anwendungen schreiben will ist hier schon mal aufgeschmissen, wenn er das mit MFC 10.0 machen will.
Im Header finden wir diese Funktion noch mit einem #ifdef auskommentiert. Allerdings nützt es nichts diesen #define zu setzen, denn es gibt keine Implementierung und entsprechend keinen Code in der DLL/Library. Ja und in der MFC Doku finden wir die Funktion auch noch.

Und auch dieses Problem war noch in der Beta-Phase bekannt und wurde abgebügelt, wie man in den nachfolgenden Links lesen kann.
http://social.msdn.microsoft.com/Forums/en/vcmfcatl/thread/29ad2859-6341-4ffb-85c2-f5f056a6ca48
https://connect.microsoft.com/VisualStudioJapan/feedback/details/533876/cmfcribbonpanel-enablelaunchbutton?wa=wsignin1.0
Wem es möglich ist, sollte hier bitte Abstimmen und diesen Bug als wichtig kennzeichnen!

Langsam frage ich mich ob es nicht gescheiter gewesen wäre bei MFC-Next 9.0 zu bleiben und mit VS-2008 weiter zu arbeiten.
Tja und so hat die BCG-Library in Verbindung mit VS-2010 auch eine Daseinsberechtigung. Die kann diesen LaunchButton natürlich darstellen.

PS: Auf Nachfrage bei Microsoft bekam ich eine Antwort aber keinerlei Begründung. Jetzt habe ich eine Support-Anfrage dies bzgl. laufen, allerdings mit wenig Hoffnung. 🙁

PPS: (Nachtrag 01.09.2010) Auch der Microsoft Support kann mir dies bzgl. keine Antwort geben aufgrund eines offenen Rechtsstreits. Siehe auch Kommentar von Samsa.

Bug in der MFC von VC-2010 in CImageList::DrawIndirect

Die Funktion CImageList::DrawIndirect der MFC-10

BOOL CImageList::DrawIndirect(CDC* pDC, int nImage, POINT pt, SIZE sz,
    POINT ptOrigin, UINT fStyle = ILD_NORMAL, DWORD dwRop = SRCCOPY,
    COLORREF rgbBack = CLR_DEFAULT, COLORREF rgbFore = CLR_DEFAULT,
    DWORD fState = ILS_NORMAL, DWORD Frame = 0,
    COLORREF crEffect = CLR_DEFAULT);

hat einen massiven Bug: Sie funktioniert einfach nicht.

Der Unsinn, der sich eingeschlichen hat, liegt in der überschriebenen Funktion

BOOL CImageList::DrawIndirect(IMAGELISTDRAWPARAMS* pimldp);

die durch die oben genannte Variante aufgerufen wird. Denn hier cbSize von IMAGELISTDRAWPARAMS nicht mehr in allen Fällen auf einen korrekten Wert gesetzt. Die Folge cbSize enthält Garbage und der Aufruf von ImageList_DrawIndirect geht in die Hose!
Sowohl in VC-2005 als auch VC-2008 wurde in dieser Funktion explizit der cbSize Member überschrieben, je nach dem ob ComCtl 6.0 oder höher von der Anwendung benutzt wird.

Auch das ist ein Bug, denn hierdurch wird evtl. ein korrekt gesetzter cbSize Wert mit IMAGELISTDRAWPARAMS_V3_SIZE (pre IE 5.01 d.h. _WIN32_IE < 0x0501) überschrieben und damit vergrößert vergrößert. Dadurch kann es zu Zugriffsfehlern kommen oder zu unerwünschten Seiteneffekten.
(siehe https://connect.microsoft.com/VisualStudio/feedback/details/322713/bug-in-cimagelist-drawindirect)

Also haben die Entwickler scheinbar die Zuweisung von pimldp->cbSize = sizeof(IMAGELISTDRAWPARAMS) entfernt! Allerdings haben Sie dabei vergessen in der anderen Funktion nun cbSize korrekt zu initialisieren!

Wir haben also wieder mal einen Fall von: Let us fix one thing and break others…

Das Gemeine an der Sache ist, dass dieser Bug noch vor RTM bekannt war:
https://connect.microsoft.com/VisualStudio/feedback/details/543108/bug-in-cimagelist-drawindirect
Nur ist er nicht mehr gefixed worden!

PS: Natürlich fährt die eigene Software mit der MFC-10 auch gegen die Wand, wenn man die CImageList::DrawIndirect(IMAGELISTDRAWPARAMS* pimldp) Variante verwendet und selbst cbSize nicht initialisiert, was ein ordentlicher Entwickler aber sicherlich nicht vergisst! 😉

Wem es möglich ist, sollte bei den beiden Connect Einträgen Abstimmen und diesen Bug als wichtig kennzeichnen!