Все записи автора Brainstream

Нюхаем сеть через Linux

В этой статье речь пойдёт не столько о написании сниффера для Linux, сколько о структуре пакетов стека протоколов TCP/IP. Идея статьи взята из статьи на сайте xakep.ru.
Мы будем рассматривать пакеты, которые попадают к нам на сетевую карту. Эти пакеты относятся к протоколу Ethernet. Примерная структура этого пакета приведена в таблице ниже.
Ethernet заголовок
Ethernet данные (фрейм сетевого протокола)
IP заголовок
IP данные (фрейм транспортного протокола)
TCP или UDP заголовок
Данные приклодного протокола (HTTP, FTP, SMB итд).

Содержание статьи

Sniffer

Было бы не справедливо, если бы я приводил примеры анализа пакетов, не показав, как их получить. Для этой цели я написал сниффер, аналогичный тому, что описан в упомянутой статье.
#ifndef __LINUX_SNIFFER_SHIFFER_H__
#define __LINUX_SNIFFER_SHIFFER_H__


#include <set>
#include <string.h>
#include <stdexcept>
#include "Analyzer.h"


namespace LinuxSniffer {

class SnifferError :
public std::runtime_error
{
public:
SnifferError(const std::string & message) throw() :
std::runtime_error(message)
{
}

virtual ~SnifferError() throw()
{
}
}; // class SnifferError


class Sniffer
{
public:
explicit Sniffer(const std::string & device_name, bool sniff_all = false)
throw(SnifferError);
virtual ~Sniffer();
bool addAnalyzer(Analyzer * analyzer);
bool removeAnalyzer(Analyzer * analyzer);
void start() throw(SnifferError);
void stop();

private:
void deinit();
void makeSocket() throw(SnifferError);
void bindSocketToDevice() throw(SnifferError);
void setPromiscuousMode() throw(SnifferError);
void unsetPromiscuousMode();
void runAnalyzers(const unsigned char * frame, size_t frame_size);

private:
Sniffer(const Sniffer &);
Sniffer & operator = (const Sniffer &);

private:
const std::string m_device;
const bool m_sniff_all;
std::set<Analyzer *> m_analyzers;
int m_socket;
bool m_is_promiscuouse_mode_set;
bool m_is_stopping;
unsigned char * mp_frame_buffer;
static const size_t m_frame_buffer_size = 65536;
}; // class Sniffer


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_SHIFFER_H__
Когда сниффер получает пакет, он запускает всех зарегистрированных анализаторов. Для их регистрации и служат методы addAnalyzer и removeAnalyzer.
Каждый анализатор должен реализовывать интерфейс Analyzer
class Analyzer
{
public:
virtual ~Analyzer() { }
virtual void analyze(const uint8_t * frame, size_t frame_size) = 0;
}; // class Analyzer
Именно этим механизмом мы и будем пользоваться при анализе структуры пакетов стека TCP/IP.
Приведу полную реализацию класса Sniffer
#include <errno.h>
#include <netpacket/packet.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <linux/if.h>
#include <linux/if_ether.h>
#include <linux/sockios.h>
#include "Sniffer.h"


using namespace LinuxSniffer;


Sniffer::Sniffer(const std::string & device_name, bool sniff_all)
throw(SnifferError) :
m_device(device_name),
m_sniff_all(sniff_all),
m_is_promiscuouse_mode_set(false),
m_is_stopping(false),
mp_frame_buffer(0)
{
makeSocket();
mp_frame_buffer = new unsigned char[m_frame_buffer_size];
}

Sniffer::~Sniffer()
{
deinit();
}

void Sniffer::deinit()
{
::close(m_socket);
unsetPromiscuousMode();
delete [] mp_frame_buffer;
}

bool Sniffer::addAnalyzer(Analyzer * analyzer)
{
return m_analyzers.insert(analyzer).second;
}

bool Sniffer::removeAnalyzer(Analyzer * analyzer)
{
return m_analyzers.erase(analyzer) > 0;
}

void Sniffer::makeSocket()
throw(SnifferError)
{
m_socket = ::socket(AF_PACKET, SOCK_RAW, ::htons(ETH_P_ALL));
if(-1 == m_socket)
throw SnifferError(::strerror(errno));
try
{
bindSocketToDevice();
if(m_sniff_all)
setPromiscuousMode();
}
catch(...)
{
deinit();
throw;
}
}

void Sniffer::bindSocketToDevice()
throw(SnifferError)
{
const size_t device_name_len = m_device.length() + 1;
char * device = new char[device_name_len];
::strcpy(device, m_device.c_str());
device[m_device.length()] = '';
int setopt_result = ::setsockopt(m_socket, SOL_SOCKET,
SO_BINDTODEVICE, device, device_name_len);
delete [] device;
if(-1 == setopt_result)
throw SnifferError(::strerror(errno));
}

void Sniffer::setPromiscuousMode() throw(SnifferError)
{
ifreq iface;
::strcpy(iface.ifr_name, m_device.c_str());
if(::ioctl(m_socket, SIOCGIFFLAGS, &iface) < 0)
throw SnifferError(::strerror(errno));
iface.ifr_flags |= IFF_PROMISC;
if(::ioctl(m_socket, SIOCSIFFLAGS, &iface) < 0)
throw SnifferError(::strerror(errno));
m_is_promiscuouse_mode_set = true;
}

void Sniffer::unsetPromiscuousMode()
{
if(!m_is_promiscuouse_mode_set)
return;
ifreq iface;
::strcpy(iface.ifr_name, m_device.c_str());
if(::ioctl(m_socket, SIOCGIFFLAGS, &iface) >= 0)
{
iface.ifr_flags &= ~IFF_PROMISC;
if(::ioctl(m_socket, SIOCSIFFLAGS, &iface) >= 0)
m_is_promiscuouse_mode_set = false;
}
}

void Sniffer::start() throw(SnifferError)
{
while(!m_is_stopping)
{
ssize_t length = ::recvfrom(m_socket, mp_frame_buffer,
m_frame_buffer_size, 0, 0, 0);
if(-1 == length)
throw SnifferError(::strerror(errno));
runAnalyzers(mp_frame_buffer, length);
}
}

void Sniffer::runAnalyzers(const unsigned char * frame, size_t frame_size)
{
for(std::set<Analyzer *>::iterator it = m_analyzers.begin();
m_analyzers.end() != it; ++it)
{
Analyzer * analyzer = *it;
if(0 != analyzer)
analyzer->analyze(frame, frame_size);
}
}

void Sniffer::stop()
{
m_is_stopping = true;
}
Конструктор принимает два параметра: имя устройства для прослушивания, например eth0 и флаг, включающий неразборчивое прослушивание. Для тех сетей, которые работают через хабы, этот режим будет прослушивать все пакеты, даже те, которые не адресованы Вашей машине.
Конструктор вызывает метод makeSocket, который создаёт сокет для прослушивания устройства. Делается это вызовом
m_socket = ::socket(AF_PACKET, SOCK_RAW, ::htons(ETH_P_ALL));
Я отошёл от того вызова, что был показан в статье на xakep.ru по причине того, что в man 2 socket написано
SOCK_PACKET
Устарело и не должно использоваться в новых программах; см. packet(7).
А страница man 7 packet говорит, что нужно использовать SOCK_RAW.
Далее метод bindSocketToDevice привязывает созданный сокет к устройству вызовом
::setsockopt(m_socket, SOL_SOCKET, SO_BINDTODEVICE, device, device_name_len); 
Последним шагом в подготовке сокета является необязательная установка опции неразборчивого прослушивания методом setPromiscuousMode. Для её установки мы должны получить структуру ifreq из сокета вызовом
::ioctl(m_socket, SIOCGIFFLAGS, &iface)
добавить флаг IFF_PROMISC в поле ifr_flags
iface.ifr_flags |= IFF_PROMISC;
и записать сруктуру обратно
::ioctl(m_socket, SIOCSIFFLAGS, &iface)
Всё готово к запуску сниффера. Метод start вызывает в цикле функцию recvfrom и передаёт полученный буфер анализаторам.
ssize_t length = ::recvfrom(m_socket, mp_frame_buffer, m_frame_buffer_size, 0, 0, 0);

Стек протоколов TCP/IP

Прежде чем перейти к описанию анализатора, давайте рассмотрим фреймы протоколов стека TCP/IP.

Ethernet

Пакеты этого протокола являются низшими из тех, что мы можем получить. Существует несколько версий пакетов ethernet, мы рассмотрим самую популярную - вторую версию. В таблице ниже показана структура фрейма Ethernet 2
Смещение Размер Описание
0 байт 6 байт MAC адрес назначения
6 байт 6 байт MAC адрес источника
12 байт 2 байта Тип Etherner
14 байт 46 - 1500 байт Данные
Последние 4 байта 4 байта CRC контрольная сумма
Для разбора фреймов всех протоколов я написал базовый класс
#ifndef __LINUX_SNIFFER_PROTOCOL_FRAME_H__
#define __LINUX_SNIFFER_PROTOCOL_FRAME_H__

#include <string>
#include <sys/types.h>
#include <stdint.h>

namespace LinuxSniffer {


class ProtocolFrame
{
public:
ProtocolFrame(const std::string & protocol_name) :
m_protocol_name(protocol_name),
m_data_offset(0)
{
}

virtual ~ProtocolFrame()
{
}

const std::string & getName() const
{
return m_protocol_name;
}

virtual bool init(const uint8_t * buffer, size_t buffer_size) = 0;

size_t getDataOffset() const
{
return m_data_offset;
}

protected:
void setDataOffset(size_t offset)
{
m_data_offset = offset;
}

private:
const std::string m_protocol_name;
size_t m_data_offset;
}; // class ProtocolFrame


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_PROTOCOL_FRAME_H__
Этот класс хранит и возвращает имя протокола описываемого фрейма и смещение данных от начала фрейма. Также класс передоставляет абстрактный метод инициализации из буфера фрейма и его размера.
Класс EthernetFrameV2 наследуется от ProtocolFrame и добавляет специфическую информацию.
#ifndef __LINUX_SNIFFER_ETHERNET_FRAME_V2_H__
#define __LINUX_SNIFFER_ETHERNET_FRAME_V2_H__

#include <cstring>
#include "ProtocolFrame.h"
#include "MacAddress.h"

namespace LinuxSniffer {

class EthernetFrameV2 :
public ProtocolFrame
{
public:
EthernetFrameV2() :
ProtocolFrame("Ethernet Version 2")
{
}

virtual ~EthernetFrameV2()
{
}

virtual bool init(const uint8_t * buffer, size_t buffer_size);

const MacAddress & getSourceMacAddress() const
{
return m_src_mac_addr;
}

const MacAddress & getDestinationMacAddress() const
{
return m_dest_mac_addr;
}

static const uint8_t (& getEthernetType())[2]
{
static bool is_init = false;
static uint8_t type[2];
if(!is_init)
{
::memcpy(type, "x08x00", 2);
is_init = true;
}
return type;
}

private:
MacAddress m_src_mac_addr;
MacAddress m_dest_mac_addr;
}; // class EthernetFrameV2

} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_ETHERNET_FRAME_V2_H__
Дополнительные поля являются MAC адресами отправителя и получателя. Эти поля имеют тип MacAddress, который описан простым классом
#ifndef __LINUX_SNIFFER_MAC_ADDRESS_H__
#define __LINUX_SNIFFER_MAC_ADDRESS_H__

#include <stdint.h>
#include <string>
#include <cstdio>

namespace LinuxSniffer {


class MacAddress
{
public:
MacAddress() :
b0(0),
b1(0),
b2(0),
b3(0),
b4(0),
b5(0)
{
}

explicit MacAddress(const uint8_t address[6]) :
b0(address[0]),
b1(address[1]),
b2(address[2]),
b3(address[3]),
b4(address[4]),
b5(address[5])
{
}

std::string toString() const
{
char str[16];
::sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x", b0, b1, b2, b3, b4, b5);
return str;
}

public:
uint8_t b0;
uint8_t b1;
uint8_t b2;
uint8_t b3;
uint8_t b4;
uint8_t b5;
}; // class MacAddress


} // namespace LinuxSniffer



#endif // __LINUX_SNIFFER_MAC_ADDRESS_H__
Реализацтя метода init класса EthernetFrameV2 сводится к проверке версии протокола Ethernet и получению MAC адресов отправителя и получателя. Для простоты, получение контрольной суммы опустим. Для того, чтобы получить все необходимые поля, достаточно привести буфер фрейма к типу ether_header, объявленному в netinet/ether.h.
#include <cstring>
#include <netinet/ether.h>
#include "EthernetFrameV2.h"

using namespace LinuxSniffer;


bool EthernetFrameV2::init(const uint8_t * buffer, size_t buffer_size)
{
if(buffer_size < sizeof(ether_header))
return false;
const ether_header * hdr = reinterpret_cast<const ether_header *>(buffer);
if(::memcmp(&hdr->ether_type, getEthernetType(), sizeof(getEthernetType())))
return false;
setDataOffset(sizeof(ether_header));
m_src_mac_addr = MacAddress(hdr->ether_shost);
m_dest_mac_addr = MacAddress(hdr->ether_shost);
return true;
}

Internet Protocol

В секции данных фрейма протокола ethernet хранится фрейм протокола IP. Этот протокол добавляет к пакету информацию об IP адресах отправителя и получателя и множество служебных данных. В следующей таблице показана структура фрейма IP версии 4.
Байты Смещение в битах Размер в битах Описание
0 0 4 Версия
4 4 Размер заголовка
1 8 6 Точка кода дифференцированных услуг (Differentiated services code point)
14 2 Явное уведомление о перегруженности (Explicit congestion notification)
2-3 16 16 Размер пакета
4-5 32 16 Идентификатор
6-7 48 3 Флаги
51 13 Смещение фрагмента
8 64 8 Время жизни
9 72 8 Протокол
10 80 16 Контрольная сумма заголовка
11-15 96 32 IP адрес источника
16-20 128 32 IP адрес назначения
20-24 160 32 Опции (если размер заголовка > 5)
20+ или 24+ 160 или 192 - Данные
В ранних версиях спецификации протокола "Точка кода дифференцированных услуг" и "Явное уведомление о перегруженности" были объединены в одно поле "Тип сервиса". Для того, чтобы использовать новое деление участники обмена должны договориться об этом. Я не буду разделять эти поля и буду использовать тип сервиса.
В поле "Флаги" биты от старшего к младшему означают:
0: Зарезервирован, должен быть равен 0;
1: Не фрагментировать;
2: У пакета еще есть фрагменты.
"Идентификатор" используется для предоставления информации о фрагментации пакета. Поле "Протокол" сообщает идентификатор протокола, фрейм которого находится в данных.
Думаю, остальные поля не нуждаются в комментариях.
Класс, описывающий IP фрейм приведён ниже
#ifndef __LINUX_SNIFFER_IP_FRAME_V4_H__
#define __LINUX_SNIFFER_IP_FRAME_V4_H__

#include <string>
#include "ProtocolFrame.h"

namespace LinuxSniffer {


class IpFrameV4 :
public ProtocolFrame
{
public:
IpFrameV4() :
ProtocolFrame("Internet Protocol Version 4"),
m_header_length(0),
m_tos(0),
m_package_size(0),
m_id(0),
m_flags(0),
m_frag_offset(0),
m_time_to_life(0),
m_protocol(0),
m_checksum(0)
{
}

virtual ~IpFrameV4()
{
}

virtual bool init(const uint8_t * buffer, size_t buffer_size);

const std::string & getSourceAddress() const
{
return m_src_ip_addr;
}

const std::string & getDestinationAddress() const
{
return m_dest_ip_addr;
}

uint8_t getHeaderLength() const
{
return m_header_length;
}

uint8_t getTypeOfService() const
{
return m_tos;
}

uint16_t getPackageSize() const
{
return m_package_size;
}

uint16_t getId() const
{
return m_id;
}

uint8_t getFlags() const
{
return m_flags;
}

uint16_t getFragmintationOffset() const
{
return m_frag_offset;
}

uint8_t getTimeToLife() const
{
return m_time_to_life;
}

uint8_t getProtocolId() const
{
return m_protocol;
}

uint16_t getHeaderCheckSum() const
{
return m_checksum;
}

public:
static const uint8_t m_ipv4_version = 4;

private:
void splitFragmentOffsetAndFlags();

private:
std::string m_src_ip_addr;
std::string m_dest_ip_addr;
uint8_t m_header_length;
uint8_t m_tos;
uint16_t m_package_size;
uint16_t m_id;
uint8_t m_flags;
uint16_t m_frag_offset;
uint8_t m_time_to_life;
uint8_t m_protocol;
uint16_t m_checksum;
}; // class IpFrameV4


} // namespace LinuxSniffer



#endif // __LINUX_SNIFFER_IP_FRAME_V4_H__
Для того, чтобы получить все поля из буфера фрейма достаточно привести указатель на него к указателю на структуру iphdr, объявленной в файле netinet/ip.h. В реализации метода init класса IpFrameV4 именно так и сделано
#include <netinet/ip.h>
#include <arpa/inet.h>
#include "IpFrameV4.h"



using namespace LinuxSniffer;

bool IpFrameV4::init(const uint8_t * buffer, size_t buffer_size)
{
if(buffer_size < sizeof(iphdr) || 0 == buffer)
return false;
const iphdr * ip_header = reinterpret_cast<const iphdr *>(buffer);
if(m_ipv4_version != ip_header->version)
return false;
in_addr addr;
addr.s_addr = ip_header->saddr;
m_src_ip_addr = ::inet_ntoa(addr);
addr.s_addr = ip_header->daddr;
m_dest_ip_addr = ::inet_ntoa(addr);
m_header_length = ip_header->ihl;
m_tos = ip_header->tos;
m_package_size = ip_header->tot_len;
m_id = ip_header->id;
m_frag_offset = ip_header->frag_off;
m_time_to_life = ip_header->ttl;
m_protocol = ip_header->protocol;
m_checksum = ip_header->check;
setDataOffset(m_header_length);
splitFragmentOffsetAndFlags();
return true;
}

void IpFrameV4::splitFragmentOffsetAndFlags()
{
union
{
struct
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
uint16_t flags : 3;
uint16_t frag_offset : 13;
#elif __BYTE_ORDER == __BIG_ENDIAN
uint16_t frag_offset : 13;
uint16_t flags : 3;
#endif
} spl;
uint16_t num;
} splitter;

splitter.num = m_frag_offset;
m_flags = splitter.spl.flags;
m_frag_offset = splitter.spl.frag_offset;
}
Смещение фрагмента и флаги в структуре iphdr объединены. Для их разделения я написал метод splitFragmentOffsetAndFlags.

Transmission Control Protocol

Протоколы траспортного уровня добавляют сведения о портах источника и назначения. Для описания общих свойств всех фреймов транспортных протоколов я ввёл класс TransportProtocolFrame, унаследованный от ProtocolFrame.
#ifndef __LINUX_SNIFFER_TRANSPORT_PROTOCOL_FRAME_H__
#define __LINUX_SNIFFER_TRANSPORT_PROTOCOL_FRAME_H__

#include "ProtocolFrame.h"


namespace LinuxSniffer {


class TransportProtocolFrame :
public ProtocolFrame
{
public:
TransportProtocolFrame(const std::string & protocol_name) :
ProtocolFrame(protocol_name),
m_src_port(0),
m_dest_port(0)
{
}

virtual ~TransportProtocolFrame()
{
}

uint16_t getSourcePort() const
{
return m_src_port;
}

uint16_t getDestinationPort() const
{
return m_dest_port;
}

protected:
void setSourcePort(uint16_t port)
{
m_src_port = port;
}

void setDestinationPort(uint16_t port)
{
m_dest_port = port;
}

private:
uint16_t m_src_port;
uint16_t m_dest_port;
}; // class TransportProtocolFrame


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_TRANSPORT_PROTOCOL_FRAME_H__
Структура фрейма протокола TCP, являющегося одним из протоколов транспортного уровня, показана в следующей таблице.
Байты Смещение в битах Размер в битах Описание
0-1 0 16 Порт источника
2-3 16 16 Порт назначения
4-8 32 32 Номер последовательности
8-12 64 32 Номер подтверждения последовательности
13-14 96 4 Смещение данных
100 6 Зарезервировано
106 6 Флаги
15-16 112 16 Размер окна
17-18 128 16 Контрольная сумма
19-20 144 16 Указатель важности
21+ 160 - Необязательные опции
За опциями или 21+ За опциями или 160 - Данные
Протокол TCP контролирует последовательность пакетов. Именно для этого нужны поля "Номер последовательности" и "Номер подтверждения последовательности".
"Флаги":
URG - Поле "Указатель важности" задействовано;
ACK - Поле "Номер подтверждения" задействовано;
PSH - Инструктирует получателя протолкнуть данные, накопившиеся в приемном буфере, в приложение пользователя;
RST - Оборвать соединения;
SYN - Синхронизация номеров последовательности;
FIN - флаг указывает на завершение соединения.
"Размер окна" - это число байтов, которые получатель готов принять.
"Указатель важности" указывает на номер октета, которым заканчиваются важные данные. Это поле игнорируется, если флаг URG не установлен.
Для чтения фрейма протокола TCP я расширил класс TransportProtocolFrame классом TcpFrame.
#ifndef __LINUX_SNIFFER_TCP_FRAME_H__
#define __LINUX_SNIFFER_TCP_FRAME_H__

#include "TransportProtocolFrame.h"

namespace LinuxSniffer {


class TcpFrame :
public TransportProtocolFrame
{
public:
TcpFrame() :
TransportProtocolFrame("Transmission Control Protocol"),
m_sequence_number(0),
m_ascknowledgment_number(0),
m_flag_fin(false),
m_flag_syn(false),
m_flag_rst(false),
m_flag_psh(false),
m_flag_ack(false),
m_flag_urg(false),
m_window_size(0),
m_checksum(0),
m_urgent_ptr(0)
{
}

virtual ~TcpFrame()
{
}

virtual bool init(const uint8_t * buffer, size_t buffer_size);

uint32_t getSequenceNumber() const
{
return m_sequence_number;
}

uint32_t getAscknowledgmentNumber() const
{
return m_ascknowledgment_number;
}

bool isFinFlagSet() const
{
return m_flag_fin;
}

bool isSynFlagSet() const
{
return m_flag_syn;
}

bool isRstFlagSet() const
{
return m_flag_rst;
}

bool isPshFlagSet() const
{
return m_flag_psh;
}

bool isAckFlagSet() const
{
return m_flag_ack;
}

bool isUrgFlagSet() const
{
return m_flag_urg;
}

uint16_t getWindowSize() const
{
return m_window_size;
}

uint16_t getCheckSum() const
{
return m_checksum;
}

uint16_t getUrgentPtr() const
{
return m_urgent_ptr;
}

public:
static const uint8_t m_protocol_id = 6;

private:
uint32_t m_sequence_number;
uint32_t m_ascknowledgment_number;
bool m_flag_fin;
bool m_flag_syn;
bool m_flag_rst;
bool m_flag_psh;
bool m_flag_ack;
bool m_flag_urg;
uint16_t m_window_size;
uint16_t m_checksum;
uint16_t m_urgent_ptr;
}; // class TcpFrame


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_TCP_FRAME_H__
Как и в случае с IP, для фрейма TCP в заголовочных файлах linux припасена структура tcphdr. Находится она в файле netinet/tcp.h. Реализуем метод init с использованием этой структуры.
#include <netinet/tcp.h>
#include "TcpFrame.h"

using namespace LinuxSniffer;


bool TcpFrame::init(const uint8_t * buffer, size_t buffer_size)
{
if(buffer_size < sizeof(tcphdr))
return false;
const tcphdr * tcp_header = reinterpret_cast<const tcphdr *>(buffer);
setSourcePort(tcp_header->source);
setDestinationPort(tcp_header->dest);
setDataOffset(tcp_header->doff);
m_sequence_number = tcp_header->seq;
m_ascknowledgment_number = tcp_header->ack_seq;
m_flag_fin = tcp_header->fin != 0;
m_flag_syn = tcp_header->syn != 0;
m_flag_rst = tcp_header->rst != 0;
m_flag_psh = tcp_header->psh != 0;
m_flag_ack = tcp_header->ack != 0;
m_flag_urg = tcp_header->urg != 0;
m_window_size = tcp_header->window;
m_checksum = tcp_header->check;
m_urgent_ptr = tcp_header->urg_ptr;
return true;
}

User Datagram Protocol

Последний протокол, который мы рассмотрим в этой статье - UDP. Это простой протокол транспортного уровня, который ни как не контролирует доставку пакетов и не устанавливает соединения. По этим причинам, фрейм этого протокола весьма прост.
Байты Смещение в битах Размер в битах Описание
0-1 0 16 Порт источника
2-3 16 16 Порт назначения
4-5 32 16 Длина дейтаграммы
6-7 48 16 Контрольная сумма
8+ 64 - Данные
В этой таблице всё понятно без коментариев.
Второй класс, унаследованный от TransportProtocolFrame - UdpFrame.
#ifndef __LINUX_SNIFFER_UDP_FRAME_H__
#define __LINUX_SNIFFER_UDP_FRAME_H__

#include "TransportProtocolFrame.h"

namespace LinuxSniffer {


class UdpFrame :
public TransportProtocolFrame
{
public:
UdpFrame() :
TransportProtocolFrame("User Datagram Protocol")
{
}

virtual ~UdpFrame()
{
}

virtual bool init(const uint8_t * buffer, size_t buffer_size);

uint16_t getDatagramLength() const
{
return m_datagram_length;
}

uint16_t getCheckSum() const
{
return m_checksum;
}

public:
static const uint8_t m_protocol_id = 17;

private:
uint16_t m_datagram_length;
uint16_t m_checksum;
}; // class UdpFrame


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_UDP_FRAME_H__
Аналогично фреймам протоколов IP и TCP, реализуем метод init с использованием структуры udphdr, объявленной в файле netinet/udp.h.
#include <netinet/udp.h>
#include "UdpFrame.h"

using namespace LinuxSniffer;


bool UdpFrame::init(const uint8_t * buffer, size_t buffer_size)
{
if(buffer_size < sizeof(udphdr))
return false;
const udphdr * udp_header = reinterpret_cast<const udphdr *>(buffer);
setSourcePort(udp_header->source);
setDestinationPort(udp_header->dest);
setDataOffset(sizeof(udphdr));
m_datagram_length = udp_header->len;
m_checksum = udp_header->check;
return true;
}

Анализатор

Теперь, когда мы знаем структуру основных протоколов, ничего не стоит проанализировать данные, которые мы получаем от сниффера. Для этого реализуем интерфейс Analyzer.
#ifndef __LINUX_SNIFFER_IP_ANALYZER_H__
#define __LINUX_SNIFFER_IP_ANALYZER_H__

#include <utility>
#include "Analyzer.h"


namespace LinuxSniffer {

class TransportProtocolFrame;

class IpAnalyzer :
public Analyzer
{
public:
virtual ~IpAnalyzer() { }
virtual void analyze(const uint8_t * frame, size_t frame_size);

private:
size_t tryEthV2Analyze(const uint8_t * frame, size_t frame_size);
std::pair<size_t, uint8_t> tryIpV4Analyze(const uint8_t * frame, ize_t frame_size);
size_t tryTcpAnalyze(const uint8_t * frame, size_t frame_size);
size_t tryUdpAnalyze(const uint8_t * frame, size_t frame_size);
void printTransportProtocolFrame(const TransportProtocolFrame & frame) const;
}; // class IpAnalyzer


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_IP_ANALYZER_H__
#include <iostream>
#include "IpAnalyzer.h"
#include "EthernetFrameV2.h"
#include "IpFrameV4.h"
#include "TcpFrame.h"
#include "UdpFrame.h"

using namespace LinuxSniffer;

void IpAnalyzer::analyze(const uint8_t * frame, size_t frame_size)
{
const size_t ip_offset = tryEthV2Analyze(frame, frame_size);
if(0 == ip_offset)
return;

const uint8_t * ip_frame = &frame[ip_offset];
const size_t ip_frame_size = frame_size - ip_offset;
std::pair<size_t, uint8_t> ip_analyze_result =
tryIpV4Analyze(ip_frame, ip_frame_size);
if(0 == ip_analyze_result.first)
return;


const uint8_t * transport_frame = ip_frame + ip_analyze_result.first;
const size_t transport_frame_size = ip_frame_size - ip_analyze_result.first;
size_t application_protocol_data_offset;
switch(ip_analyze_result.second)
{
case TcpFrame::m_protocol_id:
application_protocol_data_offset = tryTcpAnalyze(
transport_frame, transport_frame_size);
break;
case UdpFrame::m_protocol_id:
application_protocol_data_offset = tryUdpAnalyze(
transport_frame, transport_frame_size);
break;
default:
std::cout << "======= Unsupported transport protocol ======n";
}

std::cout << std::endl;
}

size_t IpAnalyzer::tryEthV2Analyze(const uint8_t * frame, size_t frame_size)
{
EthernetFrameV2 eth_frame;
if(!eth_frame.init(frame, frame_size))
return 0;

std::cout << "====== " << eth_frame.getName() << " ======n" <<
"Source MAC Address: " << eth_frame.getSourceMacAddress().toString() <<
std::endl << "Destination MAC Address: " <<
eth_frame.getDestinationMacAddress().toString() << std::endl;

return eth_frame.getDataOffset();
}

std::pair<size_t, uint8_t> IpAnalyzer::tryIpV4Analyze(const uint8_t * frame,
size_t frame_size)
{
IpFrameV4 ip_frame;
if(!ip_frame.init(frame, frame_size))
return std::make_pair(0, 0);

std::cout << "====== " << ip_frame.getName() << " ======n" <<
"Source IP Address: " << ip_frame.getSourceAddress() << std::endl <<
"Destination IP Address: " << ip_frame.getDestinationAddress() <<
std::endl <<
"Header Length: " << std::dec <<
static_cast<uint32_t>(ip_frame.getHeaderLength()) << std::endl <<
"Type Of Service: " <<
static_cast<uint32_t>(ip_frame.getTypeOfService())<< std::endl <<
"Package Size: "<< ip_frame.getPackageSize() << std::endl <<
"Identification: " << ip_frame.getId() << std::endl <<
"Flags: " << static_cast<uint32_t>(ip_frame.getFlags())<< std::endl <<
"Fragmentation Offset: " << ip_frame.getFragmintationOffset() <<
std::endl <<
"Time To Live: " << static_cast<uint32_t>(ip_frame.getTimeToLife()) <<
std::endl <<
"Transport Protocol ID: " <<
static_cast<uint32_t>(ip_frame.getProtocolId()) << std::endl <<
"CRC-16 Header CheckSum:" << std::hex << ip_frame.getHeaderCheckSum() <<
std::endl;
return std::make_pair(ip_frame.getDataOffset(), ip_frame.getProtocolId());
}

size_t IpAnalyzer::tryTcpAnalyze(const uint8_t * frame, size_t frame_size)
{
TcpFrame tcp_frame;
if(!tcp_frame.init(frame, frame_size))
return 0;

printTransportProtocolFrame(tcp_frame);

std::cout << std::dec <<
"Sequence Number: " << tcp_frame.getSequenceNumber() << std::endl <<
"Ascknowledgment Number: " << tcp_frame.getAscknowledgmentNumber() <<
std::endl <<
"Window Size: " << tcp_frame.getWindowSize() << std::endl <<
"Urgent Pointer: " << tcp_frame.getUrgentPtr() << std::endl <<
"Flags:n" <<
" FIN: " << std::boolalpha << tcp_frame.isFinFlagSet() << std::endl <<
" SYN: " << tcp_frame.isSynFlagSet() << std::endl <<
" RST: " << tcp_frame.isRstFlagSet() << std::endl <<
" PSH: " << tcp_frame.isPshFlagSet() << std::endl <<
" ACK: " << tcp_frame.isAckFlagSet() << std::endl <<
" URG: " << tcp_frame.isUrgFlagSet() << std::endl <<
"CheckSum: " << std::hex << tcp_frame.getCheckSum() << std::endl;

return tcp_frame.getDataOffset();
}

size_t IpAnalyzer::tryUdpAnalyze(const uint8_t * frame, size_t frame_size)
{
UdpFrame udp_frame;
if(!udp_frame.init(frame, frame_size))
return 0;

printTransportProtocolFrame(udp_frame);
std::cout << std::dec <<
"Datagram Length: " << std::dec << udp_frame.getDatagramLength() <<
std::endl <<
"Datagram CheckSum: " << std::hex << udp_frame.getCheckSum() <<
std::endl;
return udp_frame.getDataOffset();
}

void IpAnalyzer::printTransportProtocolFrame(
const TransportProtocolFrame & frame) const
{
std::cout << "====== " << frame.getName() << " ======n" <<
std::dec << "Source Port: " << frame.getSourcePort() << std::endl <<
"Destination Port: " << frame.getDestinationPort() <<
std::endl;
}
Всё очень просто. Пытаемся преобразовать полученный фрейм в фрейм протокола Ethernet версии 2. Затем, используя полученное смещение данных, получаем фрейм IP. Проанализировав номер протокола, пытаемся получить данные либо о TCP, либо о UDP фрейме. Попутно выводим всю полученную информацию на консоль. В конце анализа у нас остаётся переменная, хранящая смещение данных прикладного протокола, которую Вы можете применить для дальнейшего разворачивания стека.

Заключение

Полученную программу нельзя запустить от пользователя, чей UID не равен нулю. Это и естественно, ведь мы слишком много себе позволили.
Исходные тексты программы можно скачать здесь.

Многоинтерфейсное приложение

Заголовок темы может звучать, для Вас, странно, но довольно часто требуется, чтобы приложение имело сразу несколько вариантов интерфейса. Например, GUI, CLI и/или web. В этой статье пойдёт речь о построении такого приложения с использованием паттерна Observer.
По традиции, я буду вести рассказ на примере. Пример, как всегда, высосан из пальца и мало похож на реальное приложение.
Я уже сказал, что использовать мы будем паттерн Observer, но кроме него, нам понадобится ещё и паттерн MVC (Model-View-Controller). MVC состоит из трёх частей: модель, контроллер и представление. Модель - это те данные, с которыми мы работаем. Контроллер определяет способ работы с данными. Представление - это, не что иное, как интерфейс приложения, с которым взаимодействует пользователь. Если данные достаточно просты, то, часто, модель и контроллер объединяют в одном объекте. В своём примере, я именно так и поступил.
На следующей диаграмме показана упрощённая схема приложения.
Из диаграммы видно, что мы имеем два подприложения: консольное и оконное. MyObject, в данном примере, является как моделью, так и контроллером. Объект класса MyObject содержит список наблюдателей и оповещает их о смене состояние через интерфейс MyObjectObserver.
Предполагается, что мы имеем один объект класса MyObject разделённый между двумя приложениями. Когда одно из приложений меняет состояние объекта, второе приложение реагирует на это и меняет своё отображение.
Думаю, что с теорией всё ясно, давайте посмотрим на код.
package test.core;

import java.util.HashSet;

public class MyObject
{

State state = State.State1;
HashSet<MyObjectObserver> observers = new HashSet<MyObjectObserver>();

public static enum State
{
State1,
State2,
State3
}

public synchronized void addObserver(MyObjectObserver observer)
{
if(observer != null)
observers.add(observer);
}

public synchronized void removeObserver(MyObjectObserver observer)
{
observers.remove(observer);
}

public synchronized void setState(State newState)
{
if(state != newState)
{
State oldState = state;
state = newState;
for(MyObjectObserver observer : observers)
{
observer.onStateChanged(oldState, newState);
}
}
}

public synchronized State getState()
{
return state;
}
}
Так как класс MyObject выполняет роль контроллера в многопоточном приложении, то некоторые методы в нём объявлены  как synchronized.
Далее, интерфейс наблюдателя
package test.core;

public interface MyObjectObserver
{

public void onStateChanged(MyObject.State oldState, MyObject.State newState);
}
Каждое подприложение реализует интерфейс Application
package test;

import test.core.MyObject;

public interface Application
{

public void run(MyObject object);
}
Приложение с консольным интерфейсом могло бы выглядеть следующим образом
package test.cli;

import test.Application;
import test.core.MyObject;
import test.core.MyObjectObserver;
import java.util.Scanner;

public class CliApplication
implements Application, MyObjectObserver
{

private boolean needPrompt = true;

@Override
public void run(MyObject object)
{
Scanner stdin = new Scanner(System.in);
object.addObserver(this);
while(true)
{
showPrompt();
String input = stdin.nextLine().trim();
if(input.equals("q")) break;
int number;
try
{
number = Integer.parseInt(input);
}
catch(NumberFormatException e)
{
System.out.println(String.format("\"%s\" is not number", input));
needPrompt = true;
continue;
}
MyObject.State state;
switch(number)
{
case 1:
state = MyObject.State.State1;
break;
case 2:
state = MyObject.State.State2;
break;
case 3:
state = MyObject.State.State3;
break;
default:
System.out.println(
String.format("\"%d\" is wrong number", number));
needPrompt = true;
continue;
}
object.setState(state);
}
object.removeObserver(this);
}

void showPrompt()
{
if(needPrompt)
{
System.out.print("Enter state number or 'q' for exit:\n" +
" 1: State1\n" +
" 2: State2\n" +
" 3: State3\n: ");
}
needPrompt = false;
}

@Override
public void onStateChanged(MyObject.State oldState, MyObject.State newState)
{
System.out.println(String.format(
"\nObject state was changed from \"%s\" to \"%s\"",
oldState.toString(), newState.toString()));
needPrompt = true;
showPrompt();
}
}
Пользователю предлагается ввести номер состояния, после чего оно меняется в объекте. Кроме того, приложение следит за своим объектом и выводит сообщения о том, что его состояние меняется.
Оконное приложение требует двух классов: приложение, наследник от интерфейса Application и, собственно, окно. Класс GuiApplication является всего лишь средством запуска окна.
package test.gui;

import test.Application;
import test.core.MyObject;


public class GuiApplication
implements Application
{

@Override
public void run(MyObject object)
{
MainWindow window = new MainWindow(object);
window.setDefaultCloseOperation(MainWindow.EXIT_ON_CLOSE);
window.setVisible(true);
}
}
Объект класса MyObject транзитом проходит через класс GuiApplication в класс MainWindow.
package test.gui;

import test.core.MyObject;
import test.core.MyObjectObserver;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class MainWindow
extends JFrame
implements MyObjectObserver
{

private JComboBox comboBox;
private MyObject object;

class ComboBoxListener
implements ActionListener
{

@Override
public void actionPerformed(ActionEvent e)
{
MyObject.State state = (MyObject.State)comboBox.getSelectedItem();
if(state != null)
object.setState(state);
}
}

class MainWindowListener
extends WindowAdapter
{

@Override
public void windowOpened(WindowEvent e)
{
object.addObserver(MainWindow.this);
}

@Override
public void windowClosed(WindowEvent e)
{
object.removeObserver(MainWindow.this);
}
}

public MainWindow(MyObject object)
{
this.object = object;
comboBox = new JComboBox(MyObject.State.values());
comboBox.setSelectedItem(object.getState());
comboBox.addActionListener(new ComboBoxListener());
add(comboBox);
addWindowListener(new MainWindowListener());
setMinimumSize(new Dimension(200, 50));
pack();
}

@Override
public void onStateChanged(MyObject.State oldState, MyObject.State newState)
{
comboBox.setSelectedItem(newState);
}
}
Каждое подприложение запускается в отдельном потоке методом main.
package test;

import test.cli.CliApplication;
import test.core.MyObject;
import test.gui.GuiApplication;

class ApplicationRunner
implements Runnable
{

private Application application;
private MyObject object;

public ApplicationRunner(Application application, MyObject object)
{
this.application = application;
this.object = object;
}

@Override
public void run()
{
application.run(object);
}
}

public class Main
{

static MyObject object = new MyObject();

static Application[] getApplications()
{
return new Application[]
{
new CliApplication(),
new GuiApplication()
};
}

public static void main(String[] args)
{
for(Application app : getApplications())
{
ApplicationRunner runner = new ApplicationRunner(app, object);
Thread thread = new Thread(runner);
thread.start();
}
}
}
То, что в итоге получилось, можно увидеть в ролике ниже.

Спецификация исключений: друг или враг?

Исторически сложилось, что разные языки программирования, поддерживающие работу с исключениями, по-разному относятся к спецификации исключений. В Java спецификации обязательны и контролируются статически, в C# и Python их вообще нет, а в C++ этот вопрос является одним из самых "сырых" мест.
В двух словах о том, что такое спецификация исключений, для тех, кто не сталкивался с этим понятием. Спецификация исключений - это явное описание тех типов исключений, которые могут быть сгенерированы некой функцией. В java, языке, который поддерживает наилучшим образом спецификацию исключений, это выглядит так
void funct() throw MyException
{
throw new MyException();
}
MyException - это единственно возможный тип исключений, который может покинуть метод. При спецификации можно указать несколько типов исключений.

C++

С самого начала, идея спецификации исключений была чужда языку C++. Язык унаследовал миллиарды строк кода на языке C и, к тому времени, уже были написаны миллионы строк кода на C++, в которых не было ничего о спецификации исключений. Тем не менее, в C++ была добавлена возможность спецификаций. В своей книге "Дизайн и эволюция C++" Бьёрн Страуструп пишет о том, что спецификации, изначально могли контролироваться только во время исполнения, но, позже, были добавлены некоторые возможности для статического контроля, но, как мы увидим, этого не достаточно. В том же параграфе, Бьёрн приводит пример, который я сейчас Вам продемонстрирую. Итак, предположим, что у нас есть библиотека, написанная на языке C++, вот заголовок этой библиотеки
#ifndef SHAREDCPP_H
#define SHAREDCPP_H

#ifdef __cplusplus
extern "C" {
#endif

void foo();

#ifdef __cplusplus
} // extern "C"
#endif

#endif // SHAREDCPP_H
И её реализация
#include <iostream>
#include <string>

extern "C" {

void foo()
{
std::cout << "Throw exceptionn";
throw std::string("test exception");
}

}
Я назвал эту библиотеку libsharedcpp. Я использую ОС Linux, поэтому я не писал конструкции типа __declspec(dllexport). Если Вы используете ОС MS Windows, то для компиляции примеров, Вам нужно будет добавить эти конструкции (думаю, не нужно Вас учить это делать). Для компиляции, я буду использовать компиляторы из набора GCC. Собираем эту библиотеку командой
g++ -shared -fPIC sharedcpp.cpp -o libsharedcpp.so
Теперь создадим библиотеку libsharedc на языке C со следующим кодом
#ifndef SHAREDC_H
#define SHAREDC_H

#ifdef __cplusplus
extern "C" {
#endif

void bar();

#ifdef __cplusplus
} // extern "C"
#endif

#endif // SHAREDC_H
#include "sharedc.h"
#include "sharedcpp.h"

void bar()
{
foo();
}
Как видно, библиотека libsharedc использует функцию из библиотеки libsharedcpp, поэтому, при компиляции, надо указать этот факт
gcc -shared -fPIC sharedc.c -lsharedcpp -L. -o libsharedc.so
Теперь создадим исполняемый модуль на языке C++.
#include <string>
#include <iostream>
#include "sharedc.h"

int main()
{
try
{
bar();
}
catch(const std::string & str)
{
std::cout << str << std::endl;
}
return 0;
}
Если Вы используете UNIX-like ОС, то, перед компиляцией программы, Вам следует либо поместить собранные библиотеки в место, где Ваша ОС их найдёт, например в /usr/lib, либо добавить в путь для поиска текущий каталог командой
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:`pwd`
Далее собираем программу командой
g++ program.cpp -lsharedc -L. -o program
Запустим программу
$ ./program 
Throw exception
test exception
Итак, что же я хотел продемонстрировать этой программой. Прежде всего, обратите внимание, на то, что исключение прошло через два скомпилированных модуля. Мало того, один из скомпилированных модулей написан на языке C, который знать не знает ни о каких исключениях. Из этого уже можно сделать вывод о том, что указать спецификацию исключений для функции bar невозможно.
Теперь, давайте дополним программу таким образом
#include <string>
#include <exception>
#include <iostream>
#include "sharedc.h"

void test() throw(std::exception)
{
bar();
}

int main()
{
try
{
test();
}
catch(const std::exception & err)
{
}
return 0;
}
Я добавил функцию test, которая специфицирует исключения только стандартного типа - std::exception. Статические анализаторы не смогут определить, что функция bar генерирует исключение типа std::string, и компиляция пройдёт успешно. Но при выполнении нас ждёт ошибка.
$ ./program 
Throw exception
terminate called after throwing an instance of 'std::string'
Аварийный останов
И даже добавление catch блока, обрабатывающего std::string, не устранит проблему , так как она возникает до момента обработки исключения, а именно - в момент выхода исключения из функции.
try
{
test();
}
catch(const std::exception & err)
{
}
catch(const std::string & err)
{
}
Таким образом, я продемонстрировал ситуацию, когда пользы от спецификации исключений меньше, чем вреда.

Java

По-другому дела обстоят с Java. В этом языке спецификация исключений контролируется статически, а компиляция в байт-код позволяет выявить все спецификации на этапе компиляции или раньше. В отличие от C++, спецификация исключений в Java носит обязательный характер, что тоже сопряжено с некоторыми трудностями.
Предположим, что у нас в программе, есть базовый класс
package test;

public class MyBase
{

private int value;

public MyBase(int value)
{
this.value = value;
}
}
И от него наследуются множество наследников. Например
public class MyFirst
extends MyBase
{

public MyFirst()
{
super(1);
}
}
И, некоторый наследник содержит в себе данные, исходный код которых, не доступен
package test;

import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.SparseMultigraph;

public class MySecons
extends MyFirst
{

private Graph<Integer, String> graph;

public MySecons()
{
graph = new SparseMultigraph<Integer, String>();
}
}
Внезапно Вам становится необходимо сделать Ваш базовый класс клонируемым и Вы добавляете переопределение метода clone и наследование от интерфейса Cloneable
package test;

public class MyBase
implements Cloneable
{

private int value;

public MyBase(int value)
{
this.value = value;
}

@Override
public Object clone()
{
try
{
MyBase base = (MyBase)super.clone();
base.value = value;
return base;
} catch(CloneNotSupportedException ex)
{
// Этого не должно произойти.
return null;
}
}
}
Я задушил исключение, так как оно не может возникнуть, если класс реализует интерфейс Cloneable. Теперь я должен реализовать метод clone в потомках базового класса. С классом MyFirst проблем нет; он достаточно примитивен, чтобы не писать эту реализацию вообще. Но, вот, класс MySecond принесёт нам не мало хлопот. Склонировать граф из библиотеки JUNG мы не можем, так как он не предоставляет нам такой возможности. После некоторых раздумий, мы можем прийти к выводу, что клонировать граф - действительно не лучшая затея, и мы решаем запретить клонировать объекты класса MySecond. Но мы уже разрешили клонировать базовый класс и всех его детей, поэтому единственным верным решением остаётся выкинуть исключение при попытке вызова метода clone. Но не тут-то было. В java запрещается специфицировать исключения у переопределённых методов таким образом, чтобы это расходилось со спецификацией, определённой в родительском классе (убирать исключения из спецификации можно). И теперь, как ни старайся, ничего с методом clone, дельного не выйдет, либо возвращаем задушенное исключение, либо придумываем другой способ копирования. Я, к слову, смог ввести некое подобие конструкторов копирования из C++. Другой вариант решения проблемы описан в статье "Фабрика клонов".

Итого

Я не могу говорить за большинство языков, которые существуют, но о некоторых сказать кое-что могу. В частности, в языках C# и Python от спецификации исключений отказались вовсе, и правильно сделали, на мой взгляд. Как видно из предыдущих частей статьи: спецификации исключений зачастую оказываются вредными, а в случае с C++, ещё и бесполезными.

Автомонтирование флешек в Linux

Случилось так, что я отказался от полноценных рабочих столов в пользу оконного менеджера fluxbox. Всё меня устраивает, кроме одного - автоматического монтирования флешек в оконном менеджере, естественно, не предусмотрено. Один мой приятель, на своём сайте разместил статью об автомонтировании флешек через правила демона udev. В этой заметке я напишу о том, как я немного расширил правила, приведённые в указной статье.
Всем Вам известно, что накопители в Linux монтируются в существующий каталог. И каждый, кто монтировал флешки вручную, знает, что перед монтированием нужно создать каталог, затем смонтировать, а после размонтирования - удалить.  Альтернативой может служить заранее созданная куча каталогов, но это засоряет файловую систему и, к тому же, не красиво, с эстетической точки зрения.
Для автоматизации указанных действий, мною был написан следующий скрипт на bash.
#!/bin/bash

# $1 - action: mount or umount
# $2 - device name
# $3 - device file

ACTION=$1
MOUNT_ACTION=mount
UMOUNT_ACTION=umount
DEVICE_NAME=$2
DEVICE_FILE=$3
MOUNT_DIR=/mnt/
MOUNT_CMD=mount
UMOUNT_CMD=umount
MOUNT_OPTS="-o rw,umask=0"
SCRIPT_NAME=devmount


function printUsage() {
echo Usage:
echo "$SCRIPT_NAME $MOUNT_ACTION <device name> <device file>"
echo or
echo "$SCRIPT_NAME $UMOUNT_ACTION <device name>"
}

# $1 - error message
function errorMessage() {
echo -e 'E[31m'<$SCRIPT_NAME> Error: $1
tput sgr0
}

# $1 - warning message
function warningMessage() {
echo -e 'E[33m'<$SCRIPT_NAME> Warning: $1
tput sgr0
}

# $1 - message
function logMessage() {
echo -e 'E[32m'<$SCRIPT_NAME>: $1
tput sgr0
}

# $1 - error message
function errorExit() {
errorMessage "$1"
echo
printUsage
echo
exit 1
}

# $1 - error message
function crashExit() {
errorMessage "$1"
echo
exit 2
}

# $1 - directory path
# returns 1 - true, 0 - false
function isDirectoryEmpty() {
DIR_CONTENT=`ls -A $1 3> /dev/null`
NOT_EMPTY=X$DIR_CONTENT
if [ "$NOT_EMPTY" == "X" ]; then
return 1
else
return 0
fi
}

# $1 - directory name
function deleteDirectory() {
rmdir $1
if [ ! 0 == $? ]; then
warningMessage "couldn't delete $1"
return 1
fi
return 0
}

function mountDevice() {
if [ -d $MOUNT_DIR$DEVICE_NAME ]; then
warningMessage "$MOUNT_DIR$DEVICE_NAME already exist"
isDirectoryEmpty $MOUNT_DIR$DEVICE_NAME
if [ $? == 0 ]; then
crashExit "$MOUNT_DIR$DEVICE_NAME is not empty"
fi
else
mkdir $MOUNT_DIR$DEVICE_NAME
if [ ! 0 == $? ]; then
crashExit "couldn't create $MOUNT_DIR$DEVICE_NAME"
fi
fi
$MOUNT_CMD $DEVICE_FILE $MOUNT_DIR$DEVICE_NAME $MOUNT_OPTS
if [ ! 0 == $? ]; then
deleteDirectory $MOUNT_DIR$DEVICE_NAME
crashExit "couldn't mount device"
else
logMessage "mount success"
fi
}

function umountDevice() {
$UMOUNT_CMD $MOUNT_DIR$DEVICE_NAME
if [ $? == 0 ]; then
logMessage "unmount success"
deleteDirectory $MOUNT_DIR$DEVICE_NAME
else
crashExit "unmount failure"
fi
}

case $ACTION in
$MOUNT_ACTION)
mountDevice
;;

$UMOUNT_ACTION)
umountDevice
;;

*)
errorExit "unknown parametr "$ACTION
;;
esac
Этот скрипт создаёт в каталоге /mnt каталог с переданным именем и монтирует в него указанное устройство. Если каталог существует, скрипт выдаст предупреждение, а если существующий каталог не пуст - ошибку.
Этот скрипт я и скормил udev`у, как сказано в статье.
SUBSYSTEM=="block", KERNEL=="sd[c-z][0-9]", ACTION=="add", RUN+="/usr/local/bin/devmount mount flash-%k /dev/%k"
SUBSYSTEM=="block", KERNEL=="sd[c-z][0-9]", ACTION=="remove", RUN+="/usr/local/bin/devmount umount flash-%k"
Теперь при вставке флешки, она у меня автоматически монтируется, а при вытаскивании - размонтируется. Кроме того, скрипт позволяет проводить эти операции вручную, что необходимо, если Вы не уверены в безопасности извлечения флешки в данный момент.

C#, кто он?

Не так давно я начал осваивать нынче сверхпопулярный, среди windows программистов, язык программирования C#. Осваивать я его начал, естественно, не просто так, а с корыстными целями, но об этом я рассказывать не буду. Нельзя сказать, что до этого я его в глаза не видел, писал я маленький тестик. Теперь же, после недельного знакомства с этим языком, я хочу поделиться своими впечатлениями.Сразу хочу предупредить, что всё, написанное в этой статье, является моим субъективным мнением.

Платформа .NET

Для работы программ, созданных на платформе .NET, то есть всех программ на языке C#, требуется виртуальная машина .NET. Платформа .NET должна была создать конкуренцию платформе Java, но разработчики Microsoft остановились на поддержке только операционных систем семейства Windows, похоронив тем самым кроссплатформенность. Однако компания Novell разрабатывает альтернативную кроссплатформенную реализацию .NET и C#. Этот проект получил название Mono.
Даже несмотря на то, что Microsoft предоставила Novell все права, проект Mono остаётся догоняющим. По этой причине проектирование Mono сильно отстаёт. Например, WPF в проекте Mono даже и не собираются реализовывать.Исходя из вышесказанного, нетрудно предположить, что мы можем либо писать только под Windows, либо принять ограничения Mono и жить в их рамках.
Но не всё так плохо, того, что реализовано проектом Mono достаточно для создания полноценных приложений. Открытым остаётся вопрос, а согласятся ли компании, создающие ПО на C#, на такие ограничения?
Проект .NET создавался сразу с несколькими целями, давайте разберёмся, что это за цели и каков результат.

Замена устаревшим технологиям COM и ActiveX

Позволю себе напомнить о том, что такое COM и ActiveX. Технология COM служит для межпроцессного, внутрепроцессного и сетевого взаимодействия объектно-ориентированных модулей. Взаимодействие строится на понятиях виртуальной таблицы (VTBL) и интерфейса класса. Приложение, которое хочет воспользоваться объектом другого модуля, просит этот модуль создать ему экземпляр какого-либо класса и, получив его, инициализирует специальным образом, заранее известный, интерфейс. Системой маршалинга COM вызовы виртуальных методов интерфейса преобразуются в корректные вызовы и все остаются довольны. Технология ActiveX - это надстройка над COM, которая служит для работы с графическим интерфейсом, обеспечивая такие понятия как компонент, узел компонента и обмен сообщениями.
Итак, как же технология .NET решает те же проблемы, что и COM. Во-первых .NET вводит понятие сборки (assembly), которая является её функциональной частью. Сборка представляет из себя бинарный модуль, хранящий полную информацию о том, что он содержит: пространства имён, зависимости, типы, методы итд. Любая сущность, объявленная в сборке как public автоматически становится доступной из других сборок. При том все события и делегаты остаются в рабочем состоянии, а виртуальная машина .NET обеспечивает, как и в случае с COM, все межпроцессные, внутрипроцессные и сетевые взаимодействия. Таким образом, отпадает необходимость в виртуальных таблицах, а значит и работа с графическими компонентами становится неотличимой от простого взаимодействия.
С первой задачей .NET справился, но на этом создатели не остановились. Раз уж нам известно всё о типах, находящихся внутри сборки, то пользователь этой сборки может расширять все типы, унаследовав их. Технология COM не позволяет наследоваться от классов из скомпилированного модуля.

Мультиязычная поддержка

В рамках платформы .NET реализована технология под названием CLR (Common Language Runtime). Это технология, позволяющая работать с платформой .NET из других языков программирования. Также, предоставляется возможность взаимодействия сборок, написанных на разных языках.
В рамках технологии CLR реализовано множество языков, как самой корпорацией Microsoft, так и другими компаниями. Среди этих языков, такие языки как C#, Visual Basic.NET, C++.NET, F#, IronPython, Delphi, Nemerle и многие другие. Так как я не работал со многими из тех языков, что значатся в списке, поддерживающих CLR, я скажу только о C++.NET. Достаточно справедливо будет отметить, что разработчики компании Microsoft внесли такие изменения в язык C++, что работать с ним стало просто не приятно. Попробовав поработать с C++.NET, я понял, что не выдержу издевательств над своим любимым языком и поспешно удалил тестовый проект. Больше я к нему не возвращался. Получается, что не CLR поддерживает язык, а язык изменили для CLR (то есть подвинули гору к Магомету). Из этого можно сделать вывод, что некоторые языки, как минимум C++ и Visual Basic подгонялись под платформу .NET только для того, чтобы создать изначальный пул языков CLR. Надеюсь, что с другими языками, которые создавались не в такой спешке, подобной истории не случилось.
Подводя итоги для этой цели, можно сказать, что платформа .NET не полностью справилась с поставленной задачей, так как я вынужден использовать не C++, а его модификацию только потому, что разработчикам .NET так захотелось. Но нельзя отрицать тот факт, что поддерживаемых языков, действительно, много, и их количество возрастает.

Язык C#

Давайте же перейдём к виновнику сей статьи - к языку C#. Язык C# - это первый из языков CLR, который создавался с нуля и специально для платформы .NET, поэтому он должен отражать все намерения разработчиков. Язык разрабатывался под сильным влиянием языка Java и должен был стать его конкурентом. В этой статье я не буду писать учебник по этому языку, о нём написано много книг и на msdn есть руководство. Здесь я расскажу о тех конструкциях языка, которые мне понравились и о тех, которые мне не понравились.

Наследование

Как и в Java, в C# запрещено множественное наследование классов, но разрешено множественное наследование интерфейсов. Хорошо это или плохо? Споры по этому поводу не умолкают с тех пор, как придумали наследование. Моё мнение - плохо. Ведь у каждого программиста есть своя собственная голова на плечах и он ею может подумать и сам решить все проблемы, которые несёт неверное использование множественного наследования, а современные компиляторы способны подсказать ему, что в коде возникла путаница. А вот пользы от множественного наследования гораздо больше, чем вреда. Если вспомнить истоки ООП, то основной причиной создания этого подхода было определение типов, соответствующих реальным объектам и предметам. А коли это так, то "диван-кровать" и "компьютер моноблок" являются примерами таких реальных объектов, которые в ООП было бы правильно описывать множественным наследованием (от "дивана" и "кровати" в первом случае и от "системный блок" и "монитор" - во втором).

Упаковка объектов

В C# все типы неявно наследуются от типа System.Object или просто object. Тип object используется для, так называемой, упаковки объектов. Упаковка в синтаксическом виде - это ни что иное, как приведение объекта к типу object
string str = "Hello";
object obj = (object)str;
Надо отметить, что не смотря на все советы Скотта Мэйерса, приведение типов в C# осталось в стиле языка Си.
Итак, что же особенного в данной ситуации. Упаковка объектов в платформе .NET используется повсеместно, что может привести к путанице. Конечно, при неверном приведении среда исполнения выдаст исключение, но мне представляется, что runtime - это уже поздно. Ловить исключения при каждом присвоении смешно, согласитесь.
В качестве примера использования упаковки можно привести операцию клонирования. В интерфейсе IClonable объявлен метод Clone, который возвращает тип object. Служит этот метод только одной цели - создание копии объекта. Давайте посмотрим, на то, как выглядит определение и использование клонирования.
class Sheep : ICloneable
{
private string theName;
private int theAge;

public Sheep(string aName, int anAge)
{
theName = aName;
theAge = anAge;
}

public object Clone()
{
return new Sheep(theName, theAge);
}
}

class Program
{
static void Main(string[] args)
{
Sheep dolly = new Sheep("Dolly", 1);
Sheep dolly2 = (Sheep)dolly.Clone();
}
}
Представляется мне это всё, мягко говоря, не удобным и плохо читабельным.

Копирование объектов

Раз уж я заговорил о клонировании, то добью эту тему до конца. В C# все типы данных поделены на два вида: структурные и ссылочные. Структурные - это те типы, которые мы, C++ программисты, привыкли называть POD (Primitive Old Data) типами (int, float, bool, ...), перчисления и структуры - которые в C# обрели новую жизнь. Все структурные типы являются наследниками типа System.ValueType и память для их объектов всегда выделяется в стеке. Все остальные типы, то бишь классы и интерфейсы, называются ссылочными и наследуются, как уже было сказано, от System.Object или любого производного, кроме System.ValueType. Память для объектов ссылочных типов выделяется в управляемой куче.
До сих пор всё выглядит сносно, но теперь давайте представим всё на деле. Допустим, у нас есть некая структура, содержащая какие-то данные.
struct Data
{
public int theData;

public Data(int aData)
{
theData = aData;
}
}

class Program
{
static void PrintIncrementedData(Data data)
{
Console.Write("Incremental data: {0}n", ++data.theData);
}

static void Main(string[] args)
{
Data data = new Data(50);
PrintIncrementedData(data);
PrintIncrementedData(data);
}
}
Данная программа не выглядит реально, но тем не менее, ведёт себя так, как мне хочется, а именно, выводит такой результат
Incremental data: 51
Incremental data: 51
Теперь давайте представим, что тот, кто сопровождает эту структуру неожиданно переименует её в класс, ну, допустим, ему понадобится конструктор по умолчанию, который запрещено создавать в структурах C#. Тогда вывод нашей программы внезапно приобретает другой вид
Incremental data: 51
Incremental data: 52
Одно слово и наша программа перестала работать так, как положено, но продолжила компилироваться без ошибок и предупреждений. Всё это произошло по одной причине: структурные типы при присваивании и передачи в качестве параметров копируются по значению, а ссылочные, как видно из названия, - копируют только ссылку на объект в памяти.
Не знаю, какой тайный смысл разработчики C# вложили в структуры, но трудно обнаруживаемые ошибки, связанные с их использованием представляются более, чем вероятными.

Константность

Здесь я хотел бы поговорить о таких понятиях, которые с C++ называются константными методами и константными возвращаемыми значениями. Если быть точнее, то возвращаемое значение просто имеет константный тип. Но формулировки не столь важны, в C# нет ни того, ни другого. Методы в C# всегда возвращают ссылку на объет ссылочного типа или копию объекта структурного типа. Чем это чревато. Давайте посмотрим на пример из книги "C# и платформа .NET" Эндрю Троелсена, описанный в третьей главе, в разделе, посвящённом инкапсуляции. Так как этот пример там размазан по нескольким параграфам, я соберу его и немного дополню, смысл от этого не изменится.
class FullName
{
public string firstName;
public string secondName;

public FullName(string aFirstName, string aSecondName)
{
firstName = aFirstName;
secondName = aSecondName;
}
}

class Emploee
{
private FullName theFullName;

public Emploee(FullName aFullName)
{
theFullName = aFullName;
}

public FullName Name
{
get { return theFullName; }
}
}


class Program
{
static void Main(string[] args)
{
Emploee emploee = new Emploee(new FullName("William", "Gates"));
FullName name = emploee.Name;
name.firstName = "Linus";
name.secondName = "Torvalds";
Console.Write("1: {0} {1}n", emploee.Name.firstName,
emploee.Name.secondName);
Console.Write("2: {0} {1}n", name.firstName, name.secondName);
}
}
Не смотря на то, что автор утверждает, что свойство Name доступно только для чтения, я не применяя никаких усилий изменил значение приватной переменной, в чём легко убедиться, скомпилировав и запустив программу.
1: Linus Torvalds
2: Linus Torvalds
Стоит ли упоминать, что подобное поведение может привести к, довольно плачевным, последствиям. Для того, чтобы избежать такой ситуации не остаётся ничего другого, кроме как скопировать объект FullName перед возвратом из свойства.
public FullName Name
{
get { return new FullName(theFullName.firstName,
theFullName.secondName); }
}
Естественно, такой подход гораздо более затратный, нежели возврат константной ссылки в C++.

Делегаты

Увидев в первый раз концепцию делегатов, я пришёл в восторг. Это, действительно, мощная и простая технология позволяет создавать такие конструкции, для которых в C++ применялись колбеки, функторы и обсерверы. Если вкратце, то делегат - это особый вид функции, который объявляется в одном классе, а реализован может быть в другом. Кроме того, один делегат может быть реализован много раз и, при вызове, такого делегата, будут вызваны все его реализации. Давайте посмотрим на примере.
class People
{
public delegate void SpeakDelegate();
public SpeakDelegate Speak;
}

class Man
{
private string theName;

public Man(string aName)
{
theName = aName;
}

public void Speak()
{
Console.Write("Hello, my name is {0}n", theName);
}
}

class Program
{
static void Main(string[] args)
{
Man jhon = new Man("Jhon");
Man alice = new Man("Alice");
People people = new People();
people.Speak +=
new People.SpeakDelegate(jhon.Speak) +
new People.SpeakDelegate(alice.Speak);
people.Speak();
}
}
При вызове people.Speak будут вызваны методы Speak двух объектов - jhon и alice, а результат будет таким
Hello, my name is Jhon
Hello, my name is Alice
Этот же механизм (с небольшими дополнениями) используется при отправке сообщений. Но об этом я говорить не буду.
В общем-то, всё это выглядит очень красиво, но стоит помнить одну деталь - вызов делегата, у которого нет подписчиков приведёт к генерации исключения и крешу программы, если его не обработать. Следовательно, при вызове делегата мы должны каждый раз проверять, инициализирован ли он, благо не инициализированные делегаты всегда равны null. На первый взгляд, может показаться, что разработчики могли бы гарантировать безопасный вызов делегатов. Но нет, делегаты могут возвращать значение, а какое значение может вернуть то, что не было вызвано? Раз уж я об этом заговорил, то возвращаемым значением делегата - это значение, которое вернёт последняя вызванная делегатом функция.

Оператор foreach

Конечно же, каждый программист на C++ мечтает увидеть в своём любимом языке оператор обхода коллекций foreach. В языке C# он существует. При том синтаксис этого оператора весьма удобен, а работать с ним одно удовольствие.
foreach(ТипЭлементаКоллекции ссылка in коллекция)
{
}
"ссылка" принимает значение каждого из элементов и является именно ссылкой, а не копией. Чтобы Ваш класс мог работать как коллекция в операторе foreach, он должен реализовать интерфейс IEnumerable, в котором всего один метод - GetEnumerator. Вот если Вы используете не встроенные коллекции, то Вам придётся реализовать и интерфейс IEnumerator.

Коллекции

Список всевозможных коллекций в .NET достаточно большой, но что более всего меня вводит в недоумение - это то, что большинство из них не являются обобщёнными. Например, класс ArrayList хранит объекты типа object. Как я уже говорил в разделе об упаковке объектов, это может вызвать путаницу. По какой причине часть контейнеров сделали обобщёнными, а часть - хранящими объекты типа object для меня остаётся загадкой.

Разные мелочи

Из того, что я видел только в C#, стоит отметить статические конструкторы. Это особый вид конструкторов, в котором можно инициализировать статические члены класса. Кроме того, статические члены можно инициализировать прямо при объявлении, даже вызовом какой-либо функции.
Не удалось мне найти достойной замены оператору typedef из C++. Можно использовать ключевое слово using, но распространятся такой "typedef" будет только внутри одного модуля.

Итоги

Подводя итоги, могу сказать следующее: C# - довольно неплохой язык, но в некоторых местах, явно, плохо продуманный. Обойти все изъяны можно, но лучше бы разработчики подумали о них во время проектирования.

Автоматизация монтирования NFS каталогов в Linux

Есть у меня сетевое хранилище Thecus N299. Из путей доступа к нему есть протоколы SMB, NFS, FTP и web-интерфейс через HTTP. Так как я пользуюсь, чаще всего, ОС Linux, то протокол SMB является чужеродным и дико тормозящим, но зато он поддерживается любым файловым менеджером. Монтировать каталоги по NFS было очень лениво, поэтому я пользовался, в основном, SMB в ущерб скорости. Надо ли говорить, что работать с файлами через SMB напрямую не умеют почти все программы UNIX. Конечно же, решением (через одно место) могло бы быть монтирование через smbfs, но тут я вспомнил о NFS.
Писать постоянно что-то вроде
sudo mkdir /mnt/video
sudo mount -t nfs 192.168.1.100:/web/raid/video /mnt/video
лениво. И я решил автоматизировать себе этот процесс. После написания скрипта на bash, я подумал, что, может быть, кому-то он тоже будет полезен и решил выложить его сюда. Итак, встерчайте
#!/bin/bash

# Корневой каталог для монтирования.
DIRECTORY="/mnt/thecus/"
# Адрес сервера.
SERVER="192.168.1.100"
# Корневой каталог сервера.
SERVER_ROOT="/web/raid/"
# Каталоги на сервере, теже имена имеют каталоги, в которые они монтируются.
SERVER_DIRECTORIES=("video" "documents" "development" "projects" "soft")
# Параметр для монтирования всех каталогов сервера.
ALL="all"
# Имя команды скрипта.
COMMAND="thecus"
# Параметр скрипта для монтирования.
MOUNT="mount"
# Параметр скрипта для размонтирования.
UMOUNT="umount"

#####################################################################
#####################################################################


# $1 - имя каталога на nfs сервере.
# Возвращает 0 в случае успеха.
function doMount() {
if [ ! -d $DIRECTORY ]; then
mkdir $DIRECTORY
if [ ! 0 == $? ]; then
echo $COMMAND: Не удалось создать каталог $DIRECTORY
return 1
fi
fi

if [ ! -d $DIRECTORY$1 ]; then
mkdir $DIRECTORY$1
if [ ! 0 == $? ]; then
echo $COMMAND: Не удалось создать каталог $DIRECTORY$1
return 1
fi
fi

mount -t nfs $SERVER:$SERVER_ROOT$1 $DIRECTORY$1
return $?
}

# $1 - имя каталога в $DIRECTORY для размонтирования.
# Возвращает 0 в случае успеха.
function doUmount() {
umount $DIRECTORY$1
return $?
}

# Монтирует все каталоги, игнорируя ошибки.
function mountAll() {
for DIR in ${SERVER_DIRECTORIES[@]}; do
doMount $DIR
done
}

# Размонтирует все каталоги, игнорируя ошибки.
function umountAll() {
for DIR in ${SERVER_DIRECTORIES[@]}; do
doUmount $DIR
done
}

# Проверяет $1 на валидность, в случае успеха возвращает 0.
function checkTargetName() {
for DIR in ${SERVER_DIRECTORIES[@]}; do
if [ $1 == $DIR ]; then
return 0
fi
done
return 1
}

# Печатает справку.
function printHelp() {
echo Использование:
echo -e " $COMMAND $MOUNT имя_каталога"
echo -e " $COMMAND $UMOUNT имя_каталога"
echo -e " $COMMAND $MOUNT $ALL"
echo -e " $COMMAND $UMOUNT $ALL"
echo
echo -e "$MOUNT - смонтировать каталог"
echo -e "$UMOUNT - размонтировать каталог"
echo
echo Допустимые имена каталогов:
for DIR in ${SERVER_DIRECTORIES[@]}; do
echo -e " $DIR"
done
echo
echo -e "$ALL - смонтировать/размонтировать все каталоги"
echo
echo Все каталоги монтируются в $DIRECTORY
}

# Выход с ошибкой
function errorExit() {
echo
printHelp
echo
exit 1
}

#####################################################################
#####################################################################


# Если параметров меньше 2-х, то напечатать справку и выйти.
if [ $# -lt 2 ]; then
errorExit
fi

# Если вторым параметром выбрали $ALL, то действие относится ко всем каталогам.
if [ $2 == $ALL ]; then
case $1 in
$MOUNT)
mountAll
;;
$UMOUNT)
umountAll
;;
*)
errorExit
;;
esac
else
# Проверяем второй параметр на валидность.
checkTargetName $2
if [ ! $? == 0 ]; then
echo $COMMAND: Неверный каталог: $2
errorExit
fi

case $1 in
$MOUNT)
doMount $2
;;
$UMOUNT)
doUmount $2
;;
*)
errorExit
;;
esac
fi
Я сохранил этот скрипт под именем thecus в каталог /usr/local/bin и назначил права на выполнение. Теперь, для монтирования всех моих каталогов на NFS сервере, мне достаточно дать команду
sudo thecus mount all
Из комментов в тексте скрипта ясно,что он делает.

Анализ и оптимизация кода на C++ для Linux

Какие проблемы могут нас подстерегать при разработке программ? Неэффективные алгоритмы, утечки памяти, работа с невалидными указателями и не инициализированными переменными. Даже несмотря на, казалось бы, тщательное написание кода, мы порой делаем ошибки. Человеку свойственно ошибаться, поэтому эта статья посвящена контролю машин над человеком -- машинной проверки кода и исполняемых файлов. Я поделю весь процесс на три части:
  1. Статический анализ исходных текстов;
  2. Проверка утечек памяти;
  3. Нахождение участков кода, требующих неприлично много машинного времени.
В этой статье, говоря о компиляторе, я буду подразумевать g++, входящий в состав GCC (GNU Compiler Collection).

Статический анализ исходных текстов

Этот раздел опирается, во многом, на этот пост на хабре.

g++

Наряду со статическими анализаторами, компилятор g++ может выдать очень много полезной информации. Для того, чтобы добиться от компилятора максимум возмущений, следует добавит несколько опций.
-Wall - включает почти все стандартные предупреждения. Эту опцию нужно ставить всегда и везде, это должно стать Вашим правилом.
-Wextra - сообщит об ошибках в условных операторах, пустые if'ы и сравнение signed с unsigned.
-pedantic - по приказу этой опции компилятор начнёт следовать стандарту ISO C++. Например, запретит тип long long.
-Weffc++ -  эта опция напомнит Вам о некоторых правилах Скотта Майерса, которые Вы все, надеюсь, читали. В частности, это следующие правила из книги "Эффективное использование C++. 50 рекомендаций по улучшению ваших программ и проектов":
  • Правило 11. "Для классов с динамическим выделением памяти объявляйте копирующий конструктор и оператор присваивания".
  • Правило 12. "Предпочитайте инициализацию присваиванию в конструкторах".
  • Правило 14. "Убедитесь, что базовые классы имеют виртуальные деструкторы".
  • Правило 15. "operator= должен возвращать ссылку на *this".
  • Правило 23. "Никогда не пытайтесь вернуть ссылку, когда вы должны вернуть объект".
И несколько правил из книги "Наиболее эффективное использование C++. 35 новых рекомендаций по улучшению ваших программ и проектов":
  • Правило 6. "Различайте префиксную и постфиксную формы операторов инкремента и декремента".
  • Правило 7. "Никогда не перегружайте операторы &&, || и ,".
-Woverloaded-virtual - сообщит о перегрузке виртуальных функций.
-Wctor-dtor-privacy - возмутится, если найдёт у Вас в коде класс с закрытыми конструкторами и деструктором, который нигде не используется.
-Wnon-virtual-dtor - этой опции не нравятся не виртуальные деструкторы.
-Wold-style-cast - поможет избавится от приведений типов в стиле языка C.
-Wconversion -Wsign-conversion - заставят компилятор сказать пару ласковых о неверных приведениях типов, при которых Вы можете лишиться  части своих значений.
-Wunreachable-code - укажет на участки кода, которые никогда не будут выполнены. Эта опция может выдать очень много ворнингов даже в стандартной библиотеке, полезно её включать только при проверке.

Давайте проверим всё вышесказанное на практике. Для этой цели я написал следующий код полный ошибок
#include <iostream>

class CBicycle
{
public:
CBicycle(unsigned short max_speed)
{
m_max_speed = max_speed;
}

unsigned short GetMaxSpeed() const
{
return m_max_speed;
}

private:
short m_max_speed;
};

class CBicyclist
{
public:
CBicyclist(const CBicycle & bicycle)
{
mp_bicycle = new CBicycle(bicycle);
m_speed = 0;
}

unsigned short GetSpeed() const
{
return m_speed;
}

bool Move(short speed)
{
bool result = true;
if(speed <= mp_bicycle->GetMaxSpeed())
{
m_speed = speed;
std::cout << "I'm move with speed of " << m_speed << " km/hn";
}
else
{
result = false;
std::cout << "Sorry, this bicycle can't move with speed of " <<
speed << " km/hn";
}
return result;
}

private:
CBicycle * mp_bicycle;
unsigned short m_speed;
};

int TestMaxSpeed(CBicyclist & bicyclist)
{
int speed = bicyclist.GetSpeed();
int max_speed = speed;
bool loop = true;
do
{
if(bicyclist.Move(max_speed))
{
++max_speed;
}
else
{
loop = false;
--max_speed;
bicyclist.Move(speed);
}
} while(loop);
return max_speed;
}

int main()
{
CBicycle * bicycle = new CBicycle(75);
CBicyclist * bicyclist = new CBicyclist(*bicycle);
bicyclist->Move(600);
std::cout << "Begin max speed testn";
int max_speed = TestMaxSpeed(*bicyclist);
std::cout << "End max speed test. Max speed = " << max_speed << std::endl;
return 0;
}
У нас есть некое подобие классов, класс велосипеда и класс велосипедиста. Велосипед принимает в конструкторе максимальную скорость, а велосипедист - велосипед. Велосипедист может разогнаться на столько быстро, на сколько позволит велосипед или тип unsigned short, с которым у меня в коде связано много ошибок. Класс велосипедиста "забывает" удалить свой велосипед, а функция main "забывает" не только про велосипед, но и про велосипедиста. Задача функции TestMaxSpeed проверить, какая же скорость у велосипеда максимальная, используя для этой цели только велосипедиста. Но и у этой функции не всё в порядке с типами.
Итак, скомпилируем код с вышеуказанными опциями.
$ g++ -Wall -Wextra -pedantic -Weffc++ -Woverloaded-virtual -Wctor-dtor-privacy -Wnon-virtual-dtor -Wold-style-cast -Wconversion -Wsign-conversion -Wunreachable-code -g -O0  -c main.cpp
main.cpp: In constructor ‘CBicycle::CBicycle(short unsigned int)’:
main.cpp:6: warning: ‘CBicycle::m_max_speed’ should be initialized in the member initialization list
main.cpp:8: warning: conversion to ‘short int’ from ‘short unsigned int’ may change the sign of the result
main.cpp: In member function ‘short unsigned int CBicycle::GetMaxSpeed() const’:
main.cpp:13: warning: conversion to ‘short unsigned int’ from ‘short int’ may change the sign of the result
main.cpp: In constructor ‘CBicyclist::CBicyclist(const CBicycle&)’:
main.cpp:23: warning: ‘CBicyclist::mp_bicycle’ should be initialized in the member initialization list
main.cpp:23: warning: ‘CBicyclist::m_speed’ should be initialized in the member initialization list
main.cpp: In member function ‘bool CBicyclist::Move(short int)’:
main.cpp:39: warning: conversion to ‘short unsigned int’ from ‘short int’ may change the sign of the result
main.cpp: In function ‘int TestMaxSpeed(CBicyclist&)’:
main.cpp:63: warning: conversion to ‘short int’ from ‘int’ may alter its value
main.cpp:71: warning: conversion to ‘short int’ from ‘int’ may alter its value
main.cpp: In constructor ‘CBicyclist::CBicyclist(const CBicycle&)’:
main.cpp:25: warning: will never be executed
g++ -Wall -Wextra -pedantic -Weffc++ -Woverloaded-virtual -Wctor-dtor-privacy -Wnon-virtual-dtor -Wold-style-cast -Wconversion -Wsign-conversion -Wunreachable-code -g -O0 *.o -o test
Теперь давайте проанализируем вывод компилятора.
main.cpp: In constructor ‘CBicycle::CBicycle(short unsigned int)’:
main.cpp:6: warning: ‘CBicycle::m_max_speed’ should be initialized in the member initialization list
Нам сообщают, что в конструкторе класса CBicycle мы должны инициализировать член m_speed в списке инициализации, а не в теле конструктора.
main.cpp:8: warning: conversion to ‘short int’ from ‘short unsigned int’ may change the sign of the result
Конструктор CBicycle принимает значение unsigned short и присваивает его, почему-то, переменной типа short. (Это мы забыли написать unsigned в типе переменной-члена m_max_speed).
main.cpp: In member function ‘short unsigned int CBicycle::GetMaxSpeed() const’:
main.cpp:13: warning: conversion to ‘short unsigned int’ from ‘short int’ may change the sign of the result
Теперь мы ещё и возвращаем short от туда, от куда должны вернуть unsigned short.
main.cpp:23: warning: ‘CBicyclist::mp_bicycle’ should be initialized in the member initialization list
main.cpp:23: warning: ‘CBicyclist::m_speed’ should be initialized in the member initialization list
Класс CBicyclist повторил "подвиг" класса CBicycle и инициализировал все свои переменные в теле, а не в списке инициализации.
main.cpp: In member function ‘bool CBicyclist::Move(short int)’:
main.cpp:39: warning: conversion to ‘short unsigned int’ from ‘short int’ may change the sign of the result
Опять забыли про unsigned, какие же мы невнимательные.
main.cpp: In function ‘int TestMaxSpeed(CBicyclist&)’:
main.cpp:63: warning: conversion to ‘short int’ from ‘int’ may alter its value
main.cpp:71: warning: conversion to ‘short int’ from ‘int’ may alter its value
А это уже претенденты на потерю данных, конверсия из int в short.
main.cpp: In constructor ‘CBicyclist::CBicyclist(const CBicycle&)’:
main.cpp:25: warning: will never be executed
Этот ворнинг выдаёт опция -Wunreachable-code, его нужно проанализировать, и если Вы уверены, что всё в порядке -- можно проигнорировать.

cppcheck

Существуют специальные программы для анализа исходных текстов на C++, которые позволяют выявить потенциальные ошибки ещё до сборки (или использования) программ. Одна из таких программ -- cppcheck. В man руководстве к этой программе говорится, что она предназначена для выявления тех ошибок, которые не находит компилятор. Это такие ошибки как некоторые утечки памяти, выход за границу массива, исключения в деструкторах, разыменование нулевых и освобождённых указателей, виртуальность деструктора базовых классов и другое.
Работать с этой программой очень просто. Для демонстрации возьмём следующий небольшой пример.
int main()
{
int * x = new int[10];
x = 0;
return 0;
}
После запуска cppcheck в этом малюсеньком коде обнаруживается сразу две ошибки.
$ cppcheck --enable=all -v .
Checking ./main.cpp...
[./main.cpp:6]: (style) Variable 'x' is assigned a value that is never used
[./main.cpp:7]: (error) Memory leak: x
Checking usage of global functions..
Во-первых, мы объявили переменную x и никогда её не используем, а во-вторых, у нас зафиксирована утечка памяти.

Проверка утечек памяти

Практика показывает, что не всегда можно найти утечки памяти статическими анализаторами. Первый приведённый в этой статье листинг -- тому подтверждение. Если запустить cppcheck для этой программы, то мы ничего не найдём. Но отчаиваться ещё рано, нам может помочь утилита valgrind, предназначенная специально для этого.  Эта утилита находит утечки памяти не статически, а при работе программы. Давайте познокомимся с выводом этой программы
$ valgrind --leak-check=full -v ./test > /dev/null
==31174== Memcheck, a memory error detector
==31174== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==31174== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
==31174== Command: ./test
==31174==
--31174-- Valgrind options:
--31174-- --suppressions=/usr/lib/valgrind/debian-libc6-dbg.supp
--31174-- --leak-check=full
--31174-- -v
--31174-- Contents of /proc/version:
--31174-- Linux version 2.6.32-5-amd64 (Debian 2.6.32-29) (ben@decadent.org.uk) (gcc version 4.3.5 (Debian 4.3.5-4) ) #1 SMP Fri Dec 10 15:35:08 UTC 2010
--31174-- Arch and hwcaps: AMD64, amd64-sse3-cx16
--31174-- Page sizes: currently 4096, max supported 4096
--31174-- Valgrind library directory: /usr/lib/valgrind
--31174-- Reading syms from /mnt/data/projects/blog/CodeOptimize/code/test (0x400000)
--31174-- Reading syms from /lib/ld-2.11.2.so (0x4000000)
--31174-- Considering /lib/ld-2.11.2.so ..
--31174-- .. CRC mismatch (computed 91367345 wanted f148be98)
--31174-- Considering /usr/lib/debug/lib/ld-2.11.2.so ..
--31174-- .. CRC is valid
--31174-- Reading syms from /usr/lib/valgrind/memcheck-amd64-linux (0x38000000)
--31174-- object doesn't have a dynamic symbol table
--31174-- Reading suppressions file: /usr/lib/valgrind/debian-libc6-dbg.supp
--31174-- Reading suppressions file: /usr/lib/valgrind/default.supp
--31174-- REDIR: 0x40162d0 (strlen) redirected to 0x380408a7 (vgPlain_amd64_linux_REDIR_FOR_strlen)
--31174-- Reading syms from /usr/lib/valgrind/vgpreload_core-amd64-linux.so (0x4a20000)
--31174-- Reading syms from /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so (0x4c21000)
==31174== WARNING: new redirection conflicts with existing -- ignoring it
--31174-- new: 0x040162d0 (strlen ) R-> 0x04c25850 strlen
--31174-- REDIR: 0x4016140 (index) redirected to 0x4c25460 (index)
--31174-- REDIR: 0x40161c0 (strcmp) redirected to 0x4c25e30 (strcmp)
--31174-- Reading syms from /usr/lib/libstdc++.so.6.0.13 (0x4e29000)
--31174-- object doesn't have a symbol table
--31174-- Reading syms from /lib/libm-2.11.2.so (0x513d000)
--31174-- Considering /lib/libm-2.11.2.so ..
--31174-- .. CRC mismatch (computed b0a7ab6b wanted 907fac55)
--31174-- Considering /usr/lib/debug/lib/libm-2.11.2.so ..
--31174-- .. CRC is valid
--31174-- Reading syms from /lib/libgcc_s.so.1 (0x53bf000)
--31174-- Considering /lib/libgcc_s.so.1 ..
--31174-- .. CRC mismatch (computed 07151771 wanted 3f9779e8)
--31174-- object doesn't have a symbol table
--31174-- Reading syms from /lib/libc-2.11.2.so (0x55d5000)
--31174-- Considering /lib/libc-2.11.2.so ..
--31174-- .. CRC mismatch (computed 21e032ea wanted d5c67601)
--31174-- Considering /usr/lib/debug/lib/libc-2.11.2.so ..
--31174-- .. CRC is valid
--31174-- REDIR: 0x5652600 (__GI_strrchr) redirected to 0x4c25280 (__GI_strrchr)
--31174-- REDIR: 0x5650b40 (__GI_strlen) redirected to 0x4c25810 (__GI_strlen)
--31174-- REDIR: 0x4ef46a0 (operator new(unsigned long)) redirected to 0x4c24d78 (operator new(unsigned long))
--31174-- REDIR: 0x5650b10 (strlen) redirected to 0x4a205ac (_vgnU_ifunc_wrapper)
==31174== WARNING: new redirection conflicts with existing -- ignoring it
--31174-- new: 0x05650b40 (__GI_strlen ) R-> 0x04c257f0 strlen
--31174-- REDIR: 0x5653e70 (mempcpy) redirected to 0x4c26bc0 (mempcpy)
--31174-- REDIR: 0x5654750 (memcpy) redirected to 0x4c25f00 (memcpy)
--31174-- REDIR: 0x564b7e0 (free) redirected to 0x4c24076 (free)
==31174==
==31174== HEAP SUMMARY:
==31174== in use at exit: 20 bytes in 3 blocks
==31174== total heap usage: 3 allocs, 0 frees, 20 bytes allocated
==31174==
==31174== Searching for pointers to 3 not-freed blocks
==31174== Checked 180,104 bytes
==31174==
==31174== 2 bytes in 1 blocks are definitely lost in loss record 2 of 3
==31174== at 0x4C24DFA: operator new(unsigned long) (vg_replace_malloc.c:261)
==31174== by 0x400B17: main (main.cpp:79)
==31174==
==31174== 18 (16 direct, 2 indirect) bytes in 1 blocks are definitely lost in loss record 3 of 3
==31174== at 0x4C24DFA: operator new(unsigned long) (vg_replace_malloc.c:261)
==31174== by 0x400B38: main (main.cpp:80)
==31174==
==31174== LEAK SUMMARY:
==31174== definitely lost: 18 bytes in 2 blocks
==31174== indirectly lost: 2 bytes in 1 blocks
==31174== possibly lost: 0 bytes in 0 blocks
==31174== still reachable: 0 bytes in 0 blocks
==31174== suppressed: 0 bytes in 0 blocks
==31174==
==31174== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 4 from 4)
--31174--
--31174-- used_suppression: 2 dl-hack3-cond-1
--31174-- used_suppression: 2 glibc-2.5.x-on-SUSE-10.2-(PPC)-2a
==31174==
==31174== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 4 from 4)

Здесь мы можем увидеть статистику выделения и высвобождения памяти
==31174== HEAP SUMMARY:
==31174== in use at exit: 20 bytes in 3 blocks
==31174== total heap usage: 3 allocs, 0 frees, 20 bytes allocated
Три раза память была выделена, но ни разу не освобождалась. Чуть далее в выводе программы valgrind мы можем найти информацию немного подробнее.
==31174== 2 bytes in 1 blocks are definitely lost in loss record 2 of 3
==31174== at 0x4C24DFA: operator new(unsigned long) (vg_replace_malloc.c:261)
==31174== by 0x400B17: main (main.cpp:79)
==31174==
==31174== 18 (16 direct, 2 indirect) bytes in 1 blocks are definitely lost in loss record 3 of 3
==31174== at 0x4C24DFA: operator new(unsigned long) (vg_replace_malloc.c:261)
==31174== by 0x400B38: main (main.cpp:80)
Вот тут-то valgrind и сдаёт нас с потрохами, выдавая все наши грехи.

Оптимизация кода

Теперь пришло время задуматься о производительности. Пусть у нас есть такой код
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdlib>
#include <sys/time.h>

template<typename Type>
std::vector<Type> & Sort(std::vector<Type> & array)
{
size_t arr_size = array.size();
for(size_t i = 0; i < arr_size; ++i)
{
bool swaped = false;
for(size_t j = 1; j < arr_size; ++j)
{
if(array[j - 1] > array[j])
{
std::swap(array[j - 1], array[j]);
swaped = true;
}
}
if(!swaped)
break;
}
return array;
}

template<typename Type>
class CWriter
{
public:
CWriter(std::ostream & stream) :
mr_stream(stream)
{
}

void operator () (const Type & value)
{
mr_stream << value << std::endl;
}

private:
std::ostream & mr_stream;
};

template<typename Type>
bool WriteArrayToFile(const std::string & filename,
const std::vector<Type> & array)
{
bool result = false;
std::ofstream out(filename.c_str());
if(out.is_open())
{
std::for_each(array.begin(), array.end(), CWriter<Type>(out));
result = true;
out.close();
}
return result;
}

int main(int argc, char ** argv)
{
if(argc < 3)
{
std::cerr << "To few argumentsn";
return 1;
}
size_t array_size = static_cast<size_t>(::atoi(argv[1]));
std::string filename = argv[2];
::srand(static_cast<unsigned int>(::time(0)));
std::vector<int> array;
array.reserve(array_size);

for(size_t i = 0; i < array_size; ++i)
{
array.push_back(::rand());
}

if(! ::WriteArrayToFile(filename, ::Sort(array)))
{
std::cerr << "Error writing the file with name "" <<
filename << ""n";
return 2;
}

return 0;
}
Здесь генерируется массив из n элементов, сортируется и записывается в файл. Если мы его запустим, то увидим картину, не очень-то удовлетваряющую нас -- большие затраты времени. В этом можно убедиться, запустив такую команду
$ time ./test 100000 test.txt

real 0m24.913s
user 0m24.566s
sys 0m0.344s
25 секунд -- это, явно, слишком много. Естественным желанием будет оптимизировать работу программы так, чтобы предельно сократить время её работы. Из данного примера очевидно, что пузырьковая сортировка и является причиной нашего недовольства, но в больших проектах причина может быть не столь очевидной. Давайте сделаем вид, что мы не знаем причину столь затяжного выполнения и попробуем найти её методом профилирования программы. А поможет нам в этом утилита с именем gprof. Подробно о работе с этой утилитой можно прочитать на man-странице проекта opennet.
Для того, чтобы можно было работать с профилировщиком gprof, нужно добавить опцию -pg к команде сборки проекта, а затем собрать проект с отладочными символами. После сборки нужно запустить программу с тем же параметрами, с которыми мы получили неудовлетворительный результат, что приведёт к созданию файла gmon.conf в последнем текущем каталоге программы, а у нас этот каталог не менялся. Запустить программу gprof можно следующим образом
$ gprof ./test > gprof.log
На выходе программа gprof выдаёт очень много информации, поэтому её лучше перенаправить в файл. Теперь нужно открыть этот файл и проанализировать его. Я советую открывать этот файл в текстовом редакторе, поддерживающем подсветку синтаксиса и выбрать режим подсветки C++, так как профиль и граф вызовов содержат много текста на C++. Кроме того, лучше отключить динамический перенос строк. Я приведу только часть простого профиля файла, который у меня получился.
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
55.50 98.49 98.49 1 98.49 154.05 std::vector<int, std::allocator<int> >& Sort<int>(std::vector<int, std::allocator<int> >&)
24.22 141.47 42.99 7769644628 0.00 0.00 std::vector<int, std::allocator<int> >::operator[](unsigned long)
10.77 160.58 19.11 73 0.26 0.26 std::vector<int, std::allocator<int> >::size() const
6.94 172.89 12.31 2507056584 0.00 0.00 void std::swap<int>(int&, int&)
Из приведённых выше строк простого профиля видно, что 55,5% времени программа тратит на сортировку и ещё 24,22% на обращение к элементам вектора. Скорее всего, нам удастся решить обе проблемы, подставив более эффективный алгоритм сортировки. Давайте проверим, заменим сортировку пузырьком сортировкой слияниями.
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdlib>
#include <sys/time.h>


template<typename Type>
std::vector<Type> & MergeSort(std::vector<Type> & array)
{
struct CMergeSort
{
void Sort(std::vector<Type> & array)
{
size_t array_size = array.size();
if(array_size <= 1)
return;

typename std::vector<Type>::iterator middle =
array.begin() + static_cast<ptrdiff_t>(array_size) / 2;
std::vector<Type> left(array.begin(), middle);
std::vector<Type> right(middle, array.end());
Sort(left);
Sort(right);
array = Merge(left, right);
}

std::vector<Type> Merge(const std::vector<Type> & left,
const std::vector<Type> & right)
{
std::vector<Type> result;
result.reserve(left.size() + right.size());
for(typename std::vector<Type>::const_iterator l =
left.begin(), r = right.begin();;)
{
if(left.end() == l)
{
typename std::vector<Type>::iterator it =
result.end();
result.insert(it, r, right.end());
break;
}
if(right.end() == r)
{
typename std::vector<Type>::iterator it =
result.end();
result.insert(it, l, left.end());
break;
}
if(*l < *r)
{
result.push_back(*l);
++l;
}
else
{
result.push_back(*r);
++r;
}
}
return result;
}
}; // struct CMergeSort

CMergeSort().Sort(array);
return array;
}

template<typename Type>
class CWriter
{
public:
CWriter(std::ostream & stream) :
mr_stream(stream)
{
}

void operator () (const Type & value)
{
mr_stream << value << std::endl;
}

private:
std::ostream & mr_stream;
};

template<typename Type>
bool WriteArrayToFile(const std::string & filename,
const std::vector<Type> & array)
{
bool result = false;
std::ofstream out(filename.c_str());
if(out.is_open())
{
std::for_each(array.begin(), array.end(), CWriter<Type>(out));
result = true;
out.close();
}
return result;
}

int main(int argc, char ** argv)
{
if(argc < 3)
{
std::cerr << "To few argumentsn";
return 1;
}
size_t array_size = static_cast<size_t>(::atoi(argv[1]));
std::string filename = argv[2];
::srand(static_cast<unsigned int>(::time(0)));
std::vector<int> array;
array.reserve(array_size);

for(size_t i = 0; i < array_size; ++i)
{
array.push_back(::rand());
}

if(! ::WriteArrayToFile(filename, ::MergeSort(array)))
{
std::cerr << "Error writing the file with name "" <<
filename << ""n";
return 2;
}

return 0;
}
Если мы откомпилируем этот код и запустим программу, то получим следующее
$ time ./test 100000 test.txt

real 0m0.437s
user 0m0.076s
sys 0m0.360s
Очевидно, что такие результаты нас устраивают.

---------------
Источники:
Статический анализ кода C++
Профилятор gprof

Работа с COM и ActiveX в Visual C++

Сразу внесу некоторую поправку к обозначенной в заголовке теме. В этой статье речь пойдёт не просто о COM и ActiveX, а о COM и ActiveX библиотеках, содержащих библиотеки типов (type library).
Поводом для написания этой статьи стали мои собственные изыскания в этой теме. На просторах Интернета тут и там разбросана информация о работе с COM и ActiveX из Visual C++ (отмечу, что речь идёт не о .NET, а обычном C++), но хорошего, структурированного материала мне найти не удалось. В этой статье я разберу два примера: в первом я расскажу о работе с простыми COM библиотеками; во втором будет показана работа с объектом ActiveX. Я не буду рассказывать, что такое COM, ActiveX и Type libraries, об этом Вы сможете прочитать, например, здесь.

Директива препроцессора #import

Компилятор Microsoft Visual C++ определяет директиву препроцессора #import. Основное её предназначение – загружать информацию из библиотеки типов и представлять её в виде кода C++. В простейшем случае директива #import используется так
#import "libname.tlb"
После компиляции в каталоге сборки программы появляются два файла: libname.tlh и libname.tli. Первый из них является заголовочным и автоматически подключается к программе, а второй содержит код реализации и компилируется вместе с проектом.
У директивы #import есть множество дополнительных опций, о которых Вы сможете прочитать тут или в справочной системе к Visual Studio.

Где взять библиотеку типов

Хотя директива #import и позволяет подключать сами COM серверы, я советую подключать именно библиотеки типов, это бинарные файлы, обычно имеющие расширение TLB. Это позволит добавить такой файл в Ваш проект и не заботиться о расположении библиотеки при сборке проекта на другом компьютере.
Но вот тут, на первый взгляд, может возникнуть проблема. Где взять библиотеку типов, если большинство серверов распространяется одним файлом (чаще всего это либо *.dll, либо *.ocx)? Ответ прост; библиотека типов является ни чем иным как ресурсом это сервера. А если это ресурс, то получить его можно, к примеру, с помощью Resource Hacker. На скриншоте ниже показано, как это сделать.

Простой COM сервер

Начнём работу с простого примера, в котором не нужно отображать визуальные компоненты. Для демонстрации я взял библиотеку msxml6.dll. Стоит отметить, что в Windows API существует привязка к этой библиотеке. Ниже приведён листинг программы, которая создаёт файл "test.xml" с одним лишь тегом "Example".
#include <iostream>
#import "msxml6.tlb"

class CComInitializer
{
public:
CComInitializer() { ::CoInitialize(NULL); }
~CComInitializer() { ::CoUninitialize(); }
} gComInitializer;

int main()
{
MSXML2::IXMLDOMDocument3Ptr xml_document;
HRESULT hr = ::CoCreateInstance(__uuidof(MSXML2::DOMDocument60),
NULL, CLSCTX_INPROC_SERVER, __uuidof(MSXML2::IXMLDOMDocument3),
reinterpret_cast<void **>(&xml_document));
if(SUCCEEDED(hr))
{
MSXML2::IXMLDOMElementPtr xml_elem =
xml_document->createElement(L"Example");
xml_document->appendChild(xml_elem);
xml_document->save(L"test.xml");
}
else
std::cerr << "Error creating instancen";

return 0;
}
Назначение класса CComInitializer только в том, чтобы инициализировать модель COM в приложении. Для этого создаётся один глобальный объект этого класса. При разрушении объекта модель COM деинициализируется.
Директива #import помещает весь сгенерированный код в пространство имён, соответствующее имени библиотеки, в нашем случае – это MSXML2. В сгенерированный код, по умолчанию, добавляются объявления "умных" указателей для каждого интерфейса. Имена их соответствуют именам интерфейсов с постфиксом Ptr. Объявив такой объект, мы можем передать указатель на него в функцию CoCreateInstance.
Каждый интерфейс и класс объявляется в файлах *.tlh специальным образом, например
struct __declspec(uuid("2933bf96-7b36-11d2-b20e-00c04f983e60"))
IXMLDOMDocument3 : IXMLDOMDocument2
Это позволяет получить их GUID’ы с помощью оператора (специфичного для Visual C++) __uuidof. Для того, чтобы быть точно уверенными, что Вы получаете GUID именно того класса, который Вам нужен, следует обратиться к текстам библиотеки типов. Чтобы получить текст библиотеки из двоичного файла можно воспользоваться утилитой OleView или просмоторщиком из Total Commander. В IDL коде библиотеки мы должны найти coclass, который реализует нужный интерфейс.
[
uuid(88D96A05-F192-11D4-A65F-0040963251E5),
helpstring("W3C-DOM XML Document 6.0 (Apartment)")
]
coclass DOMDocument60 {
[default] interface IXMLDOMDocument3;
[default, source] dispinterface XMLDOMDocumentEvents;
};
Все последующие действия специфичны для библиотеки MSXML и приведены только для примера.
Обратите внимание, я не вызываю метод Release из xml_document и xml_elem, это за меня делают "умные" указатели.

Элемент управления ActiveX

Для того, чтобы разместить элемент ActiveX в окне требуется выполнить очень много скучной рутинной работы, поэтому мы воспользуемся готовым решением, предоставляемым библиотекой MFC. Те, кому интересно узнать всю подноготную, могут пройти по этой и этой ссылкам.
Для демонстрации встраивания элемента управления, я воспользовался библиотекой MSFLXGRD.OCX, которая входит в стандартную поставку Visual Basic 6.0. То, что у меня получилось, видно на следующем скриншоте.

По традиции, сразу приведу листинг программы, а затем прокомментирую.
#define WINVER 0x0501

#include <afxwin.h>
#include <afxole.h>
#import "msflexgrid.tlb"


template<typename InterfaceT>
class CComWindow :
public CWnd
{
public:
CComWindow(const IID & iid, const CLSID & class_id) :
mp_ax(NULL),
m_class_id(class_id),
m_iid(iid)
{
}

virtual ~CComWindow()
{
if(NULL != mp_ax)
mp_ax->Release();
}

virtual BOOL Create(LPCTSTR class_name, LPCTSTR window_name,
DWORD style, const RECT & rect, CWnd * parent, UINT id,
CCreateContext * context = NULL)
{
UNREFERENCED_PARAMETER(class_name);
UNREFERENCED_PARAMETER(context);
BOOL result = CreateControl(m_class_id, window_name,
style, rect, parent, id);
if(result)
result = InitComponent();
return result;
}

virtual BOOL Create(LPCTSTR window_name, DWORD style,
const RECT & rect, CWnd * parent, UINT id, CFile * persist = NULL,
BOOL storage = FALSE, BSTR lic_key = NULL)
{
BOOL result = CreateControl(m_class_id, window_name, style,
rect, parent, id, persist, storage, lic_key);
if(result)
result = InitComponent();
return result;
}

protected:
BOOL InitComponent()
{
BOOL result = FALSE;
COleControlSite * control_site = GetControlSite();
if(NULL != control_site)
{
HRESULT hr =
control_site->m_pInPlaceObject->QueryInterface(
m_iid, reinterpret_cast<void **>(&mp_ax));

if(SUCCEEDED(hr))
result = TRUE;
else
mp_ax = NULL;
}
return result;
}

public:
InterfaceT * mp_ax;

protected:
CLSID m_class_id;
IID m_iid;
}; // class CComWindow





class CMainWindow :
public CWnd
{
DECLARE_MESSAGE_MAP()
DECLARE_EVENTSINK_MAP()
public:
CMainWindow();
virtual ~CMainWindow();
int OnCreate(LPCREATESTRUCT cs);

HRESULT OnFlexGridClick();

private:
CComWindow<MSFlexGridLib::IMSFlexGrid> * mp_flex_grid;
static const UINT m_grid_id = 1800;
}; // class CMainWindow

BEGIN_MESSAGE_MAP(CMainWindow, CWnd)
ON_WM_CREATE()
END_MESSAGE_MAP()

BEGIN_EVENTSINK_MAP(CMainWindow, CWnd)
ON_EVENT(CMainWindow, m_grid_id, DISPID_CLICK,
CMainWindow::OnFlexGridClick, VTS_NONE)
END_EVENTSINK_MAP()

CMainWindow::CMainWindow() :
mp_flex_grid(NULL)
{
}

CMainWindow::~CMainWindow()
{
delete mp_flex_grid;
}

int CMainWindow::OnCreate(LPCREATESTRUCT cs)
{
int result = CWnd::OnCreate(cs);

if(0 == result)
{
mp_flex_grid = new CComWindow<MSFlexGridLib::IMSFlexGrid>(
__uuidof(MSFlexGridLib::IMSFlexGrid),
__uuidof(MSFlexGridLib::MSFlexGrid));
BOOL created = mp_flex_grid->Create(L"",
WS_CHILD | WS_VISIBLE, CRect(CPoint(10, 10), CSize(520, 450)),
this, m_grid_id);
if(created)
{
const long cols = 8;
const long rows = 25;
mp_flex_grid->mp_ax->Cols = cols;
mp_flex_grid->mp_ax->Rows = rows;
mp_flex_grid->mp_ax->ColWidth[0] = 350;

mp_flex_grid->mp_ax->Col = 0;
wchar_t text[8];
for(long i = 1; i < rows; ++i)
{
_itow(i, text, 10);
mp_flex_grid->mp_ax->Row = i;
mp_flex_grid->mp_ax->Text = text;
}
mp_flex_grid->mp_ax->Row = 0;
mp_flex_grid->mp_ax->Col = 1;
mp_flex_grid->mp_ax->Text = L"Пн";
mp_flex_grid->mp_ax->Col = 2;
mp_flex_grid->mp_ax->Text = L"Вт";
mp_flex_grid->mp_ax->Col = 3;
mp_flex_grid->mp_ax->Text = L"Ср";
mp_flex_grid->mp_ax->Col = 4;
mp_flex_grid->mp_ax->Text = L"Чт";
mp_flex_grid->mp_ax->Col = 5;
mp_flex_grid->mp_ax->Text = L"Пт";
mp_flex_grid->mp_ax->Col = 6;
mp_flex_grid->mp_ax->Text = L"Сб";
mp_flex_grid->mp_ax->Col = 7;
mp_flex_grid->mp_ax->Text = L"Вс";>
}
else
result = 1;
}
return result;
}

HRESULT CMainWindow::OnFlexGridClick()
{
static int i_text = 1;
wchar_t text[16];
::_itow(i_text++, text, 10);
mp_flex_grid->mp_ax->Text = text;
return S_OK;
}




class CApplication :
public CWinApp
{
public:
CApplication();
virtual ~CApplication();
virtual BOOL InitInstance();
} gApplication; // class CApplication


CApplication::CApplication()
{
}

CApplication::~CApplication()
{
delete m_pMainWnd;
}

BOOL CApplication::InitInstance()
{
BOOL result = CWinApp::InitInstance();
if(FALSE != result)
{
::AfxOleInit();
::AfxEnableControlContainer();

const wchar_t * wnd_class = ::AfxRegisterWndClass(
0, LoadStandardCursor(IDC_ARROW),
reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1));
CMainWindow * main_window = new CMainWindow();
main_window->CreateEx(0, wnd_class, L"Flex Grid Example",
WS_OVERLAPPEDWINDOW, 200, 100, 800, 600,
GetDesktopWindow(), NULL);
m_pMainWnd = main_window;
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
}
return result;
}
Теперь, нам требуется не просто получить указатель на интерфейс, но и связать его с окном, поэтому создаём класс CComWindow, сделав его шаблонным для универсальности. В качестве параметра шаблона требуется указывать тип интерфейса COM объекта.
Операции Create замещают операции класса CWnd и создают контрол из переданных в конструктор GUID’ов интерфейса и кокласса. Член mp_ax инициализируется указателем из элемента управления и является мостом между клиентом и интерфейсом COM.
Далее окно ActiveX создаётся как и все другие окна, с той лишь разницей, что в конструктор мы передаём GUID’ы класса и интерфейса.
Следует заметить, что директива #import генерирует специальные поля классов – свойства. Такие поля также являются расширением Visual C++ и выглядят следующим образом
__declspec(property(get=GetRows,put=PutRows))
long Rows;
где значениями параметров get и put являются функции, которые неявно вызываются при соответствующих обращениях к данному полю.
Чтобы окно ActiveX могло общаться с окружающим миром посредствам сообщений, библиотекой MFC предусмотрена специальная карта сообщений. Используется она практически идентично карте основных сообщений. Для её объявления добавляем в класс родительского окна макроопределение
DECLARE_EVENTSINK_MAP()
А определение этой карты должно находиться между макросами
BEGIN_EVENTSINK_MAP(CMainWindow, CWnd)
и
END_EVENTSINK_MAP()
Далее, нам нужно в IDL тексте TLB файла найти, какой класс реализует события интересующего интерфейса
[
uuid(6262D3A0-531B-11CF-91F6-C2863C385E30),
helpstring("Microsoft FlexGrid Control 6.0"),
helpcontext(0x00059621),
licensed,
control
]
coclass MSFlexGrid {
[default] interface IMSFlexGrid;
[default, source] dispinterface DMSFlexGridEvents;
};
В нашем случае – это DMSFlexGridEvents. В *.tlh файле Вы найдёте объявление этого класса со всеми доступными событиями, а в файле *.tli – все реализации.
Чтобы добавить обработку события в карту EVENTSINK_MAP следует добавить в класс окна функцию с идентичной сигнатурой (за исключением имени). После этого в карту событий помещается элемент с именем ON_EVENT. Первым параметром этого макроса является класс, принимающей событие, второй - идентификатор ActiveX объекта, третий - это номер события, его можно подсмотреть в реализации методов-событий в файле *.tli. Стандартные события имеют макроимена. Следующим параметром мы передаём метод, который будет вызван в ответ на событие. Последний параметр - это набор аргументов, которым соответствуют макроопределения. Набор этих параметров пишется без разделения запятой. Все возможные значения объявлены в файле afxdisp.h.
Для примера, я определил обработчик события Click. Теперь, при щелчке мышью, в соответствующую ячейку будут устанавливаться числовые значения, каждый раз на единицу больше предыдущего.
Следует отметить, что для работы с COM в приложении MFC необходимо вызвать функцию AfxOleInit, а для работы с компонентами ActiveX - функцию AfxEnableControlContainer. Все они находятся в файле afxdisp.h, но подключать их следует из afxole.h.

Положение двух точек относительно прямой

Поводом для написания этой статьи стала эта тема на форуме. Вопрос довольно простой, но мне показалось, что он заслуживает освещения в цикле, посвящённом применению математики в программировании.
Итак, дано: уравнение прямой, две точки, не лежащие на этой прямой. Требуется определить, лежат ли точки по одну сторону от прямой или по разные.

Где может понадобится решение подобной задачи? Представьте, вы пишите стратегическую игру. Два юнита находятся по разные стороны реки. Пользователь выделяет их обоих, и заставляет куда-то идти. И тут нужно определить, смогут ли оба юнита пройти туда, куда велит пользователь. Стало интереснее?
Итак, набросаем небольшой рисунок.

У нас есть точки A, B и C. B и C лежат по одну сторону от прямой, а A по другую.
Для того, чтобы проверить, лежат ли две точки по одну сторону от прямой или нет нужно спроецировать эти точки на прямую линией, параллельной оси OY. Затем сравнить ординаты определяемых точек с ординатами полученных. Если оба отношения будут идентичными, то точки лежат с одной стороны, иначе - с разных.
Поробуем это запрограммировать.
#include <iostream>

class CFunction
{
public:
CFunction(double a, double b) :
m_a(a),
m_b(b)
{
}

double Run(double x)
{
return m_a * x + m_b;
}

private:
double m_a;
double m_b;
};

struct CPoint
{
double m_x;
double m_y;
};

int main()
{
CPoint point_a;
CPoint point_b;
double a;
double b;

std::cout << "y = ax + b.nEnter the a value: ";
std::cin >> a;
std::cout << "Enter the b value: ";
std::cin >> b;

std::cout << "Point A.nEnter the X coord: ";
std::cin >> point_a.m_x;
std::cout << "Enter the Y coord: ";
std::cin >> point_a.m_y;

std::cout << "Point B.nEnter the X coord: ";
std::cin >> point_b.m_x;
std::cout << "Enter the Y coord: ";
std::cin >> point_b.m_y;

CFunction function(a, b);

bool a_up = function.Run(point_a.m_x) > point_a.m_y;
bool b_up = function.Run(point_b.m_x) > point_b.m_y;

if(a_up == b_up)
{
std::cout << "Points lie on one siden";
}
else
{
std::cout << "Points lie on different sidesn";
}
}
Здесь я ввёл класс, соответствующий линейной функции и структуру для описания точки. Ничего сложного. Далее, как уже говорилось, берём ординаты из функции, соответствующие абсциссам точкек и сравниваем их с ординатами точек. Проверяем результаты. Если они идентичны, выдаём сообщение о том, что точки лежат с одной стороны прямой, иначе сообщаем о том, что точки находятся по разные стороны прямой.

Универсальный Makefile

Довольно часто приходится писать небольшие проекты для тестирования того или иного участка кода. Компиляция этих проектов не всегда тривиальна, и запуск компиляции из командной строки утомляет. К тому же не хочется создавать проекты в IDE. И тут на помощь программистам приходят Makefile'ы. Пользователям UNIX-like систем хорошо знакомы файлы для сборки приложений, имеющие названия Makefile. Но и писать всякий раз Makefile'ы тоже не удобно. Поэтому я решил написать универсальный Makefile. На столько универсальный, на сколько это возможно.

К примеру, мы хотим написать такой код:
#include <iostream>
#include <string>
#include <boost/thread.hpp>

boost::mutex gMutex1;
boost::mutex gMutex2;


void MyThread(const std::string & name, size_t iter_count,
size_t step_size)
{
for(size_t i = 0; i < iter_count;)
{
gMutex1.lock();
gMutex2.lock();

for(size_t j = 0; j < step_size && i < iter_count; ++j, ++i)
{
if(0 == j)
gMutex1.unlock();
std::cout << name << ": " << i + 1 << " from " << iter_count <<
std::endl;
}
std::cout << std::endl;
gMutex2.unlock();
}
}

int main()
{
boost::thread thread_one(MyThread, "one", 12, 3);
boost::thread thread_two(MyThread, "two", 8, 4);
thread_one.join();
thread_two.join();
return 0;
}
Сразу же видно, что без подключения библиотеки boost_thread тут не обойтись. Не буду утомлять Вас и приведу полный код Makefile'а. Пусть наша программа записана в файл main.cpp
TARGET_NAME = test
DEFAULT_BUILD_TYPE = debug
#DEFAULT_BUILD_TYPE = release
COMPILER = g++
HEADERS =
SOURCES = main.cpp
OBJECTS = *.o

# Common options
COMMON_COMPILER_OPTS =
COMMON_LINKER_OPTS = -lboost_thread
COMMON_COMMON_OPTS = -Wall

# Debug options
DEBUG_COMPILER_OPTS = $(COMMON_COMPILER_OPTS)
DEBUG_LINKER_OPTS = $(COMMON_LINKER_OPTS)
DEBUG_COMMON_OPTS = $(COMMON_COMMON_OPTS) -g -O0

# Release options
RELEASE_COMPILER_OPTS = $(COMMON_COMPILER_OPTS)
RELEASE_LINKER_OPTS = $(COMMON_LINKER_OPTS)
RELEASE_COMMON_OPTS = $(COMMON_COMMON_OPTS) -O4

# Debug build steps
DEBUG_BUILD = $(COMPILER) $(DEBUG_COMMON_OPTS) $(DEBUG_COMPILER_OPTS) -c $(SOURCES)
DEBUG_LINK = $(COMPILER) $(DEBUG_COMMON_OPTS) $(DEBUG_LINKER_OPTS) $(OBJECTS) -o $(TARGET_NAME)

# Release build steps
RELEASE_BUILD = $(COMPILER) $(RELEASE_COMMON_OPTS) $(RELEASE_COMPILER_OPTS) -c $(SOURCES)
RELEASE_LINK = $(COMPILER) $(RELEASE_COMMON_OPTS) $(RELEASE_LINKER_OPTS) $(OBJECTS) -o $(TARGET_NAME)

all: $(DEFAULT_BUILD_TYPE)

debug: debug_build debug_link

release: release_build release_link

debug_build: $(HEADERS) $(SOURCES)
$(DEBUG_BUILD)

debug_link: debug_build
$(DEBUG_LINK)

release_build: $(HEADERS) $(SOURCES)
$(RELEASE_BUILD)

release_link: release_build
$(RELEASE_LINK)

clean:
rm -rf $(TARGET_NAME)
rm -rf $(OBJECTS)
Так как при вставке в этот блог строк, начинающихся со знака табуляции они (знаки табуляции) заменяются на пробелы, прошу Вас заменить четыре пробела на один знак табуляции. Утилита make требует именно табуляцию.
Итак, что мы здесь имеем. В начале файла большое количество переменных. По порядку:
TARGET_NAME - имя исполняемого файла;
DEFAULT_BUILD_TYPE - тип компиляции по умолчанию. Может принемать два значения: debug и release;
COMPILER - команда, вызывающая компилятор;
HEADERS - перечисляем здесь все заголовочные файлы через пробел.
SOURCES - а здесь перечисляем все компилируемые модули (*.cpp);
OBJECTS - тут мы вписываем *.o,- это маска для всех объектных файлов.
Далее идёт секция общих опций для отладочной и релизной компиляций:
COMMON_COMPILER_OPTS - общие опции компилятора;
COMMON_LINKER_OPTS - общие опции линковщика;
COMMON_COMMON_OPTS - общие опции общие для компилятора и для линковщика, чтобы два раза не писать;
Опции отладочного режима:
DEBUG_COMPILER_OPTS - отладочные опции компилятора;
DEBUG_LINKER_OPTS - отладочные опции линковщика;
DEBUG_COMMON_OPTS - отладочные опции общие для компилятора и линковщика;
Опции релиза:
RELEASE_COMPILER_OPTS - релизные опции компилятора;
RELEASE_LINKER_OPTS - релизные опции линковщика;
RELEASE_COMMON_OPTS - релизные опции общие для компилятора и линковщика.
Следующие переменные DEBUG_BUILD, DEBUG_LINK, RELEASE_BUILD и RELEASE_LINK складывают команды на компиляцию и линковку из предыдущих переменных. Их редактировать не нужно.
Итак, первая цель, цель по умолчанию, по традиции называется all. Цель all скомпилирует и слинкует программу в режиме, заданном переменной DEFAULT_BUILD_TYPE.
Отдельные цели debug и release компилируют и линкуют соответствующие конфигурации программы.
Цели debug_build и release_build только компилируют, но не линкуют приложение, а цели debug_link и release_link, напротив, только линкуют программу.
Цель clean очищает каталог от объектных файлов и файла приложения. Обратите внимание на то, что использованы команды rm. Для успешной работы в ОС Windows их следует заменить на команды del или установить в систему соответствующую программу, например с пакетом MSYS.

В опциях линкера мы указали библиотеку boost_thread. Но я не указал каталог, в котором она находится. Я этого не сделал потому, что в ОС Linux, которую я использую, эта библиотека находится в стандартной директории, путь до которой известен компилятору. Пользователям ОС Window придётся указать путь в опции -L (например -LC:boostlib). Аналогичным образом стоит поступить и с каталогом, содержащим заголовочный файл boost/thread.hpp. Для этого в опциях компилятора указываем путь до этого каталога в параметре -I.
Указываем имя программы: test. Для ОС Windows не обязательно указывать расширение .exe, компилятор g++ допишет его сам.
Дописываем имя нашего файла с исходными текстами: main.cpp. Теперь всё готово для компиляции. Откройте консоль и перейдите в каталог с Makefile'ом и файлом main.cpp и запустите команду make. Пользователям ОС Windows придётся установить комплект программ MinGW и поместить путь до подкаталога bin в переменную окружения PATH. Кроме того, команда для ОС Windows будет не make, а mingw32-make. После успешной компиляции проекта (для компиляции моего примера Вам понадобится библиотека boost), можно запускать программу.
sergey@debian:~/temp/cpp$ make
g++ -Wall -g -O0 -c main.cpp
g++ -Wall -g -O0 -lboost_thread *.o -o test
sergey@debian:~/temp/cpp$ ./test
one: 1 from 12
one: 2 from 12
one: 3 from 12

two: 1 from 8
two: 2 from 8
two: 3 from 8
two: 4 from 8

one: 4 from 12
one: 5 from 12
one: 6 from 12

two: 5 from 8
two: 6 from 8
two: 7 from 8
two: 8 from 8

one: 7 from 12
one: 8 from 12
one: 9 from 12

one: 10 from 12
one: 11 from 12
one: 12 from 12
Первым управление может захватить второй поток, тогда результат будет немного отличаться.