ATL


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 ;)

ATLC++MFCProgrammierenWindows APIMartin Richter - So 14 Dez 2008 15:52

CMemDC aus dem Artikel Flicker free drawing with MFC ist wohl der Klassiker für Doublebuffering und ist wohl jedem MFC Entwickler bekannt.

Er macht was er soll und ich habe ihn selbst über Jahre hinweg unverändert verwendet. Solange man einfache Sachen macht und ausschließlich den MM_TEXT Mappingmode verwendet ist alles OK.
Hat man sich aber was spezielles – zoomfähiges – zusammengebaut mit MM_ANISOTROPIC dann funktioniert das Ganze nicht mehr, weil Keith an den Window Origin Koordinaten dreht und nicht am Viewport.

Meine abgewandelte Variante trägt einer entsprechende Verwendung auch für andere Mappingmodes Rechnung. Es müsste IMHO für jeden funktionieren. Intensiv getestet habe ich es mit MM_ANISOTROPIC.

Will/muss man auch an den Viewport Koordinaten drehen, dann sollten die alten Viewport Koordinaten gelesen werden und dann entsprechend durch ein Offset geändert werden.
Alles anderen Werte wie die Extents können beliebig verändert werden.
Bei der Verwendung sollte MM_TEXT eingeschaltet sein (entsprechende ASSERTs habe ich ergänzt). MM_TEXT ist normalerweise gesetzt, wenn CMemDC direkt nach Instanzierung des CPaintDC zum Einsatz kommt. Der Destruktor setzt dann den Mappingmode auf MM_TEXT zurück.

Dieser neue, modifizierte CMemDC kann einfach die bisherige Implementierung ersetzen.

Have fun & Happy coding :-)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
class CMemDC : public CDC
{
public:
  // constructor sets up the memory DC
  CMemDC(CDC* pDC)
    : CDC()
    , m_pDC(pDC)
    , m_pOldBitmap(NULL)
    , m_bMemDC(!m_pDC->IsPrinting())
  {
    ASSERT(pDC != NULL);
 
    if (m_bMemDC)
    {
      // Create a Memory DC.
      // At this moment we should have mapping mode text
      ASSERT(m_pDC->GetMapMode()==MM_TEXT);
      ASSERT(m_pDC->GetWindowOrg()==CPoint(0,0));
      ASSERT(m_pDC->GetViewportOrg()==CPoint(0,0));
 
      // Get the clip box and create bitmap for this size
      m_pDC->GetClipBox(&m_rect);
      CreateCompatibleDC(m_pDC);
      m_bitmap.CreateCompatibleBitmap(m_pDC, 
              m_rect.Width(), m_rect.Height());
      m_pOldBitmap = SelectObject(&m_bitmap);
 
      // Adjust the view port so that we hit the clip rect.
      SetViewportOrg(-m_rect.left, -m_rect.top);
      IntersectClipRect(m_rect);
 
      // Fill background in case the user has overridden
      // WM_ERASEBKGND.  We end up with garbage otherwise.
      FillSolidRect(m_rect, m_pDC->GetBkColor());
    }
    else
    {
      // Make a copy of the relevant parts of the current DC for printing
      m_bPrinting = m_pDC->m_bPrinting;
      m_hDC     = m_pDC->m_hDC;
      m_hAttribDC = m_pDC->m_hAttribDC;
    }
  }
 
  // Destructor copies the contents of the mem DC to the original DC
  ~CMemDC()
  {
    if (m_bMemDC)
    {
      // The mapping mode might effect the BitBlt. So we need to return to
      // MM_TEXT. This makes m_rect.left and m_rect.top again the
      // coordinates of our clipped rectangle. And to make sure that the
      // coordinates are really used we just clear the ViewportOrg and the
      // WindowOrg
      SetMapMode(MM_TEXT);
      SetViewportOrg(0,0);
      SetWindowOrg(0,0);
 
      // Copy the off screen bitmap onto the screen.
      // For this we just make sure that we really target the rectangle of
      // our temporal bitmap.
      m_pDC->BitBlt(m_rect.left, m_rect.top, 
              m_rect.Width(), m_rect.Height(),
              this, 0, 0, SRCCOPY);
 
      //Swap back the original bitmap.
      SelectObject(m_pOldBitmap);
    }
    else
    {
      // All we need to do is replace the DC with an illegal value,
      // this keeps us from accidentally deleting the handles associated
      // with the CDC that was passed to the constructor.
      m_hDC = m_hAttribDC = NULL;
    }
  }
 
  // Allow usage as a pointer
  CMemDC* operator->()  {   return this;  }
 
  // Allow usage as a pointer
  operator CMemDC*()  {   return this;  }
 
private:
  CBitmap  m_bitmap;    // Offscreen bitmap
  CBitmap* m_pOldBitmap;  // bitmap originally found in CMemDC
  CDC*   m_pDC;     // Saves CDC passed in constructor
  CRect  m_rect;    // Rectangle of drawing area.
  bool   m_bMemDC;    // true if CDC really is a Memory DC.
};
ATLProgrammierenSonstigesWindows APIMartin Richter - Mi 10 Dez 2008 19:54

Ich benutze als interne Makrosprache gerne VBScript. D.h. ich hoste VBScript.
Ich biete dann bestimmte COM-Objekte (IDisptach) an, die es dem Nutzer erlauben mein  Programm anzupassen oder intern zu steuern.

Nun macht es aber unter Umständen einen gravierenden Unterschied, ob ein COM-Objekt von einem programminternen Nutzer, oder von extern angesprochen wird. Intern möchte ich zum Beispiel mehr  oder andere Funktionen erlauben als durch einen externen Zugriff von einem anderen Programm oder Skript.

Wie kann man aber unterscheiden von wo ein COM Zugriff erfolgt :?:

Netterweise hat COM die Funktion CoGetCallContext, die mir genau diese Info liefert. Liefert CoGetCallContext S_OK und damit einen Interfacezeiger auf IServerSecurity, dann wurde der COM Zugriff von einem externen Prozess abgesetzt. Wenn CoGetCallObject E_NOINTERFACE zurück gibt haben wir einen internen Aufruf aus dem eigenen Prozess heraus.

bool IsExternalComCall()
{
 // get context
 CComPtr<IServerSecurity> spSecurity;
 HRESULT hr = ::CoGetCallContext(IID_IServerSecurity,
                      reinterpret_cast<void**>(&spSecurity));
 return SUCCEEDED(hr);
}

Herzlichen Dank an René König für diesen Ansatz, den er mir in microsoft.public.de.vc gab.

ATLC++CRTDebuggingMFCProgrammierenVS-Tipps&TricksWindows APIMartin Richter - Di 04 Nov 2008 20:30

Mancher Bug macht einem nicht den Gefallen und lässt sich in der Debug-Version finden. Ursache ist oft genug eine Variable, die in der Debug-Version initialisiert (0xCC) wird aber in der Release-Version zu einem Crash führt, wenn zufällige Daten auf dem Stack für undefiniertes Verhalten sorgen.

Also macht man sich an das debuggen der Release Version und kann keinen Fehler finden.
Kaum startet man das Programm ohne Debugger dann kracht es wieder. Warum?

Manch einer könnte jetzt denken: Der Debugger verändert das Memory Layout! Das tut er schon, aber entscheidend für ein anderes Verhalten ist der Debug Heap!
Die wenigsten Entwickler wissen überhaupt, dass es ihn gibt. Ich meine hier nicht die Debug Funktionen, die die CRT zur Verfügung stellt, denn mein Thema heute ist ja das Debuggen eines Release-Programms, und die Debug-CRT hat ja bekanntlich in einem Release Programm nichts zu suchen!

Machen wir es praktisch und nehmen wieder mein kleines Crashtest-Programm:

#include <windows.h>
#include <tchar.h>
#include <crtdbg.h>
int _tmain(int argc, _TCHAR* argv[])
{
  char *pCorrupt = new char[100];
  ZeroMemory(pCorrupt,104);
  char *pOther = new char[100];
  ZeroMemory(pOther,100);
  delete [] pOther;
  delete [] pCorrupt;
  return 0;
}

Wenn wir dieses Programm als Release Version kompilieren und ausführen, dann erhalten wir keine Fehlermeldung :!: Interessant. Der Heap ist nicht soweit zerstört, dass es zu einer Zugriffsverletzung kommt. Starten wir aber unser Programm mit dem Debugger, dann wird der so genannte Debug Heap des Systems verwendet, der wie die Debug-CRT Guardbytes setzt und kontrolliert.

Ein weiteres Problem entsteht dadurch, dass der Debug Heap den allokierten Speicher auf feste Werte initialisiert genau wie die Debugversion der CRT. Wenn also nicht initialisierter Speicher genutzt wird, dann ist das Verhalten mit dem Debug-Heap deterministisch, ohne Debug Heap eher zufällig.

Das im Debugger alles etwas anders sein kann ist sogar dokumentiert ;)

Processes that the debugger creates (also known as spawned processes) behave slightly differently than processes that the debugger does not create.
Instead of using the standard heap API, processes that the debugger creates use a special debug heap. On Microsoft Windows XP and later versions of Windows, you can force a spawned process to use the standard heap instead of the debug heap by using the _NO_DEBUG_HEAP environment variable or the -hd command-line option.

In diesem Text steht auch, wie man den Debug-Heap ausschalten kann, mit:

SETX _NO_DEBUG_HEAP 1

Diese Environment-Variable sorgt dafür, dass sich auch bei geladenem Debugger, das Programm so verhält wie ohne Debugger (hoffentlich). Führt man mein Testprogramm nun im Debugger aus, wenn die Environment-Variable _NO_DEBUG_HEAP auf 1 gesetzt ist, erhält man keinen Debug-Break mehr. Denn in diesem Fall gibt es keine Guardbytes, die geprüft werden.
Löscht man den Eintrag _NO_DEBUG_HEAP wieder, dann erhält man im Debugger wieder wie erwartet einen Break.

Will man also wirklich realitätsnah eine Release-Version debuggen, dann kommt man um das Ausschalten des Debug-Heaps nicht herum.

PS: Man kann es auch etwas einfacher haben, wenn man sich nachträglich an den Prozess mit dem Debugger attached (wenn das geht). Ideal ist dieses Verfahren auch beim Remote-Debugging (dazu demnächst mehr).

Nächste Seite »