1с: 8.1 Как провести и отменить проведение документа в программно.

Как правильно сделать отмену проведения документа программно?

Такой вопрос у меня возник, когда я попытался сделать отмену проведения документа программно. Так как  команды СделатьНеПроведенным() в "восьмерке" нету, то поначалу я думал, что можно присвоить признаку Проведен = Ложь и проведение документа отменится. Так оно визуально и получалось. НО  движения этого документа оставались. Получалось: документ видно, что он не проведен, а движения документа остались.

Выход оказался прост. Для отмены проведения используется Функция Записать с реквизитом:

НашДок.Записать(РежимЗаписиДокумента.ОтменаПроведения);

Так же работает и программное проведение документа:
НашДок.Записать(РежимЗаписиДокумента.Проведение);


Кроме того, есть команда:
РежимЗаписиДокумента.Запись


В этом случае будут сохранены изменения, внесенные в документ, и движения


Модальное открытие формы в 1с 8. Как вернуть результат ОткрытьМодально()

Открытие формы модально - форма появляется на экране и блокирует другие окна, пока не будет закрыта.

МодФорма = ПолучитьФорму("ОсновнаяФорма",ВладелецФормы);
Результат = МодФорма.ОткрытьМодально();
Возникает проблема возврата результата, если форма не "для выбора"




В описании функции ОткрытьМодально() написано:


Форма.ОткрытьМодально (Form.DoModal)
ОткрытьМодально(<Таймаут>)

Параметры: <Таймаут> (необязательный)
Тип: Число. Время показа формы в секундах, по истечении которого форма будет закрыта с параметром закрытия Неопределено. Если значение параметра не задано, время показа не ограничено.
Значение по умолчанию: 0
 Возвращаемое значение:
Тип: Произвольный. Команда закрытия формы.
Если форма открывалась для выбора, возвращает выбранное значение (или массив значений, в случае разрешенного множественного выделения в открываемой форме). Если значение не выбрано, возвращается Неопределено. 
Описание:
Открывает форму в модальном режиме.

Я специально выделил фразу цветом. Получается: если форма определена "для выбора" - то она возвращает выбранные значения
В любых других случаях будет возвращаться - Неопределено. 


Что же делать, когда у нас  "обычная" форма (т. е. форма не определена как "для выбора") и мы хотим вернуть результат из формы после закрытия ?

В этом случае есть несколько решений:
1. Получив доступ к форме, мы можем вызывать различные функции и получать переменные. Естественно нужно, чтобы эти функции имели статус Экспорт
Например:  МодФорма = ПолучитьФорму("ОсновнаяФорма",ВладелецФормы);
          Результат = МодФорма.ОткрытьМодально();
РезультатФункции  = МодФорма.НашаФункция("Параметры функции");
Внимание!  Форма после закрытия продолжает существовать, и доступ к функциям с пометкой Экспорт можно получить.
......
// Функция вставляется в   Форму
Функция НашаФункция(ПараметрыФункцииЭкспорт
// Действия в функции
Возврат Результат  
КонецФункции
2.  Можно использовать команду ОповеститьОВыборе(ВозвращаемыйПараметр)
Эта команда позволяет вернуть из формы указанный параметр "ВозвращаемыйПараметр"


На  этом пока всё, может будут у кого замечания и дополнения ... пишите :)



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

Заголовок темы может звучать, для Вас, странно, но довольно часто требуется, чтобы приложение имело сразу несколько вариантов интерфейса. Например, 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";
}
}
Здесь я ввёл класс, соответствующий линейной функции и структуру для описания точки. Ничего сложного. Далее, как уже говорилось, берём ординаты из функции, соответствующие абсциссам точкек и сравниваем их с ординатами точек. Проверяем результаты. Если они идентичны, выдаём сообщение о том, что точки лежат с одной стороны прямой, иначе сообщаем о том, что точки находятся по разные стороны прямой.