Der Visual Studio 2010 Release Candidate ist erschienen

Jetzt geht es in die Schlussphase ❗

Der Release Candidate für VisualStudio 2010 ist nun draußen. MSDN Abonnenten können ihn jetzt schon herunterladen. In den nächsten Tagen wird dann auch ein freier Download zur Verfügung stehen.
http://msdn.microsoft.com/en-us/vstudio/dd582936.aspx

Wie auch schon die Beta 2 gibt es für diesen RC eine Go Live Lizenz, mit der man direkt produktiv entwickeln darf.

Das endgültige Erscheinungsdatum bleibt weiterhin der 12. April 2010

PS: Das jetzt der RC erschienen ist, heißt auch, dass nur noch wirklich extrem gravierende Bugs entfernt werden. Mit dem meisten was wir im RC sehen werden wir wohl auch im fertigen Produkt leben müssen. Ich bin gespannt welche Fehler aus der Beta 2 überlebt haben 😉
Dennoch testen und Fehler melden: https://connect.microsoft.com/VisualStudio?wa=wsignin1.0

Mein „Baby“ ist nun „Kompatibel mit Windows 7“

Das meine Software auf allem läuft was Windows XP und später heißt war mir schon lange klar 😉 aber ein offizielles „Kompatibel für…“ ist ja schon noch was anderes.
Jetzt habe ich die kostenlose Zertifizierung für die Windows 7 Kompatibilität hinter mich gebracht.
In Klartext ganz offiziell: AG-VIP SQL in der Version 1.20.008 ist kompatibel mit Windows 7

Verglichen mit der Vista Zertifizierung, die wir bei einem 3rd Party Unternehmen (VeriTest) durchgeführt haben, muss ich sagen: Windows 7 kompatibel zu werden ist nicht schwer
Es war wirklich kein großer Aufwand!

Wer sich die Spezifikationen durchgelesen hat wird sehen, dass sich gegenüber der Vista Zertifizierung kaum was geändert hat. Wer also seine Software bereits Vista kompatibel hat, kann gleich einen Durchstart machen und es für Windows 7 auch probieren. Und das schöne: Es kostet nichts!
Ich habe es nicht versucht, aber ich vermute mal, dass genau die Version die ich für Vista eingereicht habe auch durch die Windows 7 Zertifizierung gekommen wäre.

Noch ein paar Anmerkungen:

  • Ich kann jeden ermutigen, den Test zu machen, wenn man sowieso schon ein Code Signing Zertifikat und einen 64bit Rechner zur Verfügung hat. Wenn man auf dem man 64bit Windows 7 virtualisieren kann ist die Zertifizierung wirklich einfach. Ich habe VMWare benutzt und das ist einfach ein super Werkzeug für so etwas.
  • Wie bei vielen Zertifizierungen wird viel zu viel Gewicht auf Installation und Deinstallation gelegt. Das war bei der Vista Zertifizierung auch schon so.
  • Bei der Vista Zertifizierung war noch klar, wie und wo man Waiver (Freistellungen) bekommt, z.B. für Komponenten von Drittherstellern, die Ihre DLLs nicht signieren. Dazu gehört ja auch Microsoft mit den DLLs der MFC 😉
    In diesem Zertifizierungsprozess, erfährt man, dass man sie evtl. benötigt, aber nicht wo man sie bekommt…
  • Manche in meinen Augen wertvolle Tests in der Vista Zertifizierung, wie zum Beispiel, die korrekte Behandlung von Crashes, scheinen vollkommen entfallenzu sein.
  • Der Test bei dem 3rd Party Unternehmen war weitaus tiefer als das was man bei dem Selbsttest macht. Jetzt genügt ja die Software zu starten und wieder zu beenden. Bei den Tests zu Vista sind wir beim ersten mal durchgefallen, weil hier ein Bug in der MFC mit dem Application Verifier gefunden wurde, der uns beim Testen nicht aufgefallen war  (Siehe 1, 2, 3
    Ich empfinde den Selbsttest als zu schwach um dann ein Windows 7 kompatibel Logo zu bekommen.
  • Man muss versichern, dass man keine Spyware zertifiziert oder andere böse Software erzeugt… gelinde gesagt ein Scherz. Bei einem 3rd Party Tester würde solche Spyware (hoffentlich) nicht durchkommen. Das hoffe ich doch wirklich mal! Jetzt muss man nur einen Haken setzen, und sich selbst bestätigen, dass man es nicht tut, obwohl man es evtl. doch tut… 😉
    Das mindert den Wert dieses Logos ungemein.
  • Wenn man früher Geld investierte für eine Zertifizierung, hieß das nicht, dass man deshalb bessere Software produziert. Aber 1000$ waren dennoch eine Schwelle über die nicht jeder Hanswurst mit seiner Mickey-Maus-Software gegangen ist.
    Jetzt bleibt als Schwelle nur noch der Kauf eines Code-Signing Zertifikates bei VeriSign und so was kostet ca. 500$ im Jahr.
  • Ich vermisse schon lange das Verbot anderer Techniken, zum Beispiel ein Verbot von Systemweiten Hooks, wenn man Windows X kompatibel sein will. Wenn dann sollte so etwas auch nur mit einem Waiver und klarer Begründung erlaubt werden.
    Nicht wenige dieser mies programmierten Hooks sorgen für Instabilität, unnützes Aufblähen von Prozessen und manchmal auch miese Performance.

Als Fazit bleibt für mich die Frage:
Was nützt ein Logo, dass man zu leicht bekommen kann?

Es bleibt eine Marketing-Aktion und genauso wurde die Vista und jetzt auch die Windows 7 Zertifizierung auch bei uns in der Firma eingestuft. Technischen Nutzen hat das ganze nicht wirklich.

Was denn nun SwitchToThread(), Sleep(0), Sleep(1)?

Was macht man, wenn man keine Wait-Funktionen verwenden will, aber dennoch möchte, dass ein anderer Thread weiterarbeiten kann. Zum Beispiel, weil man einen Spinlock implementieren will.

Nun es gibt insgesamt vier Methoden die durch das Netz geistern.
Ich gehe mal der Häufigkeit nach, die so in manchen Code-Samples finde:

1. __noop;

Wenn der Lock kurz ist, scheint es das beste zu sein, einfach die Schleife weiterlaufen zu lassen und zu hoffen, dass der ein Thread auf einem anderen Prozessor, die Ressource freigibt. Das eignet sich wirklich nur, wenn die Zeitdauer der Sperre als extrem kurz anzusehen ist und eine hohe Anzahl von Prozessoren zur Verfügung steht.
Nach allen Test, die ich gemacht habe, sollte man aber von dieser Art des Wartens bei einem Spinlock absehen. Es schiebt die Leistung des Kerns auf 100% und bringt nichts.

2.  Sleep(0);

Lies sich gut. Schlafe aber eben nicht lange. Man hat auch schon irgendwo gelesen, dass durch diese Methode der Rest der Zeitscheibe dieses Threads aufgegeben wird und ein anderer Thread an die Reihe kommt.
Leider stimmt das nicht ganz ❗
Liest man die Doku genau steht da aber:

A value of zero causes the thread to relinquish the remainder of its time slice to any other thread of equal priority that is ready to run. If there are no other threads of equal priority ready to run, the function returns immediately, and the thread continues execution.

😮 Threads mit höherer oder niedriger Prio haben also nichts davon.

Besonders eklig wird das ganze gerade wenn man Threads unterschiedlicher Prio hat, die hier gegeneinander laufen. Sleep(0); führt in diesem Fall zu einerunnötigen Prozessorlast und eben nicht dazu, dass die Zeitscheibe abgegeben wird. Der Prozess kommt sofort wieder an die Reihe und spin-t weiter.

3. SwitchToThread();

OK. Seit Windows 2000 gibt es diese nette neue Funktion. Damit wird ein anderer Thread aktiv. Egal was für eine Prio er hat. Aber auch diese Funktion tut evtl. nicht genau das was man will.
Auch hier stecken die Tücken im Detail der Doku:

The yield of execution is limited to the processor of the calling thread. The operating system will not switch execution to another processor, even if that processor is idle or is running a thread of lower priority.

Sollte also der Thread, auf den man wartet auf dem anderen Prozessor laufen, so profitiert der nicht von dem Aufruf von SwitchToThread.

4. Sleep(1):

Hiermit erreicht man wirklich was man möchte. Man gibt seine Timeslice auf und erstmal sind die anderen dran.

Mein persönliches Fazit:

Nach meinen Recherchen ist Sleep(1); der vernünftigste Weg seine Zeitscheibe abzugeben. Und nach meinem Dafürhalten ist ein __noop; strickt zu vermeiden. Die Performance ist grottenschlecht.
Das ganze Verhalten hängt extrem auch von den Umständen ab: Zahl der Theads, Häufigkeit der Kollision, Anzahl verfügbare Prozessoren, Verteilung der Prioritäten, Allgemeine Belastung des Systems, Zeitdauer der Sperre etc.

Ich habe mit einigen Parametern gespielt und auch ein kleines Sample gebaut, dass alle 4 oben genannten Funktionen durchprobiert und in dem man auch mit anderen Faktoren (Priorität etc.) spielen kann.
Es zeigte sich, dass Sleep(1); am effektivsten war. Aber dicht auf gefolgt von Sleep(0);, was mich doch etwas überraschte.

Allerdings führen schon kleinste Änderungen (Lockdauer, Zahl der Prozessoren, Spielen mit der Priorität) zu anderen Ergebnissen.
Interessant ist vor allem das Spielen mit den Prioritäten. Man soll nicht glauben, das ein Thread selbst mit niedrigster Prio noch relativ häufig Arbeit bekommt.

Viel Spaß beim Spielen mit dem Code SleepTest

Alle SQL Server enumerieren mit den OLE-DB Enumeratoren

Wie bekommt man eigentlich einfach eine Liste aller verfügbaren SQL-Server im Netz?

In der MSDN findet sich schnell ein Artikel How to enumerate available instances of SQL Server by using the SQLDMO components. Allerdings ist dieser Artikel wenig nützlich, denn SQL-DMO findet man nur noch selten auf einem Rechner.

Dabei ist es doch relativ einfach, denn OLE-DB sieht hierfür Enumeratoren vor. Aber auch die sind nicht sonderlich gut dokumentiert. In der SQL-2000 Server Doku findet sich noch ein Eintrag für den SQLOLEDB Enumerator. Für den neuen nativen OLE-DB Clienst für den SQL-Server finde ich nichts mehr dazu.

ATL stellt direkt Klassen zur Verfügung, die die Nutzung von Enumeratoren zu einem Kinderspiel machen.

Anbei ein Codeschnippsel der alle bekannten MS-SQL Server enumeriert. Ich beginne dabei mit dem neuesten Client (2008) und gehe die Schleife weiter bis zum ältesten Server Client (2000).
Wird ein Enumerator gefunden, und dieser lieferte Ergebnisse, dann wird die Schleife abgebrochen. Denn alle Enumeratoren liefern im Allgemeinen das gleiche Ergebnis.

Code:

//////////////////////////////////////////////////////////////////////////
// Main function to enumerate all servers with the appropriate
// known enumerators.

typedef std::set  TSET_CString;

void EnumSQLServer(TSET_CString &setSQLServer)
{
  // We may need the local server name. 
  // We replace the token (local) with the current computer name.
  CString strCompLocal;
  DWORD dwLen = MAX_COMPUTERNAME_LENGTH+1;
  ::GetComputerName(CStrBuf(strCompLocal,dwLen),&dwLen);

  // Loop over all enumerators we know
  static const PCWSTR aEnumerator[] =
  {
    L"SQLNCLI10 Enumerator",    // SQL 2008
    L"SQLNCLI Enumerator",      // SQL 2005
    L"SQLOLEDB Enumerator"      // SQL 2000
  };

  // Try all enumerators
  for (int i=0; i < _countof(aEnumerator); ++i)
  {
    // Check if we have an enumerator
    bool bFoundAny = false;
    HRESULT hr;
    CLSID clsid;
    hr = CLSIDFromProgID(aEnumerator[i],&clsid);
    if (SUCCEEDED(hr))
    {
      // Open enumerator and loop over all entries
      CEnumerator enumrator;
      hr = enumrator.Open(&clsid);
      if (SUCCEEDED(hr))
      {
        while ((hr=enumrator.MoveNext())==S_OK)
        {
          CString strServerName(enumrator.m_szName);

          // Skip empty server names 
          // (older enumerators return sometimes an empty name)
          if (strServerName.IsEmpty())
            continue;

          // Some enumerators return (local) for a local main
          // SQL server instance
          if (strServerName.CompareNoCase(_T("(local)"))==0)
          {
            ATLTRACE(__FUNCTION__ " found local computer\n");
            strServerName = strCompLocal;
          }

          // get uppercase server name
          strServerName.MakeUpper();

          // Insert in list and avoid duplicates with this, if
          // developer decides not to break the loop after the first
          // enumerator.
          if (setSQLServer.insert(strServerName).second)
            ATLTRACE(__FUNCTION__ " found server %s\n",
                  CT2A(strServerName.GetString()));
          bFoundAny = true;
        }
      }

      // After we have found data in one enumerator. There is no need
      // to do this again.
      // But a developer might decide to do this for every enumerator
      if (bFoundAny)
        break;
    }
  }
}

Ein lauffähiges Projekt kann man hier herunterladen: EnumSQLServer.zip.

Bug in der Windows UI: SetRedraw verändert WS_VISIBLE Stil in einem RTF Control

Ich habe eine relativ komplexe UI, die auch dynamisch Controls erzeugt. In diese Controls werden auch zum Teil Massen an Daten hineingeschoben. Damit alle Controls zeitgleich erst die Daten präsentieren verwende ich eine einfache Methode, die aus alten Windows Tagen stammt: CWnd::SetRedraw/WM_SETREDRAW. Man verwendet diese Nachricht zum Beispiel um das Flackern von Listboxen und Comboboxen zu verhindern, wenn man viele Daten einfügt.
Diese Nachricht wird von allen Fenstern unterstützt oder sollte unterstützt werden 😉

Meine Software macht nun folgendes:

  • Zuerst hat meine Ladeprozedur für die Daten, zuerst alle Controls erzeugt, oder überflüssige vernichtet und positioniert, oder evtl. nur ausgeblendet (ShowWindow(SW_HIDE). D.h. nach dem ersten Laden der Daten ändert sich am Layout evtl. nichts mehr.
  • Anschließend wurde an alle Controls CWnd::SetRedraw/WM_SETREDRAW mit FALSE gesendet.
  • Dann die Daten geladen.
  • Nach dem Laden wird einfach wieder CWnd::SetRedraw/WM_SETREDRAW mit TRUE gesendet und ein Invalidate durchgeführt.

Das funktioniert für alle Controls, mit einer Ausnahme: Das RTF Control. Wenn man WM_SETREDRAW TRUE an ein RTF Control sendet, das nicht sichtbar ist, dann wird dieses sichtbar. Der Stil WS_VISIBLE wird also verändert. 😮

Um das Problem zu isolieren habe ich hier ein kleines Testprogramm geschrieben. Der kritische Code sieht so aus. Das gesamte Projekt kann man hier auch herunterladen: Demoprojekt.

void CTestRTFSetRedrawDlg::OnBnClickedBtDoit()
{
 bool bWasVisible = (m_wndEdRTF.GetStyle() & WS_VISIBLE)!=0;
 m_wndEdRTF.SetRedraw(FALSE);
 m_wndEdRTF.SetWindowText(_T("Line 1\r\nLine 2\r\nLine 3\r\nLine 4"));
 m_wndEdRTF.SetSel(0,0);
 m_wndEdRTF.SetRedraw(TRUE);
 m_wndEdRTF.Invalidate();
 bool bIsVisible = (m_wndEdRTF.GetStyle() & WS_VISIBLE)!=0;

 // Check if the visible state changed
  if (bIsVisible!=bWasVisible)
  AfxMessageBox(_T("The visible state of the RTF control changed!"));
}

Nachtrag 16.01.2010 (Danke Sven für Deinen produktiven Kommentar):
Auch andere Controls wie Button-, Static– und Edit-Controls verändern den Visible Status wenn WM_SETREDRAW angewendet wird. Einzig Listbox– und Combobox-Controls behalten den Visiblestatus korrekt bei ❗

CDialog::SetDefID und DM_SETDEFID, des Tastaturfreunds Liebling

Die Frage um die Eingabe-Taste in Dialogen und wie man diese „missbraucht“ (sage ich mal provokant) ist eine regelmäßige Frage in allen Foren.

Die Intention ist oft klar. Man möchte mit der Eingabetaste eine bestimmte Aktion verbinden, die evtl. sehr oft ausgeführt werden soll und nicht den Dialog schließen.
Die üblichen Wege sind schon mehrfach diskutiert worden und auch in meinem Blog finden sich dazu ein Artikel .

Zu kurz kommt bei dieser Diskussion die Funktion CDialog::SetDefID bzw. DM_SETDEFID Nachricht.
Was macht diese Funktion/Nachricht?
Sie definieren die Button-ID, die als Default-Aktion in einem Dialog ausgelöst werden soll und das ist nichts anderes als die Aktion die geschehen, soll wenn die Eingabe-Taste gedrückt wird.
Viele Entwickler definieren einfach OnOK um. Aber das eigentlich tolle ist mit SetDefID den Button in Abhängigkeit der Daten umzusetzen und das hat auch einen visuellen Effekt für den Nutzer.

Mal ein Beispiel:
Wir haben einen Dialog mit zwei List Views. Links Elemente die zur Auswahl stehen, rechts die Elemente in der Reihenfolge, die der Benutzer ausgewählt hat.
Der Mausschubser wird einfach die Einträge auf der linken oder rechten Seite doppelklicken und damit auswählen oder entfernen. Entsprechende Buttons für Hinzufügen und Entfernen wird es auch geben. Man kann also auch links oder rechts markieren und dann den Hinzufügen oder Entfernen Schalter nutzen.

Dem Tastaturnutzer können wir helfen indem wir intelligent CDialog::SetDefID / DM_SETDEFID verwenden. Die Vorgehensweise ist einfach.

  • Wir richten uns nur danach in welchem List View wir uns befinden, d.h. befinden wir uns im linken List View steuern wir den Hinzufügen Schalter, und im rechten List View steuern wir den Entfernen Schalter.
  • Wird also im linken List View ein Item ausgewählt, setzen wir mit CDialog::SetDefID / DM_SETDEFID die ID des Hinzufügen Schalters.
  • In dem Moment wird der Hinzufügen Schalter zum Default-Button. Der Nutzer kann nun die Eingabe-Taste drücken und die Items werden in die rechte Box verschoben.
  • Links liegt jetzt nun noch der Fokus, aber es sind keine Items mehr markiert. D.h. wir setzen nun den Default Button zurück auf IDOK.
  • Jetzt kann der Nutzer erneut ein Item markieren. Der Default-Button wird wieder der Hinzufügen/Entfernen Schalter und die Eingabetaste macht was der Nutzer gerne hätte.
  • Ist kein Item mehr markiert schließt die Eingabetaste wieder über IDOK den Dialog.

Ohne Maus kann man also mit den Pfeiltasten, der Leertaste (evtl. Strg-Taste) und der Eingabetaste diesen Dialog bedienen. Und das sogar intuitiv, denn der entsprechende Default Button wird ja in der UI schön umrandet und hervorgehoben.
Das freut jeden Tastaturfreund. Und man muss gar nichts groß machen mit der Behandlung Eingabetaste.

Damit das Ganze nicht so abstrakt ist, habe ich ein kleines Sample gebaut, dass diese Anwendung zeigt. Es hat keine Implementierung für Drag&Drop aber es macht deutlich, wie man dem Tastaturnutzer entgegen kommen kann indem man die Controls geschickt aktiviert.

VS-Tipps & Tricks: Springe zur nächsten Klammer funktioniert auch für #if, #elif, #else und #endif

Wer sich schon durch die Windows Header gekämpft hat um herauszufinden warum welche Definition einer Struktur oder Funktion in irgend einer Windows Version so oder gar nicht vorhanden ist, der weiß auch wie einem #if, #elif, #else und #endif das Leben schwer machen können, was die Orientierung betrifft.

Netterweise hilft einem eine Funktion, die man nur von Blöcken und verschachtelten Funktionen her kennt Strg+´ (Edit.GotoBrace). Wichtig! Man darf nicht auf der Variable oder Bedingung stehen, sondern muss auf dem Schlüsselwort stehen.

Wenn man auf einer Präprozessor Direktive kann man mit den Tasten die einem zur passenden Klammer bringt zur nachfolgenden Direktive. Und mit dem Festhalten der Umschalttaste kann man den entsprechenden Block auch markieren.

Windows Integrity Control: Schreibzugriff auf eine Named Pipe eines Services über anonymen Zugriff auf Vista, Windows 2008 Server und Windows 7

Mein Problem war ein Service, der eine Pipe als Interface zur Verfügung stellt, auf die von beliebigen PCs zugegriffen werden soll. Insbesondere eben auch von PCs außerhalb der Domäne in der der Service läuft der die Named Pipe zur Verfügung stellt. Die Clients melden sich über VPN an dem entsprechenden Server an, gehören aber eben selbst nicht zur Domäne.

Von einem Rechner, der nicht in der Domäne des Rechners liegt, auf eine Pipe eines Rechners in einer Domäne zuzugreifen war schon immer mit extra Vorkehrungen verbunden. Der Code für den anonymen Zugriff auf eine Pipe finden wir als KB Artikel http://support.microsoft.com/kb/813414/en-us. Über die Qualität dieses Beispiels möchte ich mich hier allerdings nicht auslassen.

Dieses Sample ist nett für alle die XP und Windows 2003 Server nutzen. Und interessanter Weise funktioniert es auch auf Windows Vista, Windows 7 oder Windows Server 2008. Aber nur weil dieses Beispiel die Pipe nur lesend benutzt.

Dieses Programm berücksichtigt nicht die neuen Sicherheitsfunktionen von Vista, Windows 7 oder Windows Server 2008. Es klappt genau dann nicht, wenn der Server eben auf einem Vista, Windows 7, oder Server 2008 läuft UND die Pipe für Lesen und Schreiben geöffnet wird.  ERROR_ACCESS_DENIED (5) ist das was der Client dann bekommt, wenn die Pipe lesend und schreibend geöffnet werden soll.

Was ist das Problem ❓

Das Problem liegt in einem Sicherheitsmechanismus, der sogenannten Windows Integrity.
Ich rate jedem die Literatur der nachfolgenden beiden Links:

 

Kurzbeschreibung:
Prozessen oder auch anderen Systemobjekten wird ein Inegrity-Level zugeordnet.
Greift nun ein Prozess auf ein Systemobjekt zu, dann werden nicht nur die allgemeinen Rechte geprüft (z.B. Rechte für Anonymen Zugriff, oder die entsprechenden Gruppenrechte), sondern auch der Integrity Level dieses Prozesses wird mit dem der Ressource verglichen.
Liegt nun der Integrity Level des Clients niedriger als der Intergrity Level des Objektes, dann greift eine neue Policy, die dann festlegt was passiert. In den meisten Fällen bedeutet dies, dass der Lesezugriff zugelassen wird, aber der Schreibzugriff untersagt wird.

Übrigens passiert das gleiche, wenn man ein Addin für den IE8 auf Vista (und später hat). Der IE8 läuft im geschützten Modus und damit im Integrity Level Low. Normale Programme laufen im Integrity Level Medium.
Möchte nun das IE8 Addin eine Pipe oder einen Shared Memory Block eines Services oder eines „normalen“ anderen Programmes, schreibend öffnen, dann passiert das gleiche. Der Zugriff wird reduziert auf nur lesen.
Und dieses Thema wird hauptsächlich in der Doku und im Netz diskutiert (und zum Teil, gelöst).

Ich will das jetzt hier nicht über die Maßen ausdehnen. Ich kann nur anraten, die entsprechenden Artikel wirklich einmal zu lesen.

Jetzt zurück zu dem was NICHT in diesen entsprechenden Artikeln und Forumbeiträgen steht.

1. Anonymen Zugriff wird automatisch der Integrity Level Untrusted zugewiesen. Die Doku zeigt in dem Beispiel nur die Vorgehensweise für den Integrity Level low.
Mein Fehler war es, die ganze Zeit SECURITY_MANDATORY_LOW_RID zu verwenden. Aber ich sagte es schon: Der  Anonyme Zugriff erfolgt im Integrity Level Untrusted.

2. Ich arbeite mit SDDL String und auch dort in den Headern ist Low das niedrigste für das es im Netz und auch in der Doku (http://msdn.microsoft.com/en-us/library/bb625963.aspx) findet. Dort steht ziemlich weit unten ein Beispiel, um ein Objekt einem „Low“-Integrity-Prozess zugänglich zu machen. Und dort finden wir folgende SDDL Definition:

#define LOW_INTEGRITY_SDDL_SACL_W L"S:(ML;;NW;;;LW)"

3. Anonymer Zugriff ist also Integrity Level Untrusted (ich weiß ich wiederhole mich).
Und nun wird es spaßig. Es gibt auch gar keinen SDDL Sid für untrusted, es gibt nur die folgenden Definitionen in den Headern:

// Integrity Labels
#define SDDL_ML_LOW                         TEXT("LW")
#define SDDL_ML_MEDIUM                      TEXT("ME")
#define SDDL_ML_MEDIUM_PLUS                 TEXT("MP")
#define SDDL_ML_HIGH                        TEXT("HI")
#define SDDL_ML_SYSTEM                      TEXT("SI")

Alles das hat mich in die Irre geführt und 2 Tage Arbeit gekostet.

Als mir klar war, dass ich eine Regel für den Untrusted Intergity Level benötige, war die Lösung gefunden.
Bauen wir uns einen eigenen SID! Aus der Doku können wir die entsprechenden RIDs finden und uns nun folgenden SID konstruieren: „S-1-16-0“.
Und das bringt uns zu dem SDDL String für den Level untrusted:

#define UNTRUSTED_INTEGRITY_SDDL_SACL _T("S:(ML;;NW;;;S-1-16-0)")

Ich habe das oben beschriebene Sample etwas modifiziert, dass man genau dieses Problem des anonymen Zugriffs auf eine Pipe testen kann. Das Beispiel kann man hier herunterladen.
Ich habe das Sample dazu etwas umgebaut:

  • so dass lesender und schreibender Zugriff auch benutzt wird.
  • dass SDDL verwendet wird, was den Code extrem simplifiziert.
  • dass zumindest etwas an dem ekligem Code lesbarer wurde.
  • dass ein paar Bugs raus sind.

Ich bitte dennoch dies nur als Sample zu betrachten und nicht als Beispiel Software, die meinem Anspruch an C/C++ Code genügt 🙂

PS: Eine entsprechende Diskussion über das Problem findet sich in nntp://microsoft.public.de.vc
http://groups.google.de/group/microsoft.public.de.vc/browse_thread/thread/3be20505999b8aab
Danke noch mal explizit an den Regular Andreas Heyer für seinen wertvollen Diskussionsbeitrag.

AfxMessageBox versus CWnd::MessageBox

Jeder MFC Entwickler kennt AfxMessageBox. In der Klasse CWnd finden wir auch die Memberfunktion CWnd::MessageBox.
Mancher Entwickler wird sich nun fragen: Wann nehme ich denn nun was?

Kurze Antwort: Meide CWnd::MessageBox und nutze immer AfxMessageBox

Lange Antwort:
CWnd::MessageBox ist einfach nur ein direkter Wrapper für Windows API Funktion MessageBox.
Der Grund warum man AfxMessageBox verwenden sollte liegt darin was AfxMessageBox einfach noch mehr leistet:

  1. AfxMessageBox benutzt die virtuelle Funktion CWinApp::DoMessageBox. Damit kann man zentral eine andere Behandlung für Fehlermeldungen einbauen.
  2. AfxMessageBox sorgt dafür, dass OLE Controls benachrichtigt werden, dass Ihre nicht modalen Dialoge nun deaktiviert werden müssen. Es wäre ja ziemlich heftig, wenn man solche Dialoge trotz aktiver MessageBox noch benutzen könnte. (siehe CWinApp::DoEnableModeless und Implementierung von CWnd::GetSafeOwner)
  3. CWnd::MessageBox benutzt das aktuelle Fensterhandle aus dem es aufgerufen wird als Parent-Handle für die API Funktion MessageBox. Die wenigsten Entwickler kennen, lesen oder beachten den netten Zusatz in der MessageBox Doku:
    If you create a message box while a dialog box is present, use the handle of the dialog box as the hWnd parameter. The hWnd parameter should not identify a child window, such as a control in a dialog box.
    Es ist also gerade zulässig CWnd::MessageBox aufzurufen, denn oft genug haben wir es mit Child-Windows zu tun.
    Netterweise beachtet AfxMessageBox genau das. Es ermittelt das aktuelle aktive Top-Level Fenster und setzt es intern für den Aufruf von MessageBox.
    Das wird auch ganz besonders wichtig, wenn man evtl. mehrere UI Threads hat. AfxMessageBox verwendet automatisch den richtigen. CWnd::MessageBox verwendet den aktuellen Thread aber evtl. als Fenster das Fenster-Handle aus einem anderen Threads.
  4. CWnd:MessageBox hat keine Implementierung für die F1-Taste und die Hilfeimplementierung der MFC.
  5. Ich muss mich nicht um den Titel der MessageBox kümmern, denn AfxMessageBox benutzt den hinterlegten Applikationsnamen (CWinApp::m_pszAppName) der in der MFC hinterlegt und definiert wurde.
  6. Netterweise setzt AfxMessageBox passende Icons wenn nur die Reaktionsform definiert wird, also kein Icon. (MB_OK und MB_OKCANCEL benutzt MB_ICONEXCLAMATION, MB_YESNO und MB_YESNOCANCEL benutzt MB_ICONQUESTION).

HTH

Sharepoint Timer Service frisst Giga-Bytes an Speicherplatz und kostet Performance

Ich habe einen Server auf dem mein TFS läuft. D.h. es ist eine Ein-Server Installation. SQL-Server, TFS und SharePoint liegen alle auf einem Windows 2003 R2.

In der letzten Zeit hatte ich schon das Gefühl, dass der erste Kontakt zum TFS ziemlich langsam war, bzw. auch das erste Speichern eines Tasks, oder Bugs.

Vor einigen Tagen dann bekam ich eine Meldung, dass kein Backup mehr durchgeführt wurde.
Eine Analyse ergab, dass auf dem TFS mit einem 100GB Raid5 Laufwerk nur noch 200MB frei waren. Eine Suche ergab, dass sich im Verzeichnis C:\Programme\Gemeinsame Dateien\Microsoft Shared\Web Server Extensions\12\LOGS\ weit über 22GB an Daten angesammelt hatten.

Eine weitere Analyse ergab, dass alle 15 Sekunden ca. 4000 Zeilen mit dem folgenden Text erzeugt wurden.

Alle 15 Sekunden ca. 4000 Einträge

11/02/2009 11:59:14.87  OWSTIMER.EXE (0x0F30)                    0x049C Windows SharePoint Services    Timer                          5uuf Monitorable Die vorhergehende Instanz des Timerauftrags ‚Config Refresh‘, ID {0CA4803D-1621-49F4-BEFC-1BA2B441AC28} für den Dienst ‚{8B6CADF9-8ECE-409C-8D32-E336A5564C04}‘ wird noch ausgeführt. Die aktuelle Instanz wird deshalb übersprungen. Sie sollten eine Vergrößerung des Intervalls zwischen den Aufträgen in Erwägung ziehen.

Einiges suchen im Internet ergab, dass ich nicht alleine an diesem Problem leide. Es gibt sogar einen KB Artikel dazu: http://support.microsoft.com/kb/941789/en-us/

Letzten Endes kann man die Warnungen unterdrücken, indem man den Logging Level verändert. Das geht einmal wie beschrieben über die SharePoint 3.0 Central Administration. Aber weitaus einfacher geht es auch über die Befehlszeilentools.

stsadm -o setlogginglevel -category timer -tracelevel unexpected 

Das ganze kostet aber immer noch einiges an Performance, denn dies unterdrückt nur die Protokollierung des Problems. Ein Refresh des Caches ist aber wirklich nicht alle 15 Sekunden notwendig, wie es die Standardeinstellungen vorsehen. Den Prozess alle 5 Minuten laufen zu lassen langt auch.
Das erreichen wir durch:

stsadm -o setproperty -propertyname job-config-refresh -propertyvalue "Every 5 minutes between 0 and 59"

Die entsprechende Doku dazu findet sich hier:
http://technet.microsoft.com/en-us/library/cc424971.aspx
http://technet.microsoft.com/en-us/library/cc261740.aspx

Die Standardwerte kann man wieder setzen durch die Befehle:

stsadm -o setlogginglevel -default -category timer
stsadm -o setproperty -propertyname job-config-refresh -propertyvalue "Every 15 seconds"

Ich hatte zu dem Problem auch den Microsoft Support bemüht, allerdings erfuhr ich hier auch nicht mehr, als ich selbst schon ermittelt hatte. Allerdings wurde mir angedeutet, dass es zu diesem Problem auch einen „noch“ inoffiziellen Fix gibt. Mal sehen ob sich hier mal noch etwas tut.

Mein fix behebt zumindest das Problem mit den extrem vielen Log-Datei Daten- Und auch die Performance des TFS ist wieder etwas besser geworden, nachdem der entsprechende Timer Job nur noch alle 5 Minuten läuft.

Weitere Links zu OWSTIMER und den Timer Jobs des Sharepoint 3.0