ATL


ATLC++MFCProgrammierenVS 2015Windows 10Windows 10Martin Richter - Fr 24 Feb 2017 15:29

Wer es schnell mag, kann gleich den Beitrag und technische Details auf Stack Overflow lesen:
http://stackoverflow.com/questions/41741448/random-crashes-on-windows-10-64bit-with-atl-subclassing

Hier möchte ich etwas ausführlicher, die Story dazu erzählen, eine Story aus dem ganz „normalen“ Leben eines Programmierers 😉

Letztes Jahr stellen wir in der Firma produktiv von VC-2013 auf VC-2015 um. Wir haben eine relativ große Anwendung, die im Kern, MFC und ATL verwendet. Unsere Anwendung zeigt Informationen in vielen Tabs und Dialogen an. Mehr als 256 Fenster sind da keine Ausnahme und eher die Regel als eine Seltenheit.

Unsere Anwendung erzeugt bei Crashes automatisch volle Dumps auch bei Kunden. Für unsere Qualitätssicherung ein Muss und ein Segen.
Bereits in der Alpha und Beta Phase hatten wir auf manchen Rechnern ein eigentümliches Phänomen. Unser Programm startete und erzeugte Fenster und auf einmal kam es zu einem Absturz bei oft ganz einfachen Windows API Funktionen, die ein Fenster verwendeten und die alle ausnahmslos letzten Endes dazu führten, dass eine Nachricht an ein Fenster gesendet wird. Der Stack war nichtssagend und schien zerstört. Die Dumps für die einzelnen User waren aber fast immer glich.
Das Problem trat in Release und Debug Builds auf.

Der Horror für jeden Programmierer. Nicht nachzuvollziehende Crashes auf manchen Rechnern. Heapfehler? Wilder Zeiger? Buffer-Overrun? Nichts was man gebrauchen kann… aber kein Analysetool was wir verwendeten schlug Alarm…

Nach Sammlung von mehreren Dumps waren die Crashes immer wieder an ähnlichen Stellen und fast immer alle sofort nach Programmstart. Betroffen in der Regel alles Windows 10 64bit Maschinen auf dem aktuellen Softwarestand, meistens hatten diese Maschinen 8GB und zum Teil weitaus mehr Speichern und es waren ausnahmslos schnelle Intel i7 verschiedener Generationen. Und in virtuellen Maschinen konnte ich das bisher nicht nachvollziehen.

Interessant, war, dass ich diese Crashes auch auf meiner Entwicklungsmaschine hatte. Manchmal…
Nach einigen Tests konnte ich sagen. Entweder tritt der Fehler auf und solange der Rechner gestartet ist kann man es manchmal nachvollziehen, oder der Fehler tritt eben nicht auf und man hat Ruhe vor ihm. Dann hatte ich mir 3 Tage hintereinander – ohne Rechnerneustart – mit Debug-Session auf Debug-Session eine Spur erarbeitet. Es war immer das 257 Fenster, dass gesubclassed wurde (mit ATL Thunking) oder erzeugt wurde (ATL Fenster), dass zum Crash führte.
Insofern war es bei jedem User auch immer ein ähnlicher Dump, weil der Fensteraufbau ja bei jedem User individuell war.

Am Ende des dritten Tages hatte ich einen Testcode, der den Fehler in der aktuellen Windows Session meines Rechners und meinen Programm zu 100% nachvollziehbar machte. Ich verschob den Testcode immer weiter in einem Programm nach vorne, bis ich bei InitInstance ankam.
Ups. Es liegt nicht an meiner Software.
Dann isolierte ich den Testcode in eine eigenes Programm und konnte den Bug in einem minimalen Sample nachstellen.

Zeit für einen Support-Case bei Microsoft und eine Anfrage auf stackoverflow.com (das war am 19.01.2017).

Mit dem Case hatte ich natürlich auch einen Workaround. Ich musste nur einfach die atlthunk.dll nicht benutzen und auch das ging mit einer einfachen Änderung in der atlstdthunk.h. (Auskommentieren des defines USE_ATL_THUNK2).

Die Kommunikation mit Microsoft gestaltete sich nicht einfach. Aber ich hatte zumindest einen konstruktiven MS Mitarbeiter aus Indien erwischt. Dumps konnte ich Microsoft liefern, auch den Crash direkt in einer Session zeigen. Code hatten sie auch, aber der Bug war bisher nicht bei Ihnen nachvollziehbar. Das trieb mich fast in die Verzweiflung. Natürlich musste alles andere ausgeschlossen werden.. Virenscanner, andere Software… etc…
Dennoch kein Repro auf eine Microsoft Maschine. Allerdings weiß ich nicht wie „intensiv“ da gesucht wurde.

Dann endlich ein Lichtblick auf stackoverflow.com. Eugene konnte den Bug nachvollziehen. Endlich.
Und nachdem ich wusste, dass es mit der bevorzugten Ladeadresse zu tun hatte, konnte ich auch meinen Code erweitern und mit 100% Wahrscheinlichkeit den Crash erzeugen.
Wenn also an der bisher bevorzugten Ladeadresse der atlthunk.dll kein Speicher zur Verfügung liegt und sofort wieder eine Relocation notwendig wird, dann kracht es. Lustig, da wir ja durch ASLR sowieso keine festen Ladeadressen haben, aber es gibt innerhalb einer Windows Session dennoch eine „bevorzugte Ladeadresse“.

Jetzt bin ich gespannt auf Microsoft…
Denn defakto kann jede Software die mit dem VC-2015 und VC-2017 Compiler und der ATL kompiliert wurde von diesem Problem betroffen sein.

PS: Man entschuldige mir Ungenauigkeiten oder auch falsche Begrifflichkeiten…
Bis heute habe ich den Fehler immer noch nicht ganz verstanden und ganz sooo tief lebe ich im Kernel von Windows doch nicht. 😀

PPS: Ich werden den Artikel auf stackoverflow.com aktuell halten.

ATLC++CRTMFCProgrammierenVS 2013Martin Richter - Mi 21 Aug 2013 17:32

Im Microsoft C++ Team Blog kann nachgelesen werden was sich in ALT+MFC in VS-2013 tun wird:
http://blogs.msdn.com/b/vcblog/archive/2013/08/20/atl-and-mfc-changes-and-fixes-in-visual-studio-2013.aspx

Wer hätte es gedacht…, ja es gibt wieder etwas „Neues“, auch wenn es mehr oder weniger nur Bugfixes sind.

Besonders erwähnenswert ist der Wegfall des MBCS Supports in der MFC, siehe auch hier.
Das die ATL jetzt nur noch als statische Bibliothek mit sehr kleinem Footprint existiert ist nett. Warum eigentlich nicht gleich so?

Nachtrag und Korrektur durch den aufmerksamen Blog-Leser Sasche:

Wie auch im verlinkten Artikel beschrieben, fällt der MBCS Support in VS-2013 noch nicht weg, sondern ist erst mal nur “deprecated” (und die entsprechenden Libs nur noch als separater Download verfügbar). Erst in einer Folgeversion soll der Support für MBCS ganz wegfallen.

 

ATLC++MFCProgrammierenVS 2012Windows APIMartin Richter - Fr 04 Mai 2012 09:15

Gestern Abend hatten wir hier auf der ADC 2012 für C++ in Ohlstadt bei einem schönen Abendevent eine Q&A. Letztes Jahr fand diese Q&A auf einer Schiffahrt auf dem Chiemsee statt. Dieses Jahr war es ein Fußweg von ca. 20 Minuten vom Konferenzhotel, zu einem großen „Grillplatz“, dort standen Zelte, Fackeln Lagerfeuer und es wurde gut gegessen und wie immer viel „Networking“ betrieben.

Wie auch letztes Jahr sollten detailierte Fragen auf diese Q&A zu späterer Stunde vertagt werden.

DIE FRAGE die viele Entwicklern brennend interessierte war:
Kann man mit VS-11 Programme für Windows XP entwickeln oder nicht?

Rede und Antwort stand in diesem Fall Steve Teixeira, als Director of Program Management. Also in diesem Fall jemand, der wirklich etwas sagen und auch mit zu entscheiden hat.

Ich gebe seine Antwort von Steve, auf diese Frage zusammengefasst wie folgt wieder:

  1. Zu dem Zeitpunkt als die Entscheidung für das Fallenlassen vom XP-Support gefällt wurde erschien dies als richtig.
    Jetzt muss man allerdings eingestehen, dass diese Entscheidung ein Fehler von Microsoft war.
  2. Die Benutzerzahlen wurden weiter als sinkend berechnet. Man vermutete, dass zum Zeitpunkt der Veröffentlichung von VS-11 noch maximal 20% XP-Nutzer vorhanden wären. Neue Umfragen gehen aber von einer Verbreitung von mindestens noch 46% Windows XP Installationen aus.
  3. Bei Microsoft wurde auch vermutet, dass es genügt den Entwicklern die neue VS-11 Oberfläche anzubieten aber das für das Compilieren das Toolset von VS-2010 genügen würde. Es wurde unterschätzt wie groß das Interesse an den neuen Compiler Funktionen in VS-11 mit C++11  ist. Was eben auch AMP und neue STL Funktionalität einschließt.
  4. Die Folge ist nun, dass Microsoft die Entscheidung für das Fallenlassen des Windows-XP Support neu überdenkt.
    Allerdings kann dies nicht mehr bis zum RTM geschafft werden.
    (Anmerkung von mir: Aktuell in der Beta wurde der gesamte Code, der die Windows-Vista/7 Funktionen isoliert entfernt und alle DLLs werden implizit geladen).

Möglich ist also, dass es ein Featurepack geben wird, dass nach dem RTM ausgeliefert wird, und in dem es dann doch einen XP Support in allen Bibliotheken und im Compiler gibt.

Die Antwort erschien mir ehrlich und geradeaus und war gewisslich keine Vertröstung ohne echten Hintergrund.
Ich weiß nicht wie groß wirklich die Chancen sind, aber diese Aussage deckt sich auch mit den „Gerüchten“, die ich um 5 Ecken gehört habe, und deckt sich auch mit der internen Diskussion, die mit den MVPs geführt wird.

Meine persönliche Schätzung ist eine 25 prozentige Chance, dass wir doch noch einen Windows-XP Support im VS-11 erhalten werden.

Lassen wir und überraschen. Aber es ist eine gute Nachricht ❗

PS: Ich schreibe dies direkt von der ADC für C++ 2012 in Ohlstadt.

Nachträge und Kommentare habe ich direkt in den Blog-Post übernommen, da diese manchmal übersehen werden:

Kommentar 1 vom 04.05.2012 von Steive Teixeira zur Klarstellung:

Hi Martin,
It was great to see you at ADC C++ this week! Just so that there is no confusion for the readers of your blog, the issue of XP support for C++ in Dev11 is one we’re taking very seriously, and we’re continuing to take customer feedback on Dev11 beta. However, we are not yet prepared to make an announcement on platform support for the RTM version of Dev11. We will be making an announcement on this in the coming weeks.
Thanks!
Steve

Kommentar 2 vom 04.05.2012 von Michael Kühlshammer 

Steve Teixeira schickte mir heute einen Link für einen Workaround und empfahl mir, dass möglichst viele Leute auf dem unten angegebenen Link einen Beitrag dazu schreiben sollen (dafür voten sollen), dass der VC11-Compiler auch Code für Windows XP erzeugt. Hier die Email-Antwort von Steve Teixeira:

“Thanks for your email. We continue to devote resources to and support MFC in Dev11. The Windows XP issue is still unsettled, and I appreciate your feedback on this. You me have seen that I posted some of my thoughts on XP support in the comment thread of this blog entry on the VC++ team blog: http://blogs.msdn.com/b/vcblog/archive/2012/04/18/10295093.aspx.

Thanks again,
Steve

ATLCRTIDEMFCProgrammierenVS 2008VS 2010Martin Richter - So 16 Okt 2011 19:54

In den C++ Compilereinstellungen finden sich ein vorgegebener Makro %(PreprocessorDefinitions) in den C++ Präprozessor Definitionen. Die Verwendung dieses Makros ist nicht ganz offensichtlich.

Dieser Makro sollten in jedem Fall nicht entfernt werden, denn Sie dienen der Übernahme einiger Einstellungen aus der General-Seite für die C++ Projekte. Zum Beispiel werden die Einstellungen für Unicode und MBCS über den Makro %(PreprocessorDefinitions) in die allgemeinen Compiler-Einstellungen übernommen (die entsprechenden Defines sind _UNICODE; UNICODE; _MBCS ).
Erzeugt man eine DLL wird zusätzlich _WINDLL gesetzt.
Setzt man ATL Optionen in der General Seite wird auch über die %(PreprocessorDefinitions) _ATLDLL bzw. _ATL_STATIC_REGISTRY gesetzt oder zurückgesetzt.
Gleiches gilt, wenn die MFC als shared DLL verwendet wird. In diesem Fall wird der Define _AFXDLL zusätzlich gesetzt.

Löscht man also %(PreprocessorDefinitions) dann werden alle diese Einstellungen nicht mehr  korrekt übernommen.

Anmerkung:
Bei dem Linker Makro %(AdditionalDependencies) habe ich eine ähnliche Verwendung vermutet, konnte aber keine direkte Beziehung zur Seite General herstellen.

Obwohl es auch hier Einflüsse auf die Linkereinstellungen gibt bei Änderungen in den General-Einstellungen. Werden allerdings die MFC als zusätzliche Bibliothek ausgewählt werden die Standard-LIBs aus dem SDK komplett entfernt. Hier gibt die MFC Bibliothek selbst vor in welchen zusätzlichen Libs, des SDK gesucht werden soll über #pragma comment(lib,..).

ATLProgrammierenSQLMartin Richter - So 09 Okt 2011 19:38

Es ist erstaunlich von wie viel Technologie, sich Microsoft aktuell verabschiedet, oder Wege einschlägt, die manchen (zumindest mich) wirklich überraschen. Oder vielleicht überrascht es auch nicht, weil man gerade mit der Unberechenbarkeit der Branche rechnen muss!

OLE DB war vor Jahren die Technologie und jedem Entwickler wurde geraten von ODBC auf OLE DB umzusteigen, oder zumindest bei neuen Projekten OLE DB den Vorzug zu geben. Die höhere Performance und die größere Flexibilität haben bei weitem, die etwas komplexere Technik aufgewogen. Und Dank der guten ATL Client-Klassen war OLE DB auch zu handhaben.

Nun ist Schluß damit! Der nächste SQL-Server mit dem Codenamen Denali wird die letzte Version sein, die OLE DB unterstützen wird, so ist hier zu lesen:
http://blogs.msdn.com/b/sqlnativeclient/archive/2011/08/29/microsoft-is-aligning-with-odbc-for-native-relational-data-access.aspx

Liest man die Kommentare aufmerksam, dann scheint es daran zu liegen, dass Microsoft Probleme hat, OLE DB in der Cloud vernünftig zum laufen zu bekommen. Microsoft macht es sich scheinbar einfach und schießt eine ganze Technologie ab, die mal als „die Technik“ eingeführt wurde.
D.h. eine langsamere Technik wird nun der Standard werden.
Für mich wieder mal eine Entscheidung, die sich mir in keiner Weise erklärt.

Ich verfolge die Diskussion um den MS-SQL Server nicht permanent und so ist mir dieser Artikel erst jetzt untergekommen.

Mehr Infos auch in diesem Thread, der eine kleine FAQ enthält:
http://social.technet.microsoft.com/Forums/en/sqldataaccess/thread/e696d0ac-f8e2-4b19-8a08-7a357d3d780f

Heftig finde ich die Antwort auf Frage 6, die einem auch kaum Hoffnung macht mit einem älteren Client evtl. noch auf den neuen Server zugreifen zu können:

Question6: If I have an OLE DB application that I write for Denali, will it be supported on a post Denali version of SQL Server that is released during the life of Denali?

Answer: No, in fact we may explicitly block the OLE DB applications on post-Denali versions of SQL Server. It is recommended that you plan your migration soon to ODBC, if you want to start using newer versions of SQL Server as soon as they release.

PS: Wie erkläre ich wohl meinen Kunden, die auf einmal 10%-30% langsamere Datenzugriffe haben werden, gerade wenn es größere Datenmengen geht ?  Werden die mir glauben, wenn ich sage: Microsoft will es so?  🙁

ATLC++DebuggingMFCProgrammierenMartin Richter - Fr 10 Sep 2010 17:41

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

ATLC++MFCProgrammierenVS 2008VS 2010Martin Richter - Di 17 Aug 2010 18:56

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[] ❗

ATLProgrammierenSQLWindows APIMartin Richter - Mo 18 Jan 2010 20:19

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.

ATLMFCProgrammierenMartin Richter - Do 30 Apr 2009 20:50

Immer wieder mal, taucht MFC Code auf und es wird CoInitializeEx verwendet.
Nicht unbedingt ein Fehler, aber ich denke man sollte auch hier die MFC-Art-und-Weise verwenden: AfxOleInit. Und man sollte sich klar sein, welche Apartment Modes man verwenden darf!

AfxOleInit selbst ist ein per Thread Wrapper für ::OleInitialize(NULL);.
Zudem sorgt AfxOleInit dafür, dass bei Beenden des Threads automatisch AfxOleTerm aufgerufen wird.

::OleInitialize(NULL) wiederum ist ein Wrapper für den Aufruf von CoInitializeEx im STA Kontext:
Siehe MSDN http://msdn.microsoft.com/en-us/library/ms690134(VS.85).aspx 

OleInitialize calls CoInitializeEx internally to initialize the COM library on the current apartment. Because OLE operations are not thread-safe, OleInitialize specifies the concurrency model as single-thread apartment.

Die MFC benutzt und verlangt genau diesen STA Modus. Sofern man also nicht wirklich weiß was man mit CoInitializeEx macht, sollte man es in der MFC vermeiden. Zudem man sich eben auch nicht mehr um den Cleanup kümmern muss, da die MFC dies selber macht.

Siehe auch MSDN Doku zu AfxOleInit
http://msdn.microsoft.com/de-de/library/e91aseaz(VS.80).aspx

MFC applications must be initialized as single threaded apartment (STA). If you call CoInitializeEx in your InitInstance override, specify COINIT_APARTMENTTHREADED (rather than COINIT_MULTITHREADED). For more information, see PRB: MFC Application Stops Responding When You Initialize the Application as a Multithreaded Apartment (828643) at http://support.microsoft.com/default.aspx?scid=kb;en-us;828643.

Aber auch hier sei erwähnt, dass man sicherlich auch andere Apartment Modes verwenden kann. Allerdings muss man dann wirklich wissen was man macht und darf sich nicht auf die MFC Funktionen stützen. Für solche Fälle verwende ich hier komplett ATL in meinen MFC Programmen.
Man sollte aber in diesen Fällen wirklich wissen was man macht 😉

BTW: Automatisch aufgeräumt bei einem gestarteten Thread wird nur dann, wenn auch AfxBeginThread verwendet wird. Siehe auch: AfxBeginThread versus _beginthreadex

ATLC++Windows APIMartin Richter - Mi 15 Apr 2009 22:05

Wenn man ein Web Browser Control einbindet und dieses eine Seite lädt, dann wird der Fokus in dieses Browser Control gesetzt. Dagegen ist kein Kraut und keine Notification gewachsen.
Genaugenommen ist nicht das Webbrowser Control schuld, sondern der Scriptcode der auf der Seite läuft und den Fokus umsetzt. Das sieht man schnell wenn man den Moment der WM_KILLFOCUS Nachricht im Debugger abpasst und sich den Stacktrace ansieht.

0013b84c 4a570824 USER32!NtUserSetFocus 
0013b858 4a5ce628 mshtml!CDoc::TakeFocus+0x2a 
0013b880 4a63fc0b mshtml!CElement::BecomeCurrent+0x167 
0013b8b4 4a63fb72 mshtml!CElement::focusHelper+0xcc 
0013b8c0 4a587c85 mshtml!CElement::focus+0x1d 
0013b8cc 4a5d7477 mshtml!Method_void_void+0x17 
0013b94c 4a57fae8 mshtml!CBase::ContextInvokeEx+0x462 
0013b97c 4a575413 mshtml!CElement::ContextInvokeEx+0x72 
0013b9b0 76fa5295 mshtml!CElement::ContextThunk_InvokeEx+0x44 
0013b9e8 76fa5208 jscript!IDispatchExInvokeEx2+0xa9 
0013ba20 76fa5323 jscript!IDispatchExInvokeEx+0x56 
0013ba90 76fa577b jscript!InvokeDispatchEx+0x78 
0013bad8 76fa57c6 jscript!VAR::InvokeByName+0x1c1 
0013bb18 76fa4ab0 jscript!VAR::InvokeDispName+0x43 
0013bb3c 76fa5a14 jscript!VAR::InvokeByDispID+0xfb 
0013bd30 76fa46d8 jscript!CScriptRuntime::Run+0x195b 
0013bdf4 76fa506e jscript!ScrFncObj::Call+0x69 
0013be6c 76fa5f6a jscript!CSession::Execute+0xb8 
0013bf6c 76fa672f jscript!NameTbl::InvokeDef+0x183 
0013c040 76fa5295 jscript!NameTbl::InvokeEx+0xd2

Dummerweise gibt es kein Event mehr, das danach gefeuert wird, wenn der Skript-Code abläuft. Das letzte Event bevor das aktive Fenster den Fokus verliert ist OnDocumentComplete.

Es gibt auch einige Threads die dieses Thema behandeln, aber keine vernünftige Lösung. Von so manchen Timerlösungen halte ich nichts, die da so vorgeschlagen werden, wer weiß schon wann eine Seite geladen ist?
Besonders ärgerlich auch, wenn man das Browser Control nicht mal auf einem sichtbaren Fenster hat, sondern nur in einem versteckten Fenster hält. Auch in diesem Fall verliert das aktive Fenster den Fokus.

Aber mit einem kleinen Trick bekommt man es doch hin ( 🙂 warum sonst schreibe ich den Artikel )

  1. Man baut einen OnDocumentComplete Handler ein.
  2. Wenn das Event eintritt, besorgt man sich mit GetFocus das Fenster, dass aktuell noch den Fokus inne hat.
  3. Nun sendet man mit PostMessage eine selbst definierte Nachricht (#define WM_RESTOREFOCUS (WM_APP+x)) an den Container des Webbrowser Controls und übergibt als wParam einfach das Handle des Fensters, dass man soeben mit GetFocus ermittelt hat.
  4. Nach diesem Event wird der Skript-Code ausgeführt, der den Fokus stiehlt. Das stört uns nicht.
  5. Irgendwann kommt die Messageloop jetzt wieder an die Reihe und zieht die benutzerdefinierte Nachricht WM_RESTOREFOCUS aus der Queue.
  6. Man hat natürlich einen Handler für diese Nachricht im Container des Webbrowser Controls. Dieser macht nun nichts anderes als einen SetFocus auf das HWND Handle auszuführen, das im wParam übergeben wurde. Ein Test zuvor mit IsWindow versteht sich von selbst.

Dadurch, dass die Nachricht in der Message-Queue gepostet wird, wird sie zeitnah ausgeführt sobald wirklich der User wieder eine Chance selbst Eingaben zu machen. Problem zufriedenstellend gelöst.

Das sollte sich sogar mit C# oder VB hinbekommen lassen 😉

Nächste Seite »