Windows SDK FAQ

Windows SDK GDI: How do I choose a font size to exactly fit a string in a<

Q: How do I choose a font size to exactly fit a string in a given rectangle?
A: Choosing font width and height by calculating the rectangle width and height will not solve this problem perfectly. This will only give a font which approximately fit in the rectangle, since characters are of different widths.

In this method, calculating the font size is done in two steps. First, rough font width and height are calculated from rectangle dimensions. This font is then selected for the display device context and exact width and height is determined. From this actual font dimensions, required font dimensions are calculated by proportionately sizing the font.

When you output multiple lines, some space is left between characters in subsequent lines ('tmExternalLeading' of 'TEXTMETRIC' structure). While evaluating the font size, this is also taken into account.

The method is wrapped into a function 'TextOut()'. This is similar to the SDK function 'TextOut', but differs in the type of arguments. First argument is handle to device context, similar to the SDK function. Second and third arguments are pointer to the rectangle and character array respectively. Here is the code, with comments sprinkled liberally:

void TextOut(HDC hdc, LPRECT lprect, LPSTR lpstr)
{
// Gets current font
HFONT hFont = (HFONT) GetCurrentObject(hdc, OBJ_FONT);
// Gets LOGFONT structure corresponding to current font
LOGFONT LogFont;
if
(GetObject(hFont, sizeof(LOGFONT), &LogFont) == 0)
return;
// Calculates span of the string sets rough font width and height
int Len = strlen(lpstr);
int
Width = lprect->right - lprect->left;
LogFont.lfWidth = -MulDiv(Width / Len, GetDeviceCaps(hdc, LOGPIXELSX), 72);
int
Height = lprect->bottom - lprect->top;
LogFont.lfHeight = -MulDiv(Height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
// Creates and sets font to device context
hFont = CreateFontIndirect(&LogFont);
HFONT hOldFont = (HFONT) SelectObject(hdc, hFont);
// Gets the string span and text metrics with current font
SIZE Size;
GetTextExtentExPoint(hdc, lpstr, strlen(lpstr), Width, NULL, NULL, &Size);
TEXTMETRIC TextMetric;
GetTextMetrics(hdc, &TextMetric);
int
RowSpace = TextMetric.tmExternalLeading;
// Deselects and deletes rough font
SelectObject(hdc, hOldFont);
DeleteObject(hFont);
// Updates font width and height with new information of string span
LogFont.lfWidth = MulDiv(LogFont.lfWidth, Width, Size.cx);
LogFont.lfHeight = MulDiv(LogFont.lfHeight, Height, Size.cy - RowSpace);
// Creates and selects font of actual span filling the rectangle
hFont = CreateFontIndirect(&LogFont);
SelectObject(hdc, hFont);
// Performs displaying text
TextOut(hdc, lprect->left, lprect->top - RowSpace, lpstr, strlen(lpstr));
// Select the original font and deletes the new font
SelectObject(hdc, hOldFont);
DeleteObject(hFont);
}

Result is shown in the attached image. Rectangle is shown in gray shade over which the string is output in transparent mode.

Note: Most of the material in this article is taken from codeguru.com


Recommended Reading :

Windows SDK String: How to convert between ANSI and UNICODE strings?

Q: How to convert between ANSI and UNICODE strings?
A:
The quick and dirty way

This way of working is correct for codepages that are single-byte and Unicode strings that are UCS2. This applies to most cases, but if your program should run correctly on Japanese, Chinese, Taiwanese and other systems which have DBCS codepages then use the "correct way" described further below.

ANSI to UNICODE:

The conversion is done using the 'MultiByteToWideChar()'

function: char *ansistr = "Hello";
int
a = lstrlenA(ansistr);
BSTR unicodestr = SysAllocStringLen(NULL, a);
::MultiByteToWideChar(CP_ACP, 0, ansistr, a, unicodestr, a);
//... when done, free the BSTR
::SysFreeString(unicodestr);

UNICODE to ANSI:

The UNICODE string mostly is returned by some COM function, like this one:

HRESULT SomeCOMFunction(BSTR *bstr)
{
*bstr = ::SysAllocString(L"Hello");
return
S_OK;
}

The conversion is done using the 'WideCharToMultiByte()' function:

BSTR unicodestr = 0;
SomeCOMFunction(&unicodestr);
int
a = SysStringLen(unicodestr)+1;
char
*ansistr = new char[a];
::WideCharToMultiByte(CP_ACP,
0,
unicodestr,
-
1,
ansistr,
a,
NULL,
NULL);
//...use the strings, then free their memory:
delete[] ansistr;
::SysFreeString(unicodestr);

The correct way

If you want to handle DBCS codepages and UTF-16 Unicode strings then you should do things this way. The idea is to call 'MultiByteToWideChar()' resp. 'WideCharToMultiByte()' twice. First you get the length of the result, then you allocate the resulting string and call it again to convert.

ANSI to Unicode

char *ansistr = "Hello"
int lenA = lstrlenA(ansistr);
int
lenW;
BSTR unicodestr;
lenW = ::MultiByteToWideChar(CP_ACP, 0, ansistr, lenA, 0, 0);
if
(lenW > 0)
{
// Check whether conversion was successful
unicodestr = ::SysAllocStringLen(0, lenW);
::MultiByteToWideChar(CP_ACP, 0, ansistr, lenA, unicodestr, lenW);
}
else
{
// handle the error
}
// when done, free the BSTR
::SysFreeString(unicodestr);


Unicode to ANSI

BSTR unicodestr = 0;
char
*ansistr;
SomeCOMFunction(&unicodestr);
int
lenW = ::SysStringLen(unicodestr);
int
lenA = ::WideCharToMultiByte(CP_ACP, 0, unicodestr, lenW, 0, 0, NULL, NULL);
if
(lenA > 0)
{
ansistr
= new char[lenA + 1]; // allocate a final null terminator as well
::WideCharToMultiByte(CP_ACP, 0, unicodestr, lenW, ansistr, lenA, NULL, NULL);
ansistr[lenA] = 0; // Set the null terminator yourself
}
else
{
// handle the error
}
//...use the strings, then free their memory:
delete[] ansistr;
::SysFreeString(unicodestr);

Note: Most of the material in this article is taken from codeguru.com


Recommended Reading :

Windows SDK Registry: How can I write data to the registry?

Q: How can I write data to the registry?

A: To open the registry key use the function RegOpenKeyEx. To write a vaule from registry use the function RegSetValueEx.

HKEY hKey;
if
(::RegOpenKeyEx(HKEY_LOCAL_MACHINE,
"System\\CurrentControlSet\\Control\\Windows",
0,
KEY_SET_VALUE,
&hKey)
== ERROR_SUCCESS)
{
DWORD dwValue
= 245;
// Set value
if(::RegSetValueEx(hKey,
"NameOfValue",
0,
REG_DWORD,
reinterpret_cast<BYTE *>(&dwValue),
sizeof(dwValue)) != ERROR_SUCCESS)
{
// Close key
::RegCloseKey(hKey);
// Error handling;
}
// Close key
::RegCloseKey(hKey);
}
else
// Error handling;

Note: Most of the material in this article is taken from codeguru.com


Recommended Reading :

Windows SDK Registry: How can I read in data from the registry?

Q: How can I read data from the registry?

A: To open the registry key use the function RegOpenKeyEx. To read a vaule from registry use the function RegQueryValueEx.

HKEY hKey;
DWORD dwSize = 0;
DWORD dwDataType = 0;
DWORD dwValue = 0;
if
(::RegOpenKeyEx(HKEY_LOCAL_MACHINE,
"System\\CurrentControlSet\\Control\\Windows",
0,
KEY_QUERY_VALUE,
&hKey)
== ERROR_SUCCESS)
{
// Get CSD version
dwSize = sizeof(dwValue);
if
(::RegQueryValueEx(hKey,
"CSDVersion",
0,
&dwDataType,
reinterpret_cast<BYTE *>(&dwValue),
&dwSize) !
= ERROR_SUCCESS)
{
// Close key
::RegCloseKey(hKey);
// Error handling
}
else
{
// Work with value
// Close key
::RegCloseKey(hKey);
}
}
else
// Error handling;

Q: The RegQueryValueEx function returns an error code 234 (ERROR_MORE_DATA) that means "more data is available". How many bytes must be allocated for value buffer to assure this error does not appear anymore?

A: To find the necessary buffer size, call 'RegQueryValueEx()' with a 'NULL' value in 'lpData' parameter. After return, the variable pointed by 'lpcbData' will contain the size of the data. Allocate the buffer 'lpData' then call 'RegQueryValueEx()' again:

HKEY hKey = NULL;
DWORD dwSize = 0;
DWORD dwDataType = 0;
LPBYTE lpValue = NULL;
LPCTSTR const lpValueName = _T("Directory");
LONG lRet = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE,
_T(
"System\\CurrentControlSet\\Control\\Windows"),
0,
KEY_QUERY_VALUE,
&hKey)
;
if
(ERROR_SUCCESS != lRet)
{
// Error handling
return;
}
// Call once RegQueryValueEx to retrieve the necessary buffer size
::RegQueryValueEx(hKey,
lpValueName,
0,
&dwDataType,
lpValue,
// NULL
&dwSize); // will contain the data size
// Alloc the buffer
lpValue = (LPBYTE)malloc(dwSize);
// Call twice RegQueryValueEx to get the value
lRet = ::RegQueryValueEx(hKey,
lpValueName,
0,
&dwDataType,
lpValue,
&dwSize)
;
::RegCloseKey(hKey);
if
(ERROR_SUCCESS != lRet)
{
// Error handling
return;
}
// Enjoy of lpValue...
// free the buffer when no more necessary
free(lpValue);

Note: Most of the material in this article is taken from codeguru.com


Recommended Reading :

Windows SDK Thread: How to use member functions as thread functions?

Q: How to use member functions as thread functions?
A: While trying to use member functions of a class as thread functions the compiler might complain with some kind of error like

error C2664: 'CreateThread' : cannot convert parameter 3 from 'unsigned long (void *)' to
'unsigned long (__stdcall *)(void *)'
None of the functions with this name in scope match the target type

or

error C2665: 'AfxBeginThread' : none of the 2 overloads can convert parameter 1 from
type 'unsigned int (void *)'

The problem is that every thread function has its own prototype, which determines the parameters that gets passed from the operating system to it.

In C++ every member function has a hidden parameter - the so-called 'this' pointer which will be automatically passed to the function. C++ is able to associate a function with a particular instance of an object by means of the 'this' pointer. Member functions access member variables through the 'this' pointer...

class foo
{
public:
void func() { integer_ = 0; }
private:
int integer_;
};

This code will be compiled as

class foo
{
public:
void func(foo* this) { this->integer_ = 0; }
private:
int integer_;
};


The operating system does not call thread functions through objects therefore it cannot handle the automatically added 'this' pointer... To get member functions working as thread routines you need to tell the compiler explicitly not to expect a 'this' pointer. To avoid the automatic 'this' pointer you have two possibilities:

  • Non-member functions
  • Static member functions

Non-member functions are not part of a class and therefore do not have a 'this' pointer. Static member functions do not receive a this' pointer either...thus, if you want to use a member function as a thread routine you need to declare it as 'static'...

More information can be found here and here.

Note: Most of the material in this article is taken from codeguru.com


Recommended Reading :

Threads: How to end a thread?

Q: How to end a thread?
A: There are different ways depending whether the thread will be ended from inside or outside. For ending a thread from inside the function 'ExitThread()' can be used.

ExitThread(0); // Zero is the exit code in this example

This will end the actual thread. It is the preferred method to end a thread. This function will also be called implicitly while returning from the thread procedure and provides a clean shutdown of the thread.

To end a thread from outside there exist also two general possibilities. To provide a clean exit of the thread an event can be set which was passed as a thread parameter and which is frequently checked by the thread procedure.

 

class CFoo
{
public:
CFoo()
;
~CFoo();
private
:
HANDLE m_StopThread
;
HANDLE m_WaitThread;
static
UINT ThreadFunction(LPVOID pvParam);
};
CFoo::CFoo()
{
// Create events
m_StopThread = CreateEvent(0, TRUE, FALSE, 0);
m_WaitThread = CreateEvent(0, TRUE, FALSE, 0);
// Start thread
AfxBeginThread(ThreadFunction, this);
}
CFoo::~CFoo()
{
// Trigger thread to stop
::SetEvent(m_StopThread);
// Wait until thread finished
::WaitForSingleObject(m_WaitThread, INFINITE);
// Close handles
::CloseHandle(m_StopThread);
::CloseHandle(m_WaitThread);
}
UINT CFoo::ThreadFunction(LPVOID* pvParam)
{
CFoo *pParent
= static_cast(pvParam);
while
(true)
{
// Check event for stop thread
if(::WaitForSingleObject(pParent->m_StopThread, 0) == WAIT_OBJECT_0)
{
// Set event
::SetEvent(pParent->m_WaitThread);
return
0;
}
// Do your processing
}
}

If the approach described above is not possible for several reasons (e.g. a lengthy database query is performed) then the thread can be forced to exit by a call to 'TerminateThread()'.

if(TerminateThread(hThread, 0) == FALSE)
// Could not force thread to exit -> call 'GetLastError()'


'hThread' is the handle of the thread which can be obtained by use of any of the functions described in 'How to create a worker thread?'. On Windows NT, Windows 2000 and Windows XP systems, the handle must have 'THREAD_TERMINATE' access.

'TerminateThread()' has some drawbacks which should be considered. It causes the thread to exit. When this occurs, the thread does not have the chance to execute any user-mode code any longer. DLLs attached to the thread are not notified that the thread is terminating.

Additional information is provided by the MSDN:

TerminateThread is a dangerous function that should only be used in the most extreme cases. You should call TerminateThread only if you know exactly what the target thread is doing, and you control all of the code that the target thread could possibly be running at the time of the termination. For example, TerminateThread can result in the following problems:
  • If the target thread owns a critical section, the critical section will not be released.

  • If the target thread is allocating memory from the heap, the heap lock will not be released.

  • If the target thread is executing certain kernel32 calls when it is terminated, the kernel32 state for the thread's process could be inconsistent.

  • If the target thread is manipulating the global state of a shared DLL, the state of the DLL could be destroyed, affecting other users of the DLL.
A thread cannot protect itself against TerminateThread, other than by controlling access to its handles. The thread handle returned by the CreateThread and CreateProcess functions has THREAD_TERMINATE access, so any caller holding one of these handles can terminate your thread.

Note: Most of the material in this article is taken from codeguru.com

 


Recommended Reading :

Threads: How to end a thread?

Q: How to end a thread?
A: There are different ways depending whether the thread will be ended from inside or outside. For ending a thread from inside the function 'ExitThread()' can be used.

ExitThread(0); // Zero is the exit code in this example

This will end the actual thread. It is the preferred method to end a thread. This function will also be called implicitly while returning from the thread procedure and provides a clean shutdown of the thread.

To end a thread from outside there exist also two general possibilities. To provide a clean exit of the thread an event can be set which was passed as a thread parameter and which is frequently checked by the thread procedure.

 

class CFoo
{
public:
CFoo()
;
~CFoo();
private
:
HANDLE m_StopThread
;
HANDLE m_WaitThread;
static
UINT ThreadFunction(LPVOID pvParam);
};
CFoo::CFoo()
{
// Create events
m_StopThread = CreateEvent(0, TRUE, FALSE, 0);
m_WaitThread = CreateEvent(0, TRUE, FALSE, 0);
// Start thread
AfxBeginThread(ThreadFunction, this);
}
CFoo::~CFoo()
{
// Trigger thread to stop
::SetEvent(m_StopThread);
// Wait until thread finished
::WaitForSingleObject(m_WaitThread, INFINITE);
// Close handles
::CloseHandle(m_StopThread);
::CloseHandle(m_WaitThread);
}
UINT CFoo::ThreadFunction(LPVOID* pvParam)
{
CFoo *pParent
= static_cast(pvParam);
while
(true)
{
// Check event for stop thread
if(::WaitForSingleObject(pParent->m_StopThread, 0) == WAIT_OBJECT_0)
{
// Set event
::SetEvent(pParent->m_WaitThread);
return
0;
}
// Do your processing
}
}

If the approach described above is not possible for several reasons (e.g. a lengthy database query is performed) then the thread can be forced to exit by a call to 'TerminateThread()'.

if(TerminateThread(hThread, 0) == FALSE)
// Could not force thread to exit -> call 'GetLastError()'


'hThread' is the handle of the thread which can be obtained by use of any of the functions described in 'How to create a worker thread?'. On Windows NT, Windows 2000 and Windows XP systems, the handle must have 'THREAD_TERMINATE' access.

'TerminateThread()' has some drawbacks which should be considered. It causes the thread to exit. When this occurs, the thread does not have the chance to execute any user-mode code any longer. DLLs attached to the thread are not notified that the thread is terminating.

Additional information is provided by the MSDN:

TerminateThread is a dangerous function that should only be used in the most extreme cases. You should call TerminateThread only if you know exactly what the target thread is doing, and you control all of the code that the target thread could possibly be running at the time of the termination. For example, TerminateThread can result in the following problems:
  • If the target thread owns a critical section, the critical section will not be released.

  • If the target thread is allocating memory from the heap, the heap lock will not be released.

  • If the target thread is executing certain kernel32 calls when it is terminated, the kernel32 state for the thread's process could be inconsistent.

  • If the target thread is manipulating the global state of a shared DLL, the state of the DLL could be destroyed, affecting other users of the DLL.
A thread cannot protect itself against TerminateThread, other than by controlling access to its handles. The thread handle returned by the CreateThread and CreateProcess functions has THREAD_TERMINATE access, so any caller holding one of these handles can terminate your thread.

Note: Most of the material in this article is taken from codeguru.com

 


Recommended Reading :

Threads: How to create a worker thread?

Q: How to create a worker thread?
A: There are several ways to create a worker thread:

  • '_beginthread()' (C run-time library)
  • '_beginthreadex()' (C run-time library)
  • 'CreateThread()' (Win32 API)
  • 'CreateRemoteThread()' (Win32 API)
  • 'AfxBeginThread()' (MFC)

The following samples will show the creation of a thread using the three functions '_beginthreadex()', 'CreateThread()' and 'AfxBeginThread()'.

  1. '_beginthreadex()'
    class CFoo
    {
    public:
    CFoo()
    {
    m_uiThreadID
    = 0;
    m_ulThreadHandle = 0;
    }
    bool Create()
    {
    m_ulThreadHandle
    = _beginthreadex(0,
    0,
    ThreadFunc,
    this,
    0,
    &m_uiThreadID)
    ;
    if
    (!m_ulThreadHandle)
    {
    // Could not create thread
    return false;
    }
    return true;
    }
    private:
    unsigned int m_uiThreadID;
    unsigned long
    m_ulThreadHandle;
    static unsigned int __stdcall
    ThreadFunc(void *pvParam);
    };
  2. 'AfxBeginThread()'
    class CMyDialog : public CDialog
    {
    public:
    CMyDialog(CWnd* pParent
    = NULL)
    : CDialog(CMyDialog::IDD, pParent)
    {
    m_pThread
    = 0;
    }
    bool Create()
    {
    m_pThread
    = AfxBeginThread(ThreadFunc, this);
    if
    (!m_pThread)
    {
    // Could not create thread
    return false;
    }
    return true;
    }
    private:
    CWinThread *m_pThread
    ;
    static
    UINT ThreadFunc(LPVOID pvParam);
    };

     

Note: Most of the material in this article is taken from codeguru.com


Recommended Reading :

Processes: How can I wait until a process ends?

Processes: How can I wait until a process ends?

Q: How can I wait until a process ends?
A: Depending on the method chosen for creating the process, waiting until it is finished is pretty easy to implement. 'CreateProcess()' provides handles both to the process and its primary thread within the 'PROCESS_INFORMATION' structure. The process handle can be used to wait for termination of the process:

// Wait until application has terminated
WaitForSingleObject(piProcessInfo.hProcess, INFINITE);
// Close process and thread handles
::CloseHandle(piProcessInfo.hThread);
::CloseHandle(piProcessInfo.hProcess);

 

'ShellExecuteEx()' provides only a handle to the process but unfortunately it is not guaranteed and is depending on several options you can set within the 'SHELLEXECUTEINFO' structure. For any other method used to create the process a handle to the process needs to be obtained first.

Note: Most of the material in this article is taken from codeguru.com


Recommended Reading :

Processes: How can I start a process?

Q: How can I start a process ?
A: There are several ways to start a process:

  • 'system()' family (C run-time library - ANSI ('system()') or Win NT ('_wsystem()'))
  • '_exec()' family (C run-time library - Win 95, Win NT)
  • '_spawn()' family (C run-time library - Win 95, Win NT)
  • 'WinExec()' (Win32 API)
  • 'ShellExecute()' (Shell API)
  • 'ShellExecuteEx()' (Shell API)
  • 'CreateProcess()' (Win32 API)
  • 'CreateProcessAsUser()' (Win32 API)
  • 'CreateProcessWithLogonW()' (Win32 API)

Most common are 'ShellExecute()', ShellExecuteEx()' and 'CreateProcess()'. Note that 'WinExec()' is provided only for compatibility with 16-bit Windows and should not be used any longer. The following examples will show how to display the text file 'c:\example.txt' in 'notepad.exe' using these three functions...

  1. 'ShellExecute()'
    HINSTANCE hInst = ShellExecute(0,
    "open", // Operation to perform
    "c:\\windows\\notepad.exe", // Application name
    "c:\\example.txt", // Additional parameters
    0, // Default directory
    SW_SHOW);
    if
    (reinterpret_cast<int>(hInst) <= 32)
    {
    // Could not start application
    switch(reinterpret_cast<int>(hInst))
    {
    case 0:
    // The operating system is out of memory or resources.
    break;
    case
    ERROR_FILE_NOT_FOUND:
    // The specified file was not found.
    break;
    case
    ERROR_PATH_NOT_FOUND:
    // The specified path was not found.
    break;
    case
    ERROR_BAD_FORMAT:
    // The .exe file is invalid (non-Microsoft Win32 .exe or error in .exe image).
    break;
    case
    SE_ERR_ACCESSDENIED:
    // The operating system denied access to the specified file.
    break;
    case
    SE_ERR_ASSOCINCOMPLETE:
    // The file name association is incomplete or invalid.
    break;
    case
    SE_ERR_DDEBUSY:
    // The Dynamic Data Exchange (DDE) transaction could not be completed because
    // other DDE transactions were being processed.
    break;
    case
    SE_ERR_DDEFAIL:
    // The DDE transaction failed.
    break;
    case
    SE_ERR_DDETIMEOUT:
    // The DDE transaction could not be completed because the request timed out.
    break;
    case
    SE_ERR_DLLNOTFOUND:
    // The specified dynamic-link library (DLL) was not found.

    case SE_ERR_FNF:
    // The specified file was not found.
    break;
    case
    SE_ERR_NOASSOC:
    // There is no application associated with the given file name extension.
    // This error will also be returned if you attempt to print a file that is
    // not printable.
    break;
    case
    SE_ERR_OOM:
    // There was not enough memory to complete the operation.
    break;
    case
    SE_ERR_PNF:
    // The specified path was not found.
    break;
    case
    SE_ERR_SHARE:
    // A sharing violation occurred.
    break;
    }
    }
  2. 'ShellExecuteEx()'
    SHELLEXECUTEINFO ExecuteInfo;
    memset(&ExecuteInfo, 0, sizeof(ExecuteInfo));
    ExecuteInfo.cbSize = sizeof(ExecuteInfo);
    ExecuteInfo.fMask = 0;
    ExecuteInfo.hwnd = 0;
    ExecuteInfo.lpVerb = "open"; // Operation to perform
    ExecuteInfo.lpFile = "c:\\windows\\notepad.exe"; // Application name
    ExecuteInfo.lpParameters = "c:\\example.txt"; // Additional parameters
    ExecuteInfo.lpDirectory = 0; // Default directory
    ExecuteInfo.nShow = SW_SHOW;
    ExecuteInfo.hInstApp = 0;
    if
    (ShellExecuteEx(&ExecuteInfo) == FALSE)
    // Could not start application -> call 'GetLastError()'
  3. 'CreateProcess()'
    STARTUPINFO siStartupInfo;
    PROCESS_INFORMATION piProcessInfo;
    memset(&siStartupInfo, 0, sizeof(siStartupInfo));
    memset(&piProcessInfo, 0, sizeof(piProcessInfo));
    siStartupInfo.cb = sizeof(siStartupInfo);
    if
    (CreateProcess("c:\\windows\\notepad.exe", // Application name
    " example.txt", // Application arguments
    0,
    0,
    FALSE,
    CREATE_DEFAULT_ERROR_MODE,
    0,
    0, // Working directory
    &siStartupInfo,
    &piProcessInfo)
    == FALSE)
    // Could not start application -> call 'GetLastError()'
    In general 'ShellExecuteEx()' and even 'CreateProcess()' provides more controlling features of the application to start and its termination.

Note: Most of the material in this article is taken from codeguru.com


Recommended Reading :
Syndicate content



Cheap Flights - Loans - Car Insurance - Internet Marketing