C++MFCProgrammierenWindows APIMartin Richter - Mo 02 Apr 2012 21:46

Wenn man ein COM Objekt erzeugt und dieses im System über die ROT (Running Object Table) sichtbar, dann sollte man normalerweise Weak-Locks benutzen. Das kann man auch in der Doku zu RegisterActiveObject  nachlesen. Ansonsten wird es schwierig zu entscheinden, wann man seine Objekte zerstören kann.

Wenn aber nun eine Anwendung sichtbar gemacht wird, also das Objekt vom Benutzer übernommen wird, dann darf es ja nicht beendet werden, wenn der externe Erzeugende Prozess beendet wird und die letzte Referenz zu dem Objekt beendet wird.

Wie verhindert man das?

Die Lösung ist relativ simpel. Solange die Anwendung sichtbar ist, oder besser, wenn sie sichtbar wird ruft man einmalig CoLockObjectExternal auf! Dadurch wird ein weitere Lock auf das Objekt ausgeführt.
Aber Achtung :!: Hier wird keine Referenzzählung verwendet. Egal wie oft man CoLockObjectExternal aufruft, der Referenzzähler wird nur einmal erhöht.

Beendet der User das Programm entsperrt man das Objekt wieder. Sollten keine weiteren Objekte in der Anwendung benutzt werden, dann terminiert die Anwendung wenn man alles richtig gemacht hat ;)
Man ruft CoLockObjectExternal am Besten entweder auf, wenn die Anwendung sichtbar wird (WM_SHOWWINDOW) und erneut wenn WM_CLOSE aufgerufen wird. Die MFC macht alles fast automatisch richtig, bis eben auf die Aufrufe von CoLockObjectExternal, die man selbst im Code unterbringen muss, wie auch die Registrierung der Objekte in der ROT.
Ist noch eine externe Referenz vorhanden wird die Anwendung nicht terminiert, weil der interne Objektzähler der MFC dies verhindert (Code in CFrameWnd::OnClose). Ist kein externer Lock mehr vorhanden sperrt CoLockObjectExternal die Anwendung vom terminieren weil damit exakt eine Referenz aufrecht erhalten wird. Wird durch den Benutzer das Schließen der Anwednung angefordert wird dann im WM_CLOSE diese letzte Refrenz aufgelöst und die Anwednung kann terminieren auch wnen noch ein Eintrag in der ROT vorhanden ist. Dieser wird dann beim Beenden der Applikation auch entfernt.

C++ProgrammierenWindows APIMartin Richter - Mo 12 Mrz 2012 20:58

Es ist einfach ärgerlich, dass die Änderungen, die an der API mit den Common-Control 6.0 so mies dokumentiert sind.

Wir haben eine neu programmierte Standardklasse in ein selten benutzes (uraltes) Tool übernommen.
Auf einmal funktionierten Teile der UI nicht mehr richtig, die in unseren Produkten bisher fehlerfrei gearbeitet haben. Speziell hatten wir Probleme mit der Anzeige von Comboboxen.

Die Ursache war schnell gefunden:
GetComboBoxInfo liefert für hwndItem immer NULL, wenn die Applikation kein Common-Control 6.0 benutzt.
Es wäre alles viel schneller entdeckt worden, wären passende ASSERTs eingebaut worden an den Stellen, an denen man auch etwas bestimmtest erwartet. Eben hier, dass hWndItem nicht NULL ist. Aber vermutlich dachte der Programmierer: Wenn ich schon eine Information bekomme, dann ist diese Information bestimmt auch richtig und vollständig.
Pustekuchen … :( … aber wer will ihm das übel nehmen.

Das steht in der Doku zu COMBOBOXINFO natürlich nirgends drin (bzw. jetzt natürlich schon, weil ich eine entsprechende Community Addition gemacht habe).
http://msdn.microsoft.com/en-us/library/windows/desktop/bb775798(v=vs.85).aspx

C++ProgrammierenWindows APIMartin Richter - Mo 22 Aug 2011 19:20

Ein Stück Code in unserer Software behandelt Datenimport aus fremden Dateien. Nicht wenige alter DOS Programme erzeugen ja Daten im “OEM”-Zeichensatz (DBase etc.).
Wir haben in der Vergangenheit immer streng die T-Notation verwendet und eigentlich sollte dies einen vor vielen Überraschungen bewahren. Aber Pustekuchen.

Folgender Code sollte Daten aus einem OEM-Stream in einen CString umwandeln und eigentlich bin ich davon ausgegangen, dass das Ergebnis für Tabulatoren (‘\t’) und Zeilenschaltungen (“\r\n”) ergebnisneutral ist. D.h. in anderen Worten ich erwartete in dem String genauso viele Tabulatoren und Zeilenschaltungen vor wie nach der Konvertierung.
Die Umwandlung erfolgte mit solch einem Stück Code:

CString strText;
::OemToCharBuff(szText,CStrBuf(srText,len),len);

Sieht eigentlich harmlos aus. Allerdings musste ich feststellen, dass hier OemToCharBuffA und OemToCharBuffW ganz und gar nicht korrespondierende Ergebnisse liefern.
In der Unicode Version, also in der Version die OemToCharBuffW verwendet, wurden Tabulatoren zu 0x25cb L’○’ wchar_t  und Zeilenschaltungen zu 0x266a L’♪’ wchar_t und 0x25d9 L’◙’ wchar_t!

Führt man jedoch zuerst eine Kovertierung in den “ANSI/Windows/8bit”-Zeichensatz durch und konvertiert anschließenend diesen ANSI-String nach Unicode, dann ist alles gut und so wie man es erwartet.

Wer Lust hat das nachzubauen kann das mit dem folgenden Code. Wichtig sind eigentlich nicht die OEM-Umlaute sondern nur die Tab- und Zeilenschaltungen:

const char szText[] = "Dies ist ein OEM TestString\r\n"
       "mit Zeilenschaltungen\tund Tabs\r\n"
       "und Umlauten:\r\n"
       "Ž=AE, ™=OE, š=šE\r\n"
       "„=ae, ”=oe, =ue, á=ss";
const size_t len = sizeof(szText);
 
CStringW strOut1;
::OemToCharBuffW(szText,CStrBufW(strOut1,len),len);
CStringA strOut2;
::OemToCharBuffA(szText,CStrBufA(strOut2,len),len);
CStringW strOut3(strOut2);
 
_ASSERT(strOut1.Compare(strOut3)==0); // Das sollte eigentlich gleich sein

PS: Getestet habe ich das auf einem Windows 7 64bit und 32bit OS.

Siehe folgende Links zu den Begriffen OEM/ANSI:
http://blogs.msdn.com/b/oldnewthing/archive/2005/10/27/485595.aspx
http://blogs.msdn.com/b/oldnewthing/archive/2005/03/08/389527.aspx

C++ProgrammierenWindows APIMartin Richter - Sa 25 Jun 2011 18:46

Wir haben ein Stück Code, dass verhindern soll, dass ein Programm zweimal gestartet werden kann.
Dieser basiert auf einem Mutex und einer Memory Mapped File, mit der man sich auch das Fenster-Handle einer bereits gestarteten Instanz besorgen kann.

Nun gelang es einem unserer Händler aber dennoch dieses Programm zweimal in einer Session zu starten und zwar auf folgendem Weg:

  1. Er startet die Software mit dem normalen Link auf dem Desktop, der durch das Installationsprogramm angelegt wurde.
  2. Er öffnen eine Console mit CMD.EXE und wechselt in das Verzeichnis, gibt den Programmnamen ein und das Programm startet erneut. :eek:

Die Ursache ist war wie folgt:

  1. Der Mutex den wir intern verwendet haben nutzte den Dateinamen der EXE. Der Name des Mutex wird unter Anderem auch durch GetModuleFileName ermittelt.
  2. Der Dateiname der EXE, wenn sie als Verknüpfung gestartet wird ist “XYZ.exe” (so wie die Datei auch auf der Festplatte heißt) und das liefert auch GetModuleFileName als Ergebnis.
  3. Der Dateiname, den GetModuleFileName liefert, wenn man das Programm aus CMD.EXE startest ist exakt so wie man es eintippt, also z.B. “xyz.exe”. Erstaunlich.
  4. Da der Mutex einen Namen case sensitiv behandelt (was ich nicht vermutet hätte und erst mit staunenden Augen nachgelesen habe), wurde das bereits gestartete Programm nicht erkannt und eine zweite Instanz gestartet.

Was schreiben wir uns also hinter die Löffel für die Zukunft:
a) GetModuleFileName liefert nicht den “exakten” Dateinamen (obwohl ich es anders erwartet hätte)!
b) Mutexe sind case sensitiv wie auch Events (obwohl ich hier eine Behandlung wie bei einem Dateinamen erwartet habe)!
c) Manche Erwartungen trügen… ;)

C++MFCProgrammierenWindows APIMartin Richter - Sa 04 Dez 2010 17:46

Wenn man eine Windowsnachricht bearbeitet dann ruft man in den meisten Fällen die Funktion der Elternklasse auf. Was aber, wenn man die Nachricht nicht nur entgegennehmen will, sondern sie “verändert” an die Basisfunktion weitergeben will. Was dann?
Kein Problem denkt man. Man ändert die Parameter eben und gibt diese neuen Werte an die Basisklasse weiter.

Nehmen wir mal als Beispiel ein Eingabe Control, das sich möglichst intelligent verhalten soll. D.h. in diesem Fall, wenn ein Datum eingegeben wird, dann sollte auch bei der Eingabe auf numerischen Ziffernbock, das Komma in einen Punkt gewandelt werden, oder bei englischem Datumsformat in einen Slash…

Na OK. Man überschreibt als CMyWnd::OnChar. Man prüft ob ein bestimmtes Zeichen ankommt und wenn es mir passt, dann ändere ich den Parameter und gebe diesen dann an die Basis Funktion weiter.

:Eeek: aber was ist das? Der Code hat keine Wirkung? Egal was wir an die Basisfunktion übergeben, es ändert sich nichts. Warum?

Die Antwort liegt in dem für mich etwas eigentümlichen Design der MFC. Alle CWnd Nachrichten Routinen rufen letzten Endes immer genau ein und die selbe Funktion auf: CWnd::Default(); Aber was macht CWnd::Default? Es nimmt die ursprünglichen Parameter, die Windows mal gesendet hat und übergibt die an die Window-Proc des Fensters. D.h. alle tollen Manipulationen an der WM_CHAR Nachricht sind weg in dem Moment in dem CWnd::OnChar aufgerufen wird.

Was also tun, wenn man nun aber wirklich die WM_CHAR Nachricht manipulieren will?
Eigentlich nicht schwer. Man macht das Gleiche, dass eben auch CWnd::Default macht. Man ruft DefWindowProc mit den passenden wParam und lParam Werten auf.

Aber jetzt wird es dumm :!: Damit umgehen wir alle anderen geerbten Funktionalitäten des Controls. Ich habe keine Ahnung was die Entwickler der MFC in der Anfangszeit dazu getrieben hat immer CWnd::Default aufzurufen? War es Ihnen zu kompliziert wieder aus den Parametern wParam und lParam zusammen zu bauen?

CommunityProgrammierenWindows APIMartin Richter - Fr 26 Nov 2010 22:22

Vor Jahren habe ich für die microsoft.public.de.vc FAQ den folgenden Beitrag geschrieben:
Warum klappt meine ComboBox im DropDown-Stil in einem Dialog nicht auf?

Beim Erstellen einer ComboBox in einem Dialog Template muss auch die Größe mit angegeben werden, die die ComboBox haben soll, wenn Sie denn aufgeklappt wird. Dies kann auf zwei Methoden geschehen.

Methode 1: ComboBox aus der Werkzeugleiste einfach durch einen Mausklick einsetzen. Anschließend auf den “DropDown”-Schalter klicken und nun die gewünschte Größe einstellen.

Methode 2: ComboBox durch Ziehen eines Rechteckes auf dem Dialog einsetzen. In diesem Fall wird die Größe gleich korrekt bestimmt. Nachträgliche Änderung der Größe erfolgt dann wieder durch anklicken des “DropDown”-Schalters.

Anmerkung: Die Größe einer ComboBox mit dem Stil CBS_DROPDOWN und CBS_DROPDOWNLIST im NICHT aufgeklappten Zustand kann beim Erzeugen nicht verändert werden. Diese Größe bestimmt Windows automatisch. Die Größe die bei CreateWindow/CreateWindowEx angegeben wird ist immer die Größe des Control im aufgeklappten Zustand. Nachdem ein gültiger Windowshandle auf die ComboBox existiert kann mit CComboBox::SetItemHeight die Höhe der Items bzw. des Editfelds der ComboBox verändert werden.

Jetzt habe ich entdeckt, dass dieser Beitrag eigentlich überflüssig geworden ist, seit dem es COMCTL32 in der Version 6.0 gibt.
Wenn ein Manifest für die 6.0 Version der Common Controls vorhanden ist, dann bestimmt die COMCTL32 DLL automatisch selbst anhand der Höhe des Monitors und der Position der ComboBox wie groß der DropDown-Bereich sein kann.

Aufgefallen ist mir das, als ich in einer RC-Datei sah, dass eine ComboBox mit der Höhe gerade einmal 20 DLUs angegeben wurde. Da in der RC Datei normalerweise immer nur die DropDown-Höhe eingetragen ist, fragte ich mich warum bisher niemandem aufgefallen war, dass diese ComboBox, nicht aufklappt. Ein kurzer Test, zeigte allerdings, dass alles normal war und die Box, den halben Monitor in der Höhe einnahm.
Ein weiter Test mit und ohne Manifest zeigte mir dann schnell, dass sich das Standardverhalten von Comboboxen offensichtlich verändert hat.

Nachtrag (07.01.2011):
Nur die neuen Common Controls ab Vista und Windows 7 verhalten sich wie oben beschrieben. Windows XP (einschließlich SP3) verhält sich noch gemäß der MSDN WinAPI Doku. D.h. die Höhe wird nicht automatisch angepasst. Man kann sich eben auf nichts verlassen.

C++CommunityProgrammierenSoftwareVistaWindowsWindows 7Martin Richter - Di 19 Okt 2010 13:53

Seit Windows Vista ärgere ich mich über die Anzeige der Datei-Eeigenschaften.
Bei ausführbaren Dateien gibt es nicht mehr den schönen Versionsinfo Dialog aus XP, sondern eine Detailseite, die nicht auf den ersten Blick alle Infos liefert die man als Entwickler möchte. Zudem unterschlägt die Seite noch einiges Wissenswerte.

Jetzt hat “Fish” David B. Trout auf Codeproject eine Extension bereit gestellt, die die alte Versionsinfo wieder in den Explorer integriert.
Das Addin ist zwar nicht lokalisiert und zeigt die internen VERSIONINFO Tags an, aber das ist eigentlich kein Problem.

http://www.codeproject.com/KB/shell/VersInfoEx2.aspx

My vote: 5!

ProgrammierenSoftwareVistaVista / Windows 7WindowsWindows 7Windows APIMartin Richter - Do 16 Sep 2010 20:03

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.

C++DebuggingProgrammierenVista / Windows 7Windows APIMartin Richter - Mi 23 Jun 2010 20:08

Manchmal muss man Software verstehen. D.h. auch andere Software, die man selbst nicht geschrieben hat ;)

In meinem Fall war es hier ein Client, den ich geschrieben habe, der eine andere Software startet. Diese Software verwendete interne Nachrichten zur Kommunikation, die mit RegisterWindowMessage registriert wurden. Ich wollte nun hier einen Eingriff machen, der ein Fehlverhalten unter Windows 7 und Vista vermeiden soll.

Hilfreich wäre für mich nun gewesen an den Namen der registrierten Nachrichten zu kommen. Spy++ kann es auch und der importiert auch keine mystischen Funktionen. Also muss es einfach gehen.

Und ein wenig Recherche und ein Verweis eines Community Eintrags brachte mich auf diesen Thread:
http://groups.google.it/group/microsoft.public.vc.mfc/browse_thread/thread/f83f7c12c80e4ada/460bc4c43a844a37

Siehe da GetClipboardFormatName löst das Problem. Der nachfolgende Code lieferte mir nun im Detail, was das so hin und her läuft und der Name der Nachrichten war zum Glück sprechend. Ich konnte das Problem lösen.

if (uiMsg>=0xC000)
{
  TCHAR szName[MAX_PATH];
  ::GetClipboardFormatName(uiMsg,szName,MfxCountOf(szName));
  TCHAR szOut[MAX_PATH*2];
  _stprintf(szOut,_T(__FUNCTION__) _T(" %s, wp=0x%08x, lp=0x%08x\n"),
            szName, wParam, lParam);
  OutputDebugString(szOut);
}
C++ProgrammierenWindows APIMartin Richter - Di 20 Apr 2010 20:10

Jeder der mit Fontgrößen und Darstellungsgrößen herumspielt, oder wer selber in Fenstern zeichnet kennt LOGPIXELSX und LOGPIXELSY, die durch GetDevCaps geliefert werden. Diese Werte dienen auch CFont::CreatePointFont und anderen Funktionen bei der Umrechnung von “realen” Maßen auf die Devicepoints, die man dann benötigt. Alles kein Hexenwerk und überall im Netz beschrieben.
Auf diesem Weg kann man mit etwas MulDiv Arithmetik schnell umrechnen wie viele Punkte man benötigt um etwas von 10mm Größe auf einem Device darzustellen.

Der nachfolgende Code wandelt Einheiten von 1mm entsprechend der Auflösung eines Devices in Pixel um.

pDC->SetMapMode(MM_TEXT);
// Convert mm to with LOGPIXELSX
CSize sizeLogPixel(pDC->GetDeviceCaps(LOGPIXELSX),
            pDC->GetDeviceCaps(LOGPIXELSY));
rect.top = ::MulDiv(rect.top,sizeLogPixel.cy*10,254);
rect.bottom = ::MulDiv(rect.bottom,sizeLogPixel.cy*10,254);
rect.left = ::MulDiv(rect.left,sizeLogPixel.cx*10,254);
rect.right = ::MulDiv(rect.right,sizeLogPixel.cx*10,254);

Die Auflösung von 0,1mm je Einheit ist die Metrik des Mappingmodes MM_LOMETRIC. Man sollte also meinen, dass die Verwendung von MM_LOMETRIC mit einem Faktor 10, der obigen Umrechnung gleich kommt.

pDC->SetMapMode(MM_LOMETRIC);
// Convert mm to 0.1mm
rect.top *= -10;
rect.bottom *= -10;
rect.left *= 10;
rect.right *= 10;

Probiert man dies aus, so stellt man überrascht fest, dass die Größen nicht übereinstimmen.
Der Dokumentation nach müsste man es aber denken.

Mit einem bisschen experimentieren bin ich letztlich auf den Grund gekommen.
Keiner der Mappingmodes MM_LOENGLISH, MM_HIENGLICH, MM_LOMETRIC oder MM_HIMETRIC verwendet LOGPIXELSX oder LOGPIXELSY. Diese Mappingmodes verwenden die Werte HORZRES und VERTRES in dem Viewport-Extent und die Werte HORZSIZE und VERTSIZE im Window-Extent.

D.h. der Viewport-Extent bekommt die Größe des Bildschirmes in Pixeln zugewiesen und das Window-Extent bekommt die Größe des Devices in mm zugewiesen. Nun ist diese Größe (HORZSIZE/VERTSIZE) bei Bildschirmen nicht die reale Größe sondern eine Größe, die der Hersteller festlegt. (Anmerkung: Bei Druckern stimmt dieser Wert)

Nun wäre noch alles OK, wenn sich aus den Werten von VERT/HORZSIZE und VERT/HORZRES nun der Quotient LOGPIXELSX/SY ermitteln ließe. Das ist aber nicht der Fall! LOGPIXELSX/SY sind Skalierungswerte die bei Bildschirmen unabhängig von der realen Auflösung angegeben werden und die z.B. dazu Dienen Schriftgrößen grundsätzlich größer oder kleiner anzeigen zu lassen (siehe auch High DPI Mode).

Die Konsequenz daraus ist, dass die Mappingmodes ein relativ exotisches Einzelleben führen, weil die meisten Entwickler eben korrekterweise auf LOGPIXELSX/SY zurückgreifen. Noch mal sei hier bemerkt, dass für Drucker DCs hier in mir bekannten Fällen kein Unterschied existiert und auch das ist gut so.

Die Lösung die sich anbietet, ist nicht weiter schwierig und sie auch der Grund warum ich erst jetzt auf diesen gesamten Umstand gestoßen bin. Ich habe niemals die MM_LO…/MM_HI… Mappingmodes verwendet. Entweder pur MM_TEXT und wenn ich was anderes benötigt habe einfach MM_ANISOTROPIC, und in der entsprechenden Skalierung habe ich dann meistens die Werte aus LOGPIXELSX/SY verwendet. Also musste es passen.

MM_ANISOTROPIC ist sowieso der Mappingmode der Wahl, wenn es um skalierbare Darstellungen und Zoomfaktoren geht, aber dazu vielleicht mehr in einem Artikel demnächst.

Ich habe ein kleines MFC-Programm gebaut (MappingModeTest), dass diese Konflikte aufzeigt. Ich zeichne dort ein Rechteck auf den Koordinaten 20mm,10mm mit der Größe 60mm,40mm. Damit die verschiedenen Rechtecke alle sichtbar werden verwende ich immer einen Versatz von 1mm und zeichne die Rechtecke in unterschiedlichen Farben.
In der Debugausgabe kann man wunderschön sehen wie Extents mit den Werten aus GetDeviceCaps zusammenhängen.

Dieser Artikel basiert auf zwei Anfragen in microsoft.public.de.vc die dieses unterschiedliche Verhalten aufzeigten und diskutieren:
http://groups.google.de/group/microsoft.public.de.vc/browse_thread/thread/23467a5e95051291/7c66c01b8295eaab
http://groups.google.de/group/microsoft.public.de.vc/browse_thread/thread/201719d23a411256/17b41c09844bf105

Nächste Seite »