C++CRTProgrammierenVS 2015Windows APIMartin Richter - Fr 18 Sep 2015 16:15

Eine „Spaßbremse“ Software mit Visual Studio 2015 auszuliefern war bisher in jedem Fall das Universal CRT.

Im Speedproject.de Blog ist davon auch einiges zu lesen gewesen:
Noch kein Umstieg auf VS-2015
Anwendungslokaler Einsatz der Universal CRT
Visual Studio 2015 und die Universal CRT

Dieses Paket musste man zusätzlich mit installieren auf allen Systemen die Windows 7, Vista oder Windows 8.x verwendet haben. Die Probleme und das Nachfragen von Entwicklern hat nun Wirkung gezeigt.
Microsoft veröffentlicht den KB2999226 über Windows Update. Damit entfällt das Ausrollen mit dem eigenen Setup.

angeboten bzw. installiert wurde.

Danke für den Hinweis an Michael Külshammer. (siehe auch dotnetpro)

Nachtrag:
Bei mir erscheint jetzt auf meinen Windows 7 Rechner das entsprechende Update als optionales Update
KB2999226

C++CRTProgrammierenMartin Richter - Do 20 Jun 2013 21:15

Mathe ist ja eigentlich ganz einfach. Die nachfolgende Routine berechnet die kürzeste Distanz zwischen zwei Punkten auf der Erde unter der Berücksichtigung, dass die Erde eine perfekte Kugel ist.
Benutzt wird diese Routine um die Luftlinie zwischen zwei geokodierten Adressen zu ermitteln.

double DistanceBetweenCoordinates(
          double dLatitude1, double dLongitude1, 
          double dLatitude2, double dLongitude2)
{
  // Convert to radient
  dLatitude1 *= M_PI / 180;
  dLongitude1 *= M_PI / 180;
  dLatitude2 *= M_PI / 180;
  dLongitude2 *= M_PI / 180;

  // Get distance
  double dDistance;
  dDistance = sin(dLatitude1) * sin(dLatitude2) + cos(dLatitude1) * 
        cos(dLatitude2) * cos(dLongitude2-dLongitude1);
  // Ohne diese Zeile haben wir ein Problem... dDistance ist evtl. >1
  // dDistance = min(dDistance,1.0);
  dDistance = acos(dDistance) * EARTH_RADIUS;
  return dDistance;
}

Eigentlich ganz einfach. Aber dieser Code hat ein massives Problem, denn dDistance kann vor dem acos durch Rundungsfehler größer als 1 werden.

Für die folgenden Werte passiert etwas Übles:

dLatitude1 0.86208095750908087 
dLongitude1 0.15333764167657613 
dLatitude2 0.86208095750908087 
dLongitude2 0.15333764167657613

Eigentlich müsste glatt 1 herauskommen, denn die Koordinaten sind gleich, aber dDistance wird bei diesen Werten 1.0000000000000002. Der Sinus bzw. Cosinus ergibt eben doch leicht abweichende Werte. Der folgende acos scheitert dann aber und das Resultat wird NaN (Not a Number). Erlaubt sind natürlich nur Werte <=1.0 und >=-1.0. Die Folge ist, dass die Distanz nicht 0.0 ist.

Also muss hier eine kleine Sicherung eingebaut werden, die Aufgrund der Rundungs-Genauigkeitsfehler, das überschreiten von 1 verhindert.
Eigentlich ist Mathe ganz einfach, aber auf einem Rechner ist das alles manchmal ganz anders.

PS: Ich weiß schon warum ich noch nie Fließkommaarithmetik auf PCs mochte.

C++CRTProgrammierenVS 2010Martin Richter - Fr 12 Aug 2011 20:49

Eigentlich kann man es kein Sicherheitsupdate nennen. Ich sehe  eigentlich nur eine sicherheitsrelevante Änderung in:

  • atltransactionmanager.h (ATL)
    Hier wird das Laden der ktmw32.dll jetzt mit einer neuen Funktion AtlLoadSystemLibraryUsingFullPath durchgeführt, die in atlcore.h hinzugefügt wird. Diese Funktion lädt eine DLL nur aus dem Windows-System Verzeichnis. Damit wird Binary-Planting verhindert, aber dies betrifft eigentlich nur Windows Vista / 7 / 2008 Windows 2003 und Windows XP (Nachtrag am 18.08. siehe dazu Kommentar von Stefan Kuhr) und diejenigen die den Kernel Transaction Manager mit der ATL nutzen.

Bugfixes habe ich folgende gefunden:

  • afxtoolbarimages.cpp (MFC)
    In CPngImage::LoadFromBuffer wurde bei der Verwendung dieser Funktion in Speicherblock eines Streams nicht freigegeben (falsche Nutzung von CreateStreamOnHGlobal).
  • dbcore (MFC / ODBC)
    In CRecordset::BuildUpdateSQL in der ODBC Implementierung, wird bei Abfrage eines Cursornamens ein Puffer von 18 Zeichen Länge verwendet (MAX_CURSOR_NAME). Ist der Cursorname länger so wurde eine Exception geworfen. Jetzt wird erkannt, dass der Buffer zu klein ist und es wird ein dynamischer Buffer mit ausreichender Größe alloziert und der Name dann abgefragt.
  •  xutility (STL und auch CRT)
    In der Basisklasse der Iteratoren wurde eine Änderung gemacht. Scheinbar hat die Zuweisung eines „leeren/nicht initialisierten“ Iterators bisher einen Iterator gar nicht verändert. Der alte Iterator wurde nicht aufgelöst durch _Orphan_me. Dies bringt dann Probleme mit sich wenn der _ITERATOR_DEBUG_LEVEL mit 2 genutzt wird.
    Da dieser STL Code auch komplett in der CRT verwendet wird, hat dies auch Einfluß auf die CRT.
    Auf die Release Version aber hat diese Code-Änderung jedoch keinen Einfluss, soweit ich das erkennen kann.

Es gibt noch einige andere Dateien, die geändert wurde, aber hier haben sich nur unwichtige Kommentare geändert.

Fazit: Alles in allem ein Sicherheitspatch, der eher Bugfixes enthält, aber selbst die sind nicht sonderlich weitreichend. Und der Nutzerbereich, der mit der ATL den Kernel Transaction Manager nutzt, wird wohl eher klein sein…

PS: Die Dateien dieses Sicherheitspatches haben die Version 10.0.40219.325.
Die Dateien aus dem VS-2010 SP1 haben die Versionsnummer 10.0.30319.1.

Nachtrag 01.12.2011, MSDN Links:
Sicherheits Bullentin:   MS11-025
Knowledge Base-Artikel (KB-Artikel):  KB2565057

C++CRTMFCProgrammierenVS 2008VS 2010Martin Richter - Mo 27 Dez 2010 09:33

In VS-2005 hielt die Unicode und UTF-8 Unterstützung Einzug in die CRT und damit auch in die MFC. Jochen und ich haben dazu gebloggt.

Leider ist die Implementierung nicht fehlerfrei. Wenn man eine UTF-8 Datei mit ccs=UNICODE öffnet dann liefert ftell zum Teil auch negative Werte. Das ftell nicht die exakte Position in der Datei liefert ist dokumentiert, nur sollte es mit dem Wert zumindest möglich sein wieder fseek aufzurufen um an eine alte Dateiposition zurück zu gelangen.
Das ist mit der jetzigen Implementierung nicht möglich.

Ein Sample dazu ist schnell gebaut:

int _tmain()
{
  FILE *file = NULL;
  if (_tfopen_s(&file,_T("Test.txt"),_T("rt") _T(", ccs=UNICODE")))
    return -1;

  TCHAR szBuffer[_MAX_PATH];
  while (_fgetts(szBuffer,_countof(szBuffer),file))
  {
    int iPos = static_cast(ftell(file));
    _tprintf(_T("%d\n"),iPos);
    _ASSERTE(fseek(file,iPos,SEEK_SET)==0);
  }

  fclose(file);
  return 0;
}

Ein entsprechendes Sample kann man hier herunterladen.

Der Fehler betrifft natürlich auch die MFC mit CStdioFile und GetPosition!

Hintergrund:
Ich kam auf den Fehler, weil wir ein Programm benutzen, dass sich bestimmte Dateipositionen merkte um einen Datenimport bei einer bestimmten Bedingung, ab einer definierten Stelle, neu aufnehmen zu können. Bisher hatten wir hier nur pure ASCII Dateien erlaubt, jetzt wollten wir auch UTF-8 Dateien unterstützen.
Sicher man kann auch alles im Speicher zwischenhalten, aber wenn es eine simple Dateiposition ist das Ganze so weitaus Ressourcen schonender.

Soweit ich das übersehen kann, betrifft der Fehler alle VS-Versionen ab VS-2005. Ein entsprechende Bug wurde von mir schon vor längerer Zeit auf Connect geöffnet. Getan hat sich bisher noch nichts:
https://connect.microsoft.com/VisualStudio/feedback/details/591030/ftell-returns-negative-value-for-utf-8-files-opened-with-textmode-and-ccs

C++CRTDebuggingMFCProgrammierenVS 2008VS 2010VS-Tipps&TricksMartin Richter - So 30 Aug 2009 16:10

ASSERTs in der MFC und in der CRT sind tolle Hilfsmittel, aber nicht selten verfälschen sie auch das Problem alleine dadurch, dass ein Fenster aufpoppt, wenn der ASSERT zuschlägt. Hat man nun einen Code, der in einem Tooltipp etwas Böses macht, dann wird der Tooltipp selbst aber schon wieder durch das erscheinen der ASSERT Meldung zerstört. Oder es wird ein neuer ASSERT ausgelöst. Der Callstack wird dadurch oft schwer zu lesen.
Besonders heikel kann dies auch noch werden wenn man mehrere Threads hat. Gleichfalls problematisch ist, dass in dem Moment in dem die ASSERT Box auftaucht nun auch wieder alle Timer weiterlaufen und sehr eigentümliche Seiteneffekte weiter auslösen können, dito. Probleme in WM_PAINT Handlern, denn auch die lösen evtl. schon wieder Aktionen aus, die Variablen verändern.

Nett ist am ASSERT-Dialog natürlich die Möglichkeit Ignorieren zu sagen und das Programm weiter laufen zu lassen. Ganz besonders wenn man Debug Versionen im Testfeld mit Anwendern testet.

Dennoch bin ich bei Debug-Versionen dazu übergegangen ASSERTs direkt  crashen zu lassen, bzw. direkt einen Debug-Break auszulösen. Das erleichtert das Lesen des Crashdumps bzw. hilft auch beim Debuggen, weil man direkt an der Stelle steht wo es hakt und alle Fenster und Variableninhalte exakt noch so sind, wie Sie es beim Auftreten des Problems waren (Tooltips, Popups, Menüs etc.).

Der Code um das zu erreichen ist relativ simpel. Man verwendet dazu _CrtSetReportHook2. In dem Hook sagt man einfach was man gerne hätte. Nämlich bei einem ASSERT oder ERROR keinen Dialog sondern einen Break (INT3).

#ifdef _DEBUG
int __cdecl DebugReportHook(int nReportType, char* , int* pnRet)
{
  // Stop if no debugger is loaded and do not assert, cause a crash
  // - returning TRUE indicates that we handled the problem, so no other hook
  //   needs to perform any action
  // - setting the target of *pnRet to TRUE indicates that the CRT should
  //   execute an INT3 and should crash or break into the debugger.
  return *pnRet = nReportType==_CRT_ASSERT ||
                  nReportType==_CRT_ERROR ?
                            TRUE : FALSE;
}
#endif

void SetBreakOnAssert(BOOL bBreakOnAssert/* =FALSE */)
{  
// Need to disable the ASSERT handler?
#ifdef _DEBUG  
  if (bBreakOnAssert)   
    _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, DebugReportHook); 
  else   
    _CrtSetReportHook2(_CRT_RPTHOOK_REMOVE, DebugReportHook);
#else
  UNUSED_ALWAYS(bBreakOnAssert);
#endif
}

Durch diese kleine Funktion SetBreakOnAssert kann man dieses Verhalten nun einfach ein- und ausschalten. Nähere Details stehen im Kommentar der Hook-Funktion.

C++CRTDebuggingMFCProgrammierenMartin Richter - So 07 Jun 2009 12:08

Ich verwende gerne ASSERT’s in meinem Code.
Sie sind ein wirksames Mittel Zustände abzufragen und bereist in der Testphase unzulässige Konstellationen oder Funktionsaufrufe zu entdecken.

Nun gibt es ja auch if, else oder switch Blöcke an denen das Programm normalerweise nicht vorbei kommen sollte. So eine Stelle versieht man dann schon mal mit einem

_ASSERT( FALSE );
// oder wer die MFC benutzt eben ASSERT,
// obwohl dies auch nur ein Synonym für den CRT _ASSERT makro ist
ASSERT(FALSE);

Jetzt müsste man noch einen Kommentar davor setzen, damit klar wird was hier schief läuft. Man kann das Ganze aber auch einfach kombinieren und noch einen Zusatznutzen erreichen indem man den etwas unbekannteren Makro _ASSERTE verwendet:

_ASSERTE( !"MyFuncFooHandler: This is not allowed in my special Foo Handler" );

Die Negation macht aus dem Zeiger auf den konstanten String FALSE, und damit schlägt der ASSERT an.
Wenn man jetzt wie hier gezeigt noch den _ASSERTE Makro verwendet, dann wird diese Expression, also der Text, sofort mit anzeigt. Man sieht dann sofort was das Problem ist sobald der ASSERT Dialog angezeigt wird.

C++CRTProgrammierenSonstigesWindows APIMartin Richter - So 09 Nov 2008 19:42

Mit WinMain / wWinMain / _tWinMain bekommt man einen Zeiger auf Befehlszeile frei Haus mitgeliefert als lpCmdLine. Es ist nicht schwer Code u schreiben um diese Zeile zu interpretieren. Die MFC stellt dazu eine eigene Klasse mit dem Namen CCommandLineInfo. Manchmal wäre es aber auch einfach nur schön wie bei einem simplen C/C++-Konsolenprogramm argc und argv zur Verfügung zu haben, die an main / wmain / _tmain übergeben werden. Leider werden die aber an die Startfunktion nicht übergeben. (Diese Frage taucht auch nicht selten in den Communities auf).

Versteckt in der CRT gibt es argc und argv aber immer, egal ob Konsolen- oder UI-Programm. Der Startup-Code der CRT initialisiert diese Werte immer und sie stehen als globale Variablen unter den Namen __argc und __argv bzw. __argvw zur Verfügung. Man muss nur einen #include aud stdlib.h machen und schon stehen diese Werte zur Verfügung.
Ist das Programm ein Unicode Programm ist der Array __argvw initialisiert und __argv ist NULL. Ist das Programm ein MBCS Programm dann ist __argv gefüllt und __argvw ist NULL. Entsprechend ist __targv in tchar.h definiert. (Geändert nach Kommentar von Marcus Humann am 10.09.08)

Mit __argc / __argv / __argvw / __targv kann man nun ganz leicht die Befehlszeile frei verarbeiten, wie man es gewohnt ist, auch wenn man ein Windows-GUI Programm erstellt, oder die MFC benutzt.

Das ist zwar nicht dokumentiert, aber seit VC6 bis zur aktuellen Version VC-20008 hat sich nichts geändert.

ATLC++CRTDebuggingIDEMFCProgrammierenVS-Tipps&TricksWindows APIMartin Richter - Fr 31 Okt 2008 19:42

Einige Hilfsmittel um einen Heap-Fehler zu finden habe ich in meinem letzten Beitrag ja beschrieben.

Eigentlich wünscht sich der Entwickler nichts mehr, als dass ein falscher Zugriff auf den Heap, sofort einen Break im Debugger auslöst. Die Methoden, die ich bisher gezeigt habe (AfxCheckMemory, _CrtCheckMemory, _CrtSetDbgFlag) können das nicht direkt , aber zumindest helfen sie den Fehler einzukreisen.

Ein unverzichtbarer Helfer, der sofort solch einen Break auslösen kann, ist der Application Verifier, den ich bereits in einem älteren Artikel als Freund und Helfer vorgestellt habe.

Seit Visual Studio 2005 kann man direkt Parameter für den Application Verifier im Projekt einstellen und auch direkt den Debug-Prozess mit dem Application Verifier starten (Umschalt+Alt+F5).
An den Standardeinstellungen im Projekt braucht man hier gar nichts zu ändern:
Conserve Memory – No
Protection Location – Je nach Testfall (man sollte mit beiden Einstellungen mal debuggen)
Alle anderen Einstellungen Verification Layers Settings – auf Enable

Mit dem Application Verifier lässt sich der so genannte Paged Heap nutzen, der Guard Pages anlegt hinter oder vor den allokierten Speicherbereichen (siehe auch GFLAGS.EXE). Der Vorteil: Man erhält sofort eine Access Violation, wenn man den Speicherbereich überschreitet.

Mein kleines Demoproramm

#include <windows.h>
#include <tchar.h>
#include <crtdbg.h>
int _tmain(int argc, _TCHAR* argv[])
{
  char *pCorrupt = new char[100];
  ZeroMemory(pCorrupt,106); // -- This will corrupt the heap
  char *pOther = new char[100];
  ZeroMemory(pOther,100);
  delete [] pOther;
  delete [] pCorrupt;
  return 0;
}

crashed mit der Nutzung des Application Verifiers sofort und man kann im Call Stack die Zeile 7 ausmachen.
Genial ist besonders, dass der Application Verifier auch mit der Release Version sofort die Zeile 7 als Ursache identifiziert. Gerade wenn man also nicht auf die Debug-CRT zurückgreifen kann, ist der Application Verifier ein super Hilfsmittel.

Der Nachteil: Die Guard Pages liegen nicht exakt und direkt hinter dem allokierten Bereich, sondern auf der nächsten Page Boundary. Deshalb crashed mein Sample auch nicht wenn man den Speicher um nur 1 Byte überschreitet.

Aber der Application Verifier ist zum Testen ein absolutes Muss, weil auch falsche Handles erkannt werden und auch der Lock Verfification Layer für die Qualitätssicherung einfach nützlich zum entwanzen sind. (siehe auch Application Verifier Einstellungen in der MSDN).

Hinweis ❗

Auf Windows XP und Windows Server 2003 erhält man ohne administrative Rechte die folgende Fehlermeldung:

Access denied. You need administrative credentials to use Application Verifier on image <App_Name.exe> on machine <Machine_Name>. Contact your system administrator for assistance

Unter Windows Vista oder Windows Server 2008 erhält man die flogende Fehlermeldung wenn der Application Verifier nicht elevated gestartet wird:

Access denied. You need administrative credentials to use Application Verifier on image <App_Name.exe> on machine <Machine_Name> or per user verifier settings should be enabled by the administrator. Please refer to documentation for more information.

Durch einen simplen Eintrag in der Registry lässt sich aber auch als normaler Benutzer, ohne administrative Rechte, der Application Verifier nutzen, man erzeugt einen DWORD Eintrag in der Registry mit dem Wert 1
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manger\ImageExecutionOptions
Nach einem Reboot kann man nun einfach den Application Verifier auch non-elevated, als normaler Benutzer nutzen.

AllgemeinATLC++CRTDebuggingMFCProgrammierenVS-Tipps&TricksMartin Richter - Mo 27 Okt 2008 22:47

Probleme finden, die mit dem Heap zusammenhängen ist oft genug eine Sache für sich und für Anfänger nicht selten ein Buch mit sieben Siegeln. Die CRT und der Debugger stellen aber einige Werkzeuge zur Verfügung, die es einem doch mit etwas Geschick und Wissen erlauben auch komplexere versteckte Fehler zu finden, die Heapfehler auslösen.

Jeder C/C++ Entwickler hat schon Meldungen dieser Art beim Testen seiner Programme gesehen:

HEAP[CrashTest.exe]: Heap block at 006D7920 modified at 006D79B0 past requested size of 88
Windows has triggered a breakpoint in CrashTest.exe.
This may be due to a corruption of the heap, which indicates a bug in CrashTest.exe or any of the DLLs it has loaded. 

oder etwas in dieser Art:

Debug Error!
Program: …nts\Visual Studio 2008\Projects\CrashTest\Debug\CrashTest.exe
HEAP CORRUPTION DETECTED: after Normal block (#110) at 0x000D7948.
CRT detected that the application wrote to memory after end of heap buffer.

Ursache ist ein Fehler wie in diesem kleinen Beispielprogramm:

#include <windows.h>
#include <tchar.h>
#include <crtdbg.h>
int _tmain(int argc, _TCHAR* argv[]) 
{ 
  char *pCorrupt = new char[100];
  ZeroMemory(pCorrupt,106); // -- This will corrupt the heap 
  char *pOther = new char[100]; 
  ZeroMemory(pOther,100); 
  delete [] pOther; 
  delete [] pCorrupt; 
  return 0; 
}

Wenn die Debug-CRT benutzt wird erhält man automatisch einen Break im Debugger wenn der Speicherblock pCorrupt freigeben wird (Zeile 11). Man braucht also nur den Call-Stack oder Stacktrace anzusehen und kann zumindest feststellen, welcher Block defekt ist.

Schwieriger wird es dann schon die Stelle zu finden, an der der Block überschrieben wird. In meinem Beispiel also die Zeile 7. Besonders dann wird es komplex, wenn das Programm größer ist, und der Speicherblock an evtl. sehr vielen Stellen genutzt wird.

Um die Position einzugrenzen und nicht evtl. bis zum Programmende warten zu müssen, wenn man (hoffentlich) alle Objekte freigibt kann man die CRT veranlassen den Heap zu prüfen. Dies geschieht mit _CrtCheckMemory oder AfxCheckMemory.
Streut man also in seinem Code an strategisch guten Stellen das folgende Statement in seinen Code

ASSERT(AfxCheckMemory()); // oder _CrtCheckMemory

kann man relativ gut die Stelle einkreisen die den Fehler verursacht, und das ohne große Performanceverluste. Man erhält sofort einen ASSERT, ab dem Moment ab dem die Integrität des Heaps zerstört wurde und der Check durchgeführt wird.

Noch etwas einfacher ist es, die CRT dazu zu bringen sich sofort zu melden wenn der Heap zerstört wird. Dies kann man erreichen indem man das Debug-Flag _CRTDBG_CHECK_ALWAYS_DF setzt.

Platziert man bei Programmstart die folgende Codezeile in seinem Programm

_CrtSetDbgFlag(_CrtSetDbgFlag(0)|_CRTDBG_CHECK_ALWAYS_DF);

dann unterbricht die CRT das Programm sofort bei der nächsten Allokation eines Speicherbocks, nachdem der Heap zerstört wurde. In meinem Beispiel also direkt bei der nächsten Allokation in Zeile 8!
Nachteil ist, dass bei jeder Allokation der Heap geprüft wird und damit die Performance schon in den Keller gehen kann, wenn das Programm groß ist und der Fehler evtl. selten auftritt.

Man kann also mit den einfachen Bordmitteln der CRT einen Fehler schon relativ leicht eingrenzen.

Soweit für heute. Was man noch alles machen kann um effektiv Heap-Fehler zu finden werde ich demnächst noch in weiteren Artikeln zu diesem Thema beschreiben.

CRTProgrammierenSonstigesVS 2008Windows APIMartin Richter - Do 04 Sep 2008 21:02

Immer wieder kommt die Frage auf, warum sich ein Konsolenfenster kurz öffnet wenn man eine Windows Anwendung, wie z.B. Notepad mit den CRT Funktion system startet.

Die Antwort ist ganz einfach, dazu muss man einfach mal nicht einmal unbedingt einen Blick in die Sourcen werfen, denn es ist sogar richtig für system dokumentiert in der MSDN.

The system function passes command to the command interpreter, which executes the string as an operating-system command. system refers to the COMSPEC and PATH environment variables that locate the command-interpreter file (the file named CMD.EXE in Windows 2000 and later). If command is NULL, the function simply checks to see whether the command interpreter exists.

Also kurz und bündig: Es wird zwangsläufig immer ein Konsolenfenster geöffnet. Das verschwindet zwar sofort wieder, wenn man eine GUI Applikation startet, aber das kann man vermeiden indem man gleich ShellExecute verwendet.

Ganz anders sieht es mit der Doku bei _popen aus. _popen scheint sich anzubieten, um die Ausgaben eines Tools in eine Datei umzuleiten. Aber auch _popen nutzt wie system CMD.EXE /c (COMSPEC). Um das heraus zu bekommen muss man allerdings den Source Code der CRT zu Rate ziehen. Das bedeutet, dass auch bei Verwendung von _popen ein Flackern durch ein Konsolenfenster nicht ausbleibt.

Wie man es richtig macht, wenn man die stdin/stdout umleiten will findet man in den wohlbekannten Artikeln der MSDN http://support.microsoft.com/kb/190351 und http://msdn.microsoft.com/en-us/library/ms682499.aspx. Durch die Verwendung von CreateProcess lässt sich auch ganz vermeiden, dass der zweite Prozess angezeigt wird.

Nächste Seite »