Pages

Saturday, October 31, 2009

Polimorphism without virtual functions

I found an interesting place in an old article on CodeProject - WTL for MFC Programmers, Part I - ATL GUI Classes. It is 2003-2005 years, when COM technology, ATL and WTL were in fashion. This place is about the ATL-style templates. In 2003, when I first time used the ATL and WTL, I was told, that such ATL-style does not creae the virtual functions table, so the classes are smaller. And I accepted it as something given, as a rule or a coding standard. This article explains the subject in a so simple and short manner, so even I understood what's going on and why.


I will try to repeat the main idea in this post, in order to be sure that I remember it well now.
#include <iostream>
using namespace std;

template <class T>
class CBaseT
{

public:
void SayHi()
{
T* pT = static_cast<T*>(this);
pT->ClassName();
}

void ClassName()
{
cout << "This is class CBaseT" << endl;
}
};

class CDerive1 : public CBaseT<CDerive1>
{
};

class CDerive2 : public CBaseT<CDerive2>
{
public:
void ClassName()
{
cout << "This is class CDerived2" << endl;
}
};

int main()
{
CDerive1 one;
CDerive2 two;

one.SayHi();
two.SayHi();
}

Firstly, class name CDerive1 and CDerive2 were declared and in the same line already were used for the inheritance list:
class CDerive1 : public CBaseT<CDerive1>
It is legal, because C++ standard allows to use the name immediately after the definition. This trick allowed the second thing for this code - compile-time virtual function:
void ClassName()
This function is not declared as the virtual function, but, in fact, the application screenshot shows that it behaves exactly as the virtual method.
The trick here is
T* pT = static_cast<T*>(this);
in SayHi method of the CBaseT class. It casts type CBaseT to either CDerive1 or CDerive2 depending on which specialization is being invoked. Because template code is generated at compile-time, this cast is safe, because the this object can be only CDerive1 or CDerive2 and nothing else.


If we use the templates in the usual way, we have to check the pointer, and if it is not NULL, we can call the method as it is shown here:
#include <iostream>
using namespace std;

template <class T>
class CTestT
{
public:
void SayHi(T* pT)
{
if (pT != NULL)
pT->ClassName();
}

};

class CTest
{
public:
void ClassName()
{
cout << "This is CTest" << endl;
}
};

int main()
{
CTest test;
CTestT<CTest> one_more_test;
one_more_test.SayHi(&test);
return 0;
}
Check for NULL in the ATL-style templates simply does not exist - it is the this pointer.
The trouble will happen, if I will write:
class CDerive2 : public CBaseT<CDerive1>
So the benefits are obvious:
  1. It does not require using pointers to objects.
  2. It saves memory because there is no need for the virtual functions' table.
  3. It's impossible to call a virtual function through a NULL pointer at run-time because of unintialized vtbl.
  4. All function calls are resolved at compile-time, so they can be optimized.
If you remember the classical "Effective C++" (Scott Meyers), such use of the templates as it was shown above is a trick, it is a little bit against Scott Meyers formula:
  • A template should be used to generate a collection of classes when the type of the objects does not affect the behavior of the class's functions.
  • Inheritance should be used for a collection of classes when the type of the objects does affect the behavior of the class's functions.

Wednesday, October 28, 2009

Google Maps Navigation

WOW!
This application is availabe on the Android 2.0 phones.

Google is so kind that proposes to install this navigation on iPhone, if Apple will agree.
Motorola is going to release Droid phone and hopes that it will have Google Navigation also.

 

And you see what's going on with the Garmin and TomTom stocks?






Friday, October 23, 2009

I don't like STL

I don't like STL. Shame on me. I hope, all other programmers disagree with me.
I can explain why:
1. Mainly, I hate all these exception thrown from the STL classes everywhere. I don't like code protected with try...catch if this code does nothing with the hardware. I prefer to predict all mistakes on a logical level or, in the worst case detect them with a tool like IBM Purify+ or Bound Checker. Of course, I don't want the user to see any error message box.
2. I make a lot of projects running on Windows CE devices and I've met 3 of them and spent few days to make our project running there. The problem was STL. Precisely, the error exception handling. The code had #include and that was enough for the system to disregard my application.
3. The C++ code with STL sometimes looks like C#. STL is a very rich library and has a million of own classes. The code on the application level looks like a pseudo-code.
4. I don't like to support code made by someone else for me with a promise to work fine. :)

Wednesday, October 21, 2009

Win32: Send email

An improved edition of this article was published on EE: "Sending Email with MAPI".

If you have a task to send an email from your C/C++ application, the most recommended way is to use ShellExecute as in the following sample:
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <ShellAPI.h>

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow)
{
ShellExecute(NULL, L"open",
L"mailto:pgnatyuk@gmail.com\
         ?Subject=Hello, world\
         &body=The email sent from ShellExecute",
L"", L"", SW_SHOWNORMAL );
return 0;
}

You will get the same result, if you will choose this long way:
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <mapi.h>

BOOL SendMail(LPCSTR lpszSubject, LPCSTR lpszTo,
LPCSTR lpszName, LPCSTR lpszText);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow)
{
SendMail("Hello", "SMTP:pgnatyuk@gmail.com",
"Pavel", "Text");
return 0;
}

BOOL SendMail(LPCSTR lpszSubject, LPCSTR lpszTo,
LPCSTR lpszName, LPCSTR lpszText)
{
HINSTANCE hMAPI = ::LoadLibrary(L"mapi32.dll");
LPMAPISENDMAIL lpfnMAPISendMail =
(LPMAPISENDMAIL)::GetProcAddress(hMAPI, "MAPISendMail");

char szTo[MAX_PATH] = { 0 };
strcat_s(szTo, lpszTo);

char szName[MAX_PATH] = { 0 };
strcat_s(szName, lpszName);

MapiRecipDesc recipient[1] = { 0 };
recipient[0].ulRecipClass = MAPI_TO;
recipient[0].lpszAddress = szTo;
recipient[0].lpszName = szName;

char szSubject[MAX_PATH] = { 0 };
strcat_s(szSubject, lpszSubject);

char szText[MAX_PATH] = { 0 };
strcat_s(szText, lpszText);

MapiMessage MAPImsg = { 0 };
MAPImsg.lpszSubject = szSubject;
MAPImsg.lpRecips = recipient;
MAPImsg.nRecipCount = 1;
MAPImsg.lpszNoteText = szText;

ULONG nSent = lpfnMAPISendMail(0, 0,
&MAPImsg, MAPI_LOGON_UI | MAPI_DIALOG, 0);

FreeLibrary(hMAPI);
return (nSent == SUCCESS_SUCCESS || nSent == MAPI_E_USER_ABORT);
}
The SendMail function from this example uses the so called Simple MAPI. If you have checked this link, you see that the Simple MAPI functions are declared in MAPI.H but to call these functions you need load mapi32.dll dynamically in the run-time and use GetProcAddress function to get the function pointers:
HINSTANCE hMAPI = ::LoadLibrary(L"mapi32.dll");
LPMAPISENDMAIL lpfnMAPISendMail =
(LPMAPISENDMAIL)::GetProcAddress(hMAPI, "MAPISendMail");

This way allowed to set the subject and the text of the message.

If your mail client (probably Outlook) already runs on your computer, you can send an email from your code and do not prompt the user, do not show any message dialog, you need to replace MAPI_LOGON_UI | MAPI_DIALOG with NULL in the MAPISendMail function call:
ULONG nSent = lpfnMAPISendMail(0, 0, &MAPImsg, NULL, 0);

On Windows Vista, even in this way you will see a system dialog asking you to allow the message sending from your computer.

You can attach a file to your email as it is whown here:
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <stdlib.h>
#include <mapi.h>

BOOL SendFile(LPCSTR lpszSubject, LPCSTR lpszTo,
LPCSTR lpszName, LPCSTR lpszText,
LPCSTR lpszFullFileName);


int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow)
{
SendFile("Hello", "SMTP:pgnatyuk@gmail.com",
"Pavel", "Text", "c:\\text.txt");
return 0;
}

BOOL SendFile(LPCSTR lpszSubject, LPCSTR lpszTo,
LPCSTR lpszName, LPCSTR lpszText,
LPCSTR lpszFullFileName)
{
HINSTANCE hMAPI = ::LoadLibrary(L"mapi32.dll");
LPMAPISENDMAIL lpfnMAPISendMail =
(LPMAPISENDMAIL)::GetProcAddress(hMAPI, "MAPISendMail");

char szDrive[_MAX_DRIVE] = { 0 };
char szDir[_MAX_DIR] = { 0 };
char szName[_MAX_FNAME] = { 0 };
char szExt[_MAX_EXT] = { 0 };
_splitpath_s(lpszFullFileName, szDrive, szDir, szName, szExt);

char szFileName[MAX_PATH] = { 0 };
strcat_s(szFileName, szName);
strcat_s(szFileName, szExt);

char szFullFileName[MAX_PATH] = { 0 };
strcat_s(szFullFileName, lpszFullFileName);

MapiFileDesc MAPIfile = { 0 };
ZeroMemory(&MAPIfile, sizeof(MapiFileDesc));
MAPIfile.nPosition = 0xFFFFFFFF;
MAPIfile.lpszPathName = szFullFileName;
MAPIfile.lpszFileName = szFileName;

char szTo[MAX_PATH] = { 0 };
strcat_s(szTo, lpszTo);

char szNameTo[MAX_PATH] = { 0 };
strcat_s(szNameTo, lpszName);

MapiRecipDesc recipient = { 0 };
recipient.ulRecipClass = MAPI_TO;
recipient.lpszAddress = szTo;
recipient.lpszName = szNameTo;

char szSubject[MAX_PATH] = { 0 };
strcat_s(szSubject, lpszSubject);

char szText[MAX_PATH] = { 0 };
strcat_s(szText, lpszText);

MapiMessage MAPImsg = { 0 };
MAPImsg.lpszSubject = szSubject;
MAPImsg.lpRecips = &recipient;
MAPImsg.nRecipCount = 1;
MAPImsg.lpszNoteText = szText;
MAPImsg.nFileCount = 1;
MAPImsg.lpFiles = &MAPIfile;

ULONG nSent = lpfnMAPISendMail(0, 0, &MAPImsg, NULL, 0);

FreeLibrary(hMAPI);
return (nSent == SUCCESS_SUCCESS || nSent == MAPI_E_USER_ABORT);
}
These examples work, if you have your mail client (Outlook) running. Otherwise MAPISendMail function will fail with MAPI_E_LOGON_FAILURE return code.

Simple MAPI has 2 special functions that are supposed to fix the situation:
1. MAPILogon
2. MAPILogoff
You can test these functions with the following sample:
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <mapi.h>

BOOL SendMail(LPCSTR lpszSubject, LPCSTR lpszTo,
LPCSTR lpszName, LPCSTR lpszText);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow)
{
SendMail("Hello", "SMTP:pgnatyuk@gmail.com",
"Pavel", "Text. No mail client running.");

return 0;
}

BOOL SendMail(LPCSTR lpszSubject, LPCSTR lpszTo,
LPCSTR lpszName, LPCSTR lpszText)
{
HINSTANCE hMAPI = NULL;
LPMAPISENDMAIL lpfnMAPISendMail = NULL;
LPMAPILOGON lpfnMAPILogOn = NULL;
LPMAPILOGOFF lpfnMAPILogOff = NULL;

hMAPI = ::LoadLibrary(L"mapi32.dll");
lpfnMAPISendMail =
(LPMAPISENDMAIL)::GetProcAddress(hMAPI, "MAPISendMail");

lpfnMAPILogOn =
(LPMAPILOGON)::GetProcAddress(hMAPI, "MAPILogon");

lpfnMAPILogOff =
(LPMAPILOGOFF)::GetProcAddress(hMAPI, "MAPILogoff");

char szTo[MAX_PATH] = { 0 };
strcat_s(szTo, lpszTo);

char szName[MAX_PATH] = { 0 };
strcat_s(szName, lpszName);

MapiRecipDesc recipient[1] = { 0 };
recipient[0].ulRecipClass = MAPI_TO;
recipient[0].lpszAddress = szTo;
recipient[0].lpszName = szName;

char szSubject[MAX_PATH] = { 0 };
strcat_s(szSubject, lpszSubject);

char szText[MAX_PATH] = { 0 };
strcat_s(szText, lpszText);

MapiMessage MAPImsg = { 0 };
MAPImsg.lpszSubject = szSubject;
MAPImsg.lpRecips = recipient;
MAPImsg.nRecipCount = 1;
MAPImsg.lpszNoteText = szText;

LHANDLE lhSession;
lpfnMAPILogOn(0, NULL, NULL,0, 0, &lhSession);

ULONG nSent = lpfnMAPISendMail(lhSession, 0,
&MAPImsg, MAPI_LOGON_UI | MAPI_DIALOG, 0);

lpfnMAPILogOff(lhSession, 0, 0, 0);

FreeLibrary(hMAPI);
return (nSent == SUCCESS_SUCCESS || nSent == MAPI_E_USER_ABORT);
}

You can pass the outlook profile name and your password to the MAPILogon function:
LHANDLE lhSession;
lpfnMAPILogOn(0, szProfile, szPassword,0, 0, &lhSession);

On CodePlex you can download MFCMAPI - Microsoft's published APIs providing access to MAPI.

Saturday, October 17, 2009

Win32: Первая OpenGL программа


OpenGL добавили еще в Windows NT. Если кто-то собирается изучать OpenGL, то для начала хорошо бы посмотреть самую простую программу поддерживающую OpenGL:
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <WinGDI.h>
#include <GL/gl.h>

#pragma comment(lib, "opengl32.lib")

LPCWSTR s_szWndClassName = L"Window for Open GL Test application";

ATOM RegisterWndClass(HINSTANCE, LPCWSTR);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

void ChangeSize(int cx, int cy);
BOOL SetPixelFormat(HDC);
void RenderScene();

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);

HWND hWnd = NULL;
MSG msg = { 0 };

RegisterWndClass(hInstance, s_szWndClassName);

hWnd = CreateWindowEx(WS_EX_APPWINDOW | WS_EX_WINDOWEDGE,
s_szWndClassName, s_szWndClassName,
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
NULL, NULL, hInstance, NULL);

if (hWnd != NULL)
{
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

return (int)msg.wParam;
}

ATOM RegisterWndClass(HINSTANCE hInstance, LPCWSTR lpszWndClassName)
{
WNDCLASSEX wcex = { 0 };
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wcex.lpfnWndProc = WndProc;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = lpszWndClassName;
return RegisterClassEx(&wcex);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
static HGLRC hRC;
static HDC hDC;

switch (message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
SetPixelFormat(hDC);
hRC = wglCreateContext(hDC);
wglMakeCurrent(hDC,hRC);
break;

case WM_PAINT:
RenderScene();
SwapBuffers(hDC);
ValidateRect(hWnd, NULL);
break;

case WM_DESTROY:
wglMakeCurrent(hDC,NULL);
wglDeleteContext(hRC);
ReleaseDC(hWnd, hDC);
PostQuitMessage(0);
break;

    default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

BOOL SetPixelFormat(HDC hDC)
{
PIXELFORMATDESCRIPTOR pfd = { 0 };
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 24;
pfd.cDepthBits = 32;
pfd.iLayerType = PFD_MAIN_PLANE;
int nPixelFormat = ChoosePixelFormat(hDC, &pfd);
if (nPixelFormat == 0)
return FALSE;
BOOL bResult = SetPixelFormat(hDC, nPixelFormat, &pfd);
return bResult;
}

void RenderScene()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
Эта апликация не более, чем шаблон. Она ничего не рисует, а просто создает окно и готовит рабочую среду для использования OpenGL. Рисование должно быть добавлено в функцию RenderScene:
void RenderScene()
{
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(0.0f, 0.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex2f(0.0f, 1.0f);
glColor3f(0.0f, 1.0f, 0.0f);
glVertex2f(0.87f, -0.5f);
glColor3f(0.0f, 0.0f, 1.0f);
glVertex2f(-0.87f, -0.5f);
glEnd();
glPopMatrix();
}

Можна реагировать на изменения размеров окна:
case WM_SIZE:
ChangeSize(LOWORD(lParam), HIWORD(lParam));
break;

Ну и сама ChangeSize:
void ChangeSize(int cx, int cy)
{
glViewport(0, 0, cx, cy);
}

Можно добавить таймер и заставить нарисованый треугольник крутиться - SetTimer в обработку WM_CREATE сообщения, KillTimer в WM_DESTROY, добавить обработку WM_TIMER - просто вызвать InvalidateRect.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, 
WPARAM wParam, LPARAM lParam)
{
static HGLRC hRC; // Save the rendering context between calls
static HDC hDC; // Save the device context between calls


switch (message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
SetPixelFormat(hDC);
hRC = wglCreateContext(hDC);
wglMakeCurrent(hDC,hRC);
SetTimer(hWnd, 123, 100, NULL);
break;

case WM_PAINT:
RenderScene();
SwapBuffers(hDC);
ValidateRect(hWnd, NULL);
break;

case WM_TIMER:
InvalidateRect(hWnd, NULL, FALSE);
break;

case WM_SIZE:
ChangeSize(LOWORD(lParam), HIWORD(lParam));
break;

case WM_DESTROY:
KillTimer(hWnd, 123);
wglMakeCurrent(hDC,NULL);
wglDeleteContext(hRC);
ReleaseDC(hWnd, hDC);
PostQuitMessage(0);
break;

    default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

И изменить функцию рисования:
void RenderScene()
{
static GLfloat angle = 0;
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glPushMatrix();
glRotatef(angle, 0.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex2f(0.0f, 1.0f);
glColor3f(0.0f, 1.0f, 0.0f);
glVertex2f(0.87f, -0.5f);
glColor3f(0.0f, 0.0f, 1.0f);
glVertex2f(-0.87f, -0.5f);
glEnd();
glPopMatrix();

angle += 1.0f;
}

И абсолютно без всякого смысла можно сделать окно полупрозрачным - WS_EX_LAYERED в вызов CreateWindowEx и вызвать SetLayeredWindowAttributes для дескриптора окна:
SetLayeredWindowAttributes(hWnd, 0, (255 * 70) / 100, LWA_ALPHA);

MSDN:OpenGL I: Quick Start
OpenGL Win32 Tutorial