Шаблонная виртуальность (Parameterized Virtuality)
Помимо использования С++ интересно использовать трюки, которые позволяет этот язык. Одним из таких трюков является Parameterized Virtuality(C++ Templates: The Complete Guide By David Vandevoorde, Nicolai M. Josuttis).
Параметризированная виртуальность позволяет указывать будет ли использоваться механизм виртуальных функций при работе с иерархией классов.
Для этого использутся два класса:
view plainprint?
struct not_virtual_type {};
struct virtual_type
{
virtual void method() {}
};
Далее, определяется базовый класс иерархии:
view plainprint?
template< class vbase >
struct base: private vbase
{
void method() {}
};
И класс наследник:
view plainprint?
template< class type >
struct derived: public base<type>
{
void method();
};
Теперь, при использовании класса derived мы можем указать, использовать ли виртуальность. Это делается следующим образом:
view plainprint?
template< class T >
void test(base< T >& t)
{
t.method();
}
view plainprint?
base< not_virtual_type >* ptr = new derived< not_virtual_type >;
test(*ptr); // здесь вызывается base::method()
delete ptr;
base< virtual_type >* vptr = new derived< virtual_type >;
test(*vptr); // здесь вызывается derived::method()
delete vptr;
Интересная штука- индексируемые свойства С# и Pascal(Delphi)
Прикольная штука Вопрос как в C# сделать код который может реализовать вот это
program IndexerTest;
{$APPTYPE CONSOLE}
{%TogetherDiagram 'ModelSupport_IndexerTest\default.txaPackage'}
{%TogetherDiagram 'ModelSupport_IndexerTest\IndexerTest\default.txaPackage'}
{%TogetherDiagram 'ModelSupport_IndexerTest\default.txvpck'}
{%TogetherDiagram 'ModelSupport_IndexerTest\IndexerTest\default.txvpck'}
uses
SysUtils;
type
TTestIndexer = class
strict private
procedure SetProperty1(i:integer;val : Integer);
function GetProperty1(i:integer) : Integer;
procedure SetProperty2(i:integer;val : Integer);
function GetProperty2(i:integer) : Integer;
private
ar: array of integer;
public
constructor Create;
property Property1 [i :integer]: Integer read GetProperty1 write SetProperty1;
property Property2 [i :integer]: Integer read GetProperty2 write SetProperty2;
end;
var
///<directed>True</directed>
IndTestArr: array[0..2] of TTestIndexer;
i,j:integer;
constructor TTestIndexer.Create;
var
i:integer;
begin
inherited;
SetLength(ar,3);
for i := 0 to 2 do
ar[i] := i;
end;
function TTestIndexer.GetProperty1(i:integer): Integer;
begin
result := ar[i];
end;
procedure TTestIndexer.SetProperty1(i:integer;val : Integer);
begin
ar[i]:= val;
end;
function TTestIndexer.GetProperty2(i:integer): Integer;
begin
result := ar[i];
end;
procedure TTestIndexer.SetProperty2(i:integer;val : Integer);
begin
ar[i]:= val;
end;
begin
{ TODO -oUser -cConsole Main : Insert code here }
for I := 0 to 2 do
begin
IndTestArr[i] := TTestIndexer.Create;
IndTestArr[i].Property1[2] := 2;
end;
for I := 0 to 2 do
begin
for j := 0 to 2 - 1 do
with IndTestArr[i] do
Write(Property1[j],' ',Property2[j]);
Writeln;
end;
readln;
end.
Что тут интересно так это то что на C# влоб задачу не решить.
Нужно исхитриться
Такой код не пройдет
class testind
{
int [] ar;
public testind ()
{
ar = new int[3];
ar[0] = 1;
ar[1] = 2;
ar[2] = 3;
}
void setP1( int i, int val)
{
ar[i] = val;
}
int getP1( int i)
{
return ar[i];
}
public int this[int i]
{
get{ return ar[i];}
set{ ar[i] = value;}
}
public int this[int i]
{
get{ return this.getP1(i);}
set{ return this.setP1(i,value);}
}
}
А такой пройдет
class testind: IMyProp1,IMyProp2
{
int IMyProp1.this[int i]
{
get{ return ar[i];}
set{ ar[i] = value;}
}
int IMyProp2.this[int i]
{
get{ return ar[i];}
set{ ar[i] = value;}
}
public IMyProp1 MyProp1{ get {return this;}}
public IMyProp2 MyProp2{ get {return this;}}
int [] ar;
public testind ()
{
ar = new int[3];
ar[0] = 1;
ar[1] = 2;
ar[2] = 3;
}
}
Только использовать придется таким способом – через указатель на интерфейс
MpTd.MyProp1[1] = 10;
MpTd.MyProp1[3] = 10;
------------
Престижный мобильный телефон от известного производителя – признак успеха любого делового человека. Купить самые прогрессивные модели вы всегда можете на сайте Esms.com.ua.
Узнать интересную информацию про бодибилдинг, программу тренировок и спортивную диету можно на сайте artbody.ru.
Свежие новости строительной России, последние технологии и материалы в отделке интерьера – все это сайт Pbloks.ru.
Опыты со SWIG'ом: C++ код и Ruby
Недавно у меня возник вопрос, как скрестить C++ и Ruby, а именно - есть С++ либа, хотелось бы использовать ее из Ruby. Покопавшись немного в инете, нашел статью "Использование C и Ruby", откуда стало ясно, что основные способы создания расширений для Руби - использование Ruby API (ruby.h, rubyio.h, intern.h) и утилита SWIG. Для начала решил попробовать SWIG.
SWIG на основе С и С++ интерфейса генерит код для расширений к другим высокоуровневым языкам (причем не только к скриптовым). На входе получает файл интерфейса .i, на выходе - С или С++ код.
В качестве примера берем такой код:
view plainprint?
// test.h
class MyTest
{
public:
// typedef делаем для того, что бы можно было безболезненно
// поэксперементировать с другими типами
typedef double type_t;
private:
type_t m_t;
public:
MyTest() {}
MyTest(const type_t& t) {m_t = t;}
void set(const type_t& val);
const type_t& get() const;
};
//test.cpp
#include "test.h"
void MyTest::set(const MyTest::type_t& val)
{
m_t = t;
}
const MyTest::type_t& MyTest::get() const
{
return m_t;
}
Создаем интерфейсный файл test.i:
view plainprint?
%module test
%{
#include "test.h"
%}
class MyTest
{
public:
typedef double type_t;
private:
type_t m_t;
public:
MyTest() {}
MyTest(const type_t& t) {m_t = t;}
void set(const type_t& val);
const type_t& get() const;
};
Тепер на основанни этого файла можно сгенерить код расширения для Руби:
swig -c++ -ruby test.i
Очевидно, что -ruby указывает целевой язык.
Ключ -с++ указывает на то, что исходный код написан на с++. Если этого не указать, то может вылезти ворнинг типа: "test.i:7 Warning(301): class keyword used, but not in C++ mode.", файл получится с расширением .c и в дальнейшем возникнут проблемы с его сборкой.
Для указания имя модуля можно использовать либо ключ -module либо в интерфейсном файле добавить строку %module name.
Результат выполнения команды файл test_wrap.cxx. Получить необходимую библиотеку из него можно двумя способами - создать скрипт, генерящий make-файл или собрать в ручную. В ручную это будет выглядеть подобным образом:
$g++ -c test.cpp
$g++ -c test_wrap.cxx -I/usr/lib/ruby/1.8/i586-linux
$g++ -shared test.o test_wrap.o -o test.so
Скрипт, создающий Makefile, будет выглядеть так:
view plainprint?
require 'mkmf'
$libs = append_library($libs, "supc++")
create_makefile('test')
В итоге получилась библиотека test.so. Осталось посмотреть, как это будет работать в Руби:
view plainprint?
require 'test'
t1 = Test::MyTest.new
puts t1.get #-> 0.0
t1.set(12.34)
puts t1.get #-> 12.34
t2 = Test::MyTest.new(45.67)
puts t2.get #->45.67
В принципе ничего сложного.
Интересно что получится, если type_t определить как std::string... Все описанные выше манипуляции прошли нормально, но вот тест не пошол, в месте require 'test' выводит ошибку 'LoadError'. Просто так не получается, но SWIG предоставляет механизм typemaps - трансляция С/С++ типа в тип целевого языка, т.е. теоретически можно использовать контейнеры STL в коде, который будет использоваться Руби, но это оставлю до следующего раза.
Существуют ли конструкторы копирования???
конструкторы копирования... конструкторы копирования... конструкторы копирования... конструкторы копирования... конструкторы копирования... после того, как я повторил это слово 5 раз, каждый раз слыша в ответ "Что-то я не пойму о чем Вы...", сомнения начали одолевать и меня. Действительно, о чем это я??? Может чего напутал?
Только что проводил собеседования у человека, который позиционирует себя (фраза "позиционирует себя" почему то всегда меня коробит) как С++ Синьер Девелопер. Попросил нарисовать конструктор копирования и оператор присваивания для простого класса... В итоге он нарисовал только оператор присваивания, а на счет КК сказал, что такого не существует и в помине, всегда используется оператор копирования, мало того, такой код просто не правильный:
view plainprint?
class A
{
std::string m_name;
public:
A() {}
};
A a;
A a2 = a; // здесь будет жутко не доволен компилятор...
Когда он не поверил мне про КК в векторах... я начал сомневаться сам, разве может человек с 8-ми летним С++ным опытом не слышать о них... Блин, я даж после собеседования написал класс с конструктором копирования
Все таки КК существуют :))
**********
Современная уличная мода многогранна и постоянно подвержена изменениям. Но несмотря на это, мало кто может утверждать, что кроссовки nike со временем теряют популярность.
boost::any изнутри
boost::any позволяет хранить переменные различных типов без явного указания типа, например так:
view plainprint?
double dl = 1.2;
std::string str = "aa";
boost::any a = dl;
boost::any b = str;
boost::any c = 232;
std::vector<boost::any> v;
// v.push_back("dddd"); так поступать нельзя
v.push_back(std::string("dddd"));
v.push_back(45.23);
v.push_back(4);
for(unsigned i=0;i < v.size();i++)
std::cout << v[i].type().name() << std::endl;
В том, что boost::any позволяет создавать вектор "зоопарка-элементов" нет ничего удивительного. Реализация этого класса предельна проста - он просто напросто хранит указатель на класс-холдер конкретного типа. Самое примечательное, что есть в boost::any - это шаблонный конструктор и шаблонный оператор присваивания:
view plainprint?
class any
{
public:
//...
template<typename ValueType>
any(const ValueType& value)
: content(new holder<ValueType>(value))
// holder<ValueType> - реализация хранителя для конкретного типа
{}
//...
template<typename ValueType>
any& operator=(const ValueType& rhs)
{
any(rhs).swap(*this);
return *this;
}
//...
private:
// интерфейсный классс для хранителя
placeholder * content;
};
Сразу же надо уточнить, что шаблонный конструктор и шаблонный оператор присваивания не могут быть конструктором копирования и оператором присваивания (их нужно определять отдельно). Эти шаблонные методы позволяют производить присваивание и инициализацию объекта класса boost::any без явного указания типа. Такой подход очень удобен и применяется в stl-контейнерах и различных smart-pointer'ах. Есть даже такая идеома - Coercion by Member Template.
Тип содержимого указывается только при получения значения из контейнера типа. Для этого boost предоставляет шаблонную функцию boost::any_cast, которая сранивает желаемый и реальный типы на основании std::type_info, и в зависимости от результата либо получает конкретное значение у холдера, используя static_cast, либо бросает исключение.
Вот так вот просто и красиво решается задача хранения значений различного типа в одном контейнере.
ЗЫ: забыл как пишется изнутри, вместе или раздельно...
Внешне указатели, сокеты внутри
Программирование сетевых и многозадачных приложений довольно трудная задача, так как в данной области довольно часто возникают нетривиальные ошибки, которые очень редко проявляются и их очень затруднительно воспроизвести. Типичный пример - потеря соединения. Пусть у нас имеется следующий код:
#define COMMAND_PORT 2021
//Функция обработки принятого соединения
void* ProcessConnection(void*p){
/*делаем что-нибудь полезное*/
int rval=0;
close(*((int*)p));
pthread_exit((void*)rval);
}
//
int main(int argc,char**argv){
//создаем сокет
int sd = socket( PF_INET, SOCK_STREAM, 0 );
if ( sd == -1 ) {
perror( "Error create socket: " );
return EXIT_FAILURE;
}
struct sockaddr_in addr;
bzero( &addr, sizeof( addr ) );
addr.sin_family = AF_INET;
addr.sin_port = htons( COMMAND_PORT );
addr.sin_addr.s_addr = INADDR_ANY;
int addr_size = sizeof( struct sockaddr_in );
//привязываем адрес к сокету
if ( bind( sd, ( struct sockaddr * ) & addr, addr_size ) != 0 ) {
perror( "Error assigning address to socket: " );
return EXIT_FAILURE;
}
//выражаем готовность принимать запросы на соединеие
if ( listen( sd, 5 ) != 0 ) {
perror( "Error create listener queue: " );
return EXIT_FAILURE;
}
//далее извлекаем запросы на соединение и для обработки
//каждого такого соединения создаем поток
while ( 1 ) {
int cld=accept(sd,(struct sockaddr*)&addr,(socklen_t*)&addr_size);
if ( cld ==-1 ) {
perror("Error accepting with client: ");
return EXIT_FAILURE;
} else {
pthread_t tchild;
if(pthread_create(&tchild,NULL,&ProcessConnection,(void*)&cld )!= 0){
perror("Error create thread: " );
close( cld );
return EXIT_FAILURE;
}
}// if-else
}// while
return EXIT_SUCCESS;
}
На первый взгляд этот код не содержит ошибок и будет работать правильно в большинстве случаев. Но при большой нагрузке на этот сервер часть соединений будет утеряна. Почему? Если обратить внимание на следующую строчку
pthread_create( &tchild, NULL, &ProcessConnection,(void*) &cld );
то можно увидеть, что файловый дескриптор созданный accept(); передается в обрабатывающую функцию не по значению, а по адресу. В действительности неизвестно как операционная система распланирует выполнение потоков и вполне возможно что поток обработки соединения начнет выполнятся тогда, когда будет принят следующий запрос на соединение. А поскольку передается адрес дескриптора, то в данном случае первое принятое соединение будет потеряно (кроме того дескриптор не будет закрыт). Более того возможно возникновение ситуации когда два потока будут обрабатывать одно и то же соединение, что естественно ничего хорошего не принесет. Решение как всегда лежит на поверхности: следует изменить чуть-чуть функцию обработки соединения и передавать не адрес дескриптора а его значение:
//Функция обработки принятого соединения
void* ProcessConnection(void*p){
/*делаем что-нибудь полезное*/
int rval=0;
close((int)p);
pthread_exit((void*)rval);
}
int main(int argc,char**argv){
/*.........*/
//далее извлекаем запросы на соединение и для обработки
//каждого такого соединения создаем поток
while ( 1 ) {
int cld=accept(sd,(struct sockaddr*)&addr,(socklen_t*)&addr_size);
if ( cld ==-1 ) {
perror("Error accepting with client: ");
return EXIT_FAILURE;
} else {
pthread_t tchild;
if(pthread_create(&tchild,NULL,&ProcessConnection,(void*)cld )!= 0) {
perror("Error create thread: " );
close( cld );
return EXIT_FAILURE;
}
}//if-esle
}//while
/*.........*/
}
[Qt собирается долго-долго]
5. Последний шаг - установка переменных окружения:
QTPATH - путь к директории Qt;
QMAKESPEC - шаблон для qmake, ставим win32-msvc2005;
Path - добавляем Qt\bin.
Qt (version 4.3.4 OpenSource) + MSVC 2005
1. Распаковываем архив с исходниками куда удобно, например, в C:\Qt.
2. Запускаем терминал (с предустановленными MSVC переменными окружения):
Start -> All Programms -> Microsoft Visual Studio 2005 -> Visual Studio Tools -> Visual Studio 2005 Command Promt
3. Переходим в директорию с Qt и выполняем команду:
configure.exe -platform win32-msvc
Прим.:
в принципе, можно просто запустить configure.exe - тогда всё будет по-умолчанию, но у меня параллельно установлена версия Qt+MinGW, поэтому configure автоматом цепляется за g++ (из-за установленных при установке Qt+MinGW системных переменных окружения), так что лучше указывать руками платформу-компилятор для сборки.
Так же можно посмотреть большое количество различных опций сборки Qt при помощи команды configure.exe --help.
4. Следующий шаг - непосредственно сборка, одна команда:
nmake
Фан-клуб автомобилей Volvo подскажет вам, где располагается ближайший в вашей местности автосалон Вольво, а также станция технического обслуживания моделей шведской марки.
Using .NET Controls from WinAPI/MFC/WTL applications (Part 1)
До появления .NET графические приложения разрабатывались с использованием узкого круга инструментов и библиотек. Самыми популярными были Borland Delphi/C++ Builder (VCL), Microsoft Visual Studio (WinAPI, MFC, WTL) и QT. Инфраструктура Borland позволяла сторонним разработчикам создавать библиотеки графических елементов управления, которые хорошо интегрировались в IDE и позволяли быстро "рисовать" красивые, удобные интерфейсы. Microsoft Visual Studio на тот момент была по всем параметрам хуже - слабенький компилятор, не поддерживающий актуальные стандарты, отсутствие полноценного дизайнера форм, плохо спроектированная обьектная модель базовых библиотек (а их расширения можно перечислить на пальцах одной страусиной ноги). Выход Visual Studio 2002/2003 (официальное рождение .NET) сильно повлиял на чашу весов - все больше проектов пишут под .NET. Borland решил не отставать и включил поддержку управляемого кода в свою IDE, правда это не поменяет текущее аутсайдерское положение.
Единственный механизм, который позволяет подружить функционал различных платформ (речь о платформах разработки, а не ОС), - COM/ActiveX. Ни у кого не вызывает сомнений факт возможности интеграции компонента VCL-ActiveX в приложение MFC или наоборот. Оказывается, .NET тоже позволяет пользоваться этим механизмом на полную. Я рассмотрю лишь случай использования .NET-обьектов из неуправляемого кода (C++/CLI является управляемым!), так как обратный вариант взаимодействия прост и не нуждается в разжевывании. Существует 2 способа достижения поставленной цели:
Классический - использование .NET через COM/ActiveX.
Анальный - непосредственный хостинг CLR и использование COM-подобных механизмов взаимодействия.
Каждый рассмотрим детально. Для понимания того, что написано далее, необходимы твердые знания технологии COM! Мы напишем элемент управления .NET WinForms (ComplexControl), который попытаемся использовать из неуправляемого приложения следующим образом:
Создать екземпляра обьекта
Добавить элемент управления в форму или диалог
Вызывать методы обьекта, читать и записывать его свойства
Подписываться на уведомления и получать их через callback-интерфейс
Диаграмма классов следующая:
Часть первая. COM-Interop в .NET.
Любой тип MySuperDotNetType (класс MySuperDotNetClass, структура MySuperDotNetStruct, перечисление MySuperDotNetEnum, инртерфейс IMySuperDotNetInterface или делегат MySuperDotNetDelegate) при определенных условиях может быть доступен посредством COM. Эти условия также влияют на видимость полей структур, методов и свойств классов. Минимальный набор таков:
Область видимости элемента - public.
Наличие атрибута [ComVisible(true)] в обьявлении самого элемента либо в его родительской области (сборка в целом или тип, в котором находится обьявление).
Также его можно использовать для сокрытия ([ComVisible(false)]) полей структуры, методов и свойств класса, а также внутренних типов.
Далее любой элемент CLR, удовлетворяющий этим требованиям, будем называть ComVisible.
Дополнительно можно использовать следующие атрибуты для более тонкой настройки взаимодействия:
[Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")] - CLSID CO-класса, IID интерфейса, ID библиотеки типов (в зависимости от контекста атрибута - класс, интерфейс, сборка)
[ProgId("MyHumanReadableTypeName")] позволяет задавать ProgID для типа MySuperDotNetType
[ClassInterface(ClassInterfaceType.XXX)] определяет вид экспортируемого интерфейса класса (далее - класс-интерфейс) MySuperDotNetClass. Перечисление ClassInterfaceType содержит 3 значения:
AutoDispatch
Означает, что класс будет явно поддерживать исключительно позднее связывание через свой dispinterface. Библиотека типов, которую создает утилита Tlbexp.exe (о ней - чуть позже), не содержит информацию о "начинке" этого интерфейса - свойствах и методах. Это сделано для предотвращения кеширования (когда они подставляются непосредственно в вызовы метода Invoke компилятором) клиентами значений DISPID. Является значением по умолчанию.
Вот, как в этом случае выглядит IDL для CO-класса ComplexControl:
[
uuid(2D7FDCB4-6C3F-4529-A93D-10DFC72927B1),
version(1.0),
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, NetControlsLibrary.ComplexControl)
]
coclass ComplexControl {
[default] interface _ComplexControl;
interface _Object;
interface IComponent;
interface IDisposable;
interface IWin32Window;
interface IComplexView;
[default, source] dispinterface IComplexView_Events;
};
Обратите внимание, что CO-класс ComplexControl явно поддерживает итерфейс _Object, который на самом деле - дуальный dispinterface:
[
uuid(65074F7F-63C0-304E-AF0A-D51741CB4A8D),
hidden,
dual,
nonextensible,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, System.Object)
]
dispinterface _Object {
properties:
methods:
[id(00000000), propget, custom(54FC8F55-38DE-4703-9C4E-250351302B1C, 1)]
BSTR ToString();
[id(0x60020001)]
VARIANT_BOOL Equals([in] VARIANT obj);
[id(0x60020002)]
long GetHashCode();
[id(0x60020003)]
_Type* GetType();
};
Обратите внимание, что интерфейс _ComplexControl - это dispinterface, а в CO-классе обьявлен, как обычный:
[
uuid(6148B23F-BFFE-344F-809A-FF0915CC4C5B),
hidden,
dual,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, NetControlsLibrary.ComplexControl)
]
dispinterface _ComplexControl {
properties:
methods:
};
Важно:
Такой диспинтерфейс позволяет использовать исключительно ComVisible методы и свойства класса такие, что C#-выражение myClassInstance.SomeMemberIWantToUseFromIDispatch не вызывает ошибку компиляции. К примеру, методы интерфейсов с явной реализацией сюда не попадают. Другими словами, он еквивалентен общедоступному интерфейсу (здесь речь идет концепции, а не о ключевом слове interface) класса.
Возможности явно задать IID для него нет, поэтому во время разработки можно смело ожидать сюрпризов в виде E_NOINTERFACE.
AutoDual
Означает, что интерфейс класса будет дуальным дисп-интерфейсом (dual dispinterface). Анологично предыдущему варианту + возможность использовать интерфейс класса через стандартный vtbl-механизм + в библиотеку типов попадает информация об экспортируемых свойствах и методах (попадающих под вышеизложенное правило). Использовать этот механизм не рекомендуют из-за проблем с версионностью (изменение таблицы виртуальных функций после добавления или удаления методов класса).
IDL для CO-класса ComplexControl анологичен, а вот интерфейс _ComplexControl выглядит монстрообразно:
[
uuid(6148B23F-BFFE-344F-809A-FF0915CC4C5B),
hidden,
dual,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, NetControlsLibrary.ComplexControl)
]
dispinterface _ComplexControl {
properties:
methods:
[id(0x60020000), propget,
custom(54FC8F55-38DE-4703-9C4E-250351302B1C, 1)]
BSTR ToString();
[id(0x60020001)]
VARIANT_BOOL Equals([in] VARIANT obj);
[id(0x60020002)]
long GetHashCode();
[id(0x60020003)]
_Type* GetType();
[id(0x60020004)]
VARIANT GetLifetimeService();
[id(0x60020005)]
VARIANT InitializeLifetimeService();
[id(0x60020006)]
_ObjRef* CreateObjRef([in] _Type* requestedType);
[id(0x60020007), propget]
ISite* Site();
[id(0x60020007), propputref]
void Site([in] ISite* rhs);
[id(0x60020009)]
void add_Disposed([in] _EventHandler* value);
// Last 1000000 lines omited for brevity... ![]()
};
None
Означает, что интерфейс для класса не генерируется. Рекомендуемое значение.
[InterfaceType(ComInterfaceType.XXX)] - по аналогии с предыдущим, только касательно .NET-интерфейсов.
[ComDefaultInterface(typeof(IMySuperDotNetInterface))] указывает на интерфейс по-умолчанию CO-класса. В IDL - default.
[ComSourceInterfaces(typeof(IMySuperDotNetInterface_Events))] присоединяет Sink-интерфейс (добавляет в класс Connection Point). В IDL - source. О связи этих интерфейсов с классом - далее.
[DispId(1234)] - явно заданный DISPID для элемента дисп-интерфейса (свойства или метода).
Прочие атрибуты из пространства имен System.Runtime.InteropServices.
С помощью интроспекции CLR способен спроектировать обьектную модель на COM и создать на лету объект (так называемый CCW - COM Callable Wrapper), совместимый с конвенциями вызова __stdcall и по структуре памяти совпадающий с vtbl-интерфейсами. CCW выглядит, как обычный COM-объект, но транслирует все вызовы в упраыляемую среду. А утилита Tlbexp.exe (Type Library Exporter) способна создать для сборки библиотеку типов (Type Library) в виде tlb-файла, который можно просмотреть с помощью программы OleView (идет в комлекте со студией по адресу C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\Bin\OleView.Exe).
Алгоритм отображения сборки на IDL сложен и часто просто непонятен. Более полную модель можно увидеть с помощью OleView, заглянув в категорию ".NET Category" (насколько я понимаю, эту информацию .NET-обьекты отдают непосредственно через IDispatch::GetTypeInfo). Вот несколько интересных и важных моментов, которые стоит знать:
Информация tlb-файла является порядочно урезанной версией реальной обьектной модели, которую строит CLR.
Каждый .NET COM-обьект поддерживает интерфейсы IUnknown, IDispatch, _Object, IConnectionPointContainer, IProvideClassInfo, ISupportErrorInfo, IManagedObject.
ComplexControl дополнительно поддерживает интерфейсы _Component, _ContainerControl, _Control, _MarshalByRefObject, _ScrollableControl, _UserControl, IComponent, IOleControl, IOleInPlaceActiveObject, IOleInPlaceObject, IOleObject, IOleWindow, IPersist, IPersistPropertyBag, IPersistStorage, IPersistStreamInit, IQuickActivate, IViewObject, IViewObject, IWin32Window. Судя по этому списку можно сделать следующие выводы.
Каждый .NET COM-обьект поддерживает все ComVisible интерфейсы своих предков.
Каждый .NET COM-обьект поддерживает все ComVisible класс-интерфейсы своих предков (с учетом атрибута ClassInterfaceType, конечно-же).
Каждый .NET WinForms Control COM-обьект поддерживает множество интерфейсов (если не все) OLE/ActiveX. Соответственно, может быть использован, как OLE/ActiveX.
Для того, чтобы использовать библиотеки посредством классического COM, их нужно зарегистрировать.
Статической регистрацией занимается утилита RegAsm.exe (C:\Windows\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe). Она выполняет инструментирование сборки (аналогичным Tlbexp.exe образом) и вносит записи в системный реестр. Запуск RegAsm.exe можно возложить на Visual Studio - в настройках проекта в разделе Build поставить галочку [Register for COM interop]. Запись в реестре для CO-класса ComplexControl выглядит следующим образом:
[HKEY_CLASSES_ROOT\CLSID\{2D7FDCB4-6C3F-4529-A93D-10DFC72927B1}]
@="NetControlsLibrary.ComplexControl"
[HKEY_CLASSES_ROOT\CLSID\{2D7FDCB4-6C3F-4529-A93D-10DFC72927B1}\Implemented Categories]
[HKEY_CLASSES_ROOT\CLSID\{2D7FDCB4-6C3F-4529-A93D-10DFC72927B1}\Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}]
[HKEY_CLASSES_ROOT\CLSID\{2D7FDCB4-6C3F-4529-A93D-10DFC72927B1}\InprocServer32]
@="mscoree.dll"
"Assembly"="NetControlsLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
"Class"="NetControlsLibrary.ComplexControl"
"CodeBase"="file:///D:/Development/Projects/NETComInterop/Debug/NetControlsLibrary.dll"
"RuntimeVersion"="v2.0.50727"
"ThreadingModel"="Both"
[HKEY_CLASSES_ROOT\CLSID\{2D7FDCB4-6C3F-4529-A93D-10DFC72927B1}\InprocServer32\1.0.0.0]
"Assembly"="NetControlsLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
"Class"="NetControlsLibrary.ComplexControl"
"CodeBase"="file:///D:/Development/Projects/NETComInterop/Debug/NetControlsLibrary.dll"
"RuntimeVersion"="v2.0.50727"
[HKEY_CLASSES_ROOT\CLSID\{2D7FDCB4-6C3F-4529-A93D-10DFC72927B1}\ProgId]
@="NetControlsLibrary.ComplexControl"
Здесь {2D7FDCB4-6C3F-4529-A93D-10DFC72927B1} - CLSID моего класса ComplexControl, {62C8FE65-4EBB-45e7-B440-6E39B2CDBF29} в ключе "Implemented Categories" - GUID категории компонента (".NET Category" в нашем случае), 1.0.0.0 в ключе "InprocServer32" - версия класса (поддержка нескольких версий компонента). Стоит отметить, что InprocServer32 указывает на mscoree.dll! Именно там находится точка входа в фабрику классов - функция DllGetClassObject. Ну, а остальные записи ниже InprocServer32 служат для нахождения нужной сборки с типом (читает их, конечно-же, не подсистема COM, а CLR).
Динамически зарегистрировать поможет класс TypeLibConverter.
Утилита regsvcs.exe тоже годится, но это другой конек, связанный с COM+.
Для добавдения в CO-класс точки подключения (эта часть была самая сложная в инвестигации!) необходимо выполнить следующие действия:
Создать интерфейс IMySuperDotNetClass_Events
Настроить COM-аспекты - [ComVisible(true)], [Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")] и [InterfaceType(ComInterfaceType.XXX)] (рекомендуется использовать InterfaceIsIDispatch, обьяснение дальше).
Наполнить интерфейс обьявлениями методов, совместимыми с типами-делегатами поддерживаемых событий.
Добавить в класс MySuperDotNetClass определения событий с именами, которые соответствуют именам функций интерфейса IMySuperDotNetClass_Events.
Лучше это показать на примере:
Интерфейс (открытый) и делегат (область видимости не имеет значения).
internal delegate void PropertyChangedEventHandler(Object src, String propertyName);
[ComVisible(true)]
[Guid("FD9AEC7A-3688-4394-B4D0-636E4A7FE3B9")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] // if InterfaceIsDual then SinkObj must also be dual!
public interface IComplexView_Events
{
[DispId(0x01)]
void PropertyChanged(Object src, String propertyName);
}
Тело класса ComplexControl.
#region IComplexView_Events Mappings
private PropertyChangedEventHandler _propertyChanged;
internal event PropertyChangedEventHandler PropertyChanged {
add {
_propertyChanged += value;
Trace.WriteLine("PropertyChanged.add(...)");
}
remove {
_propertyChanged -= value;
Trace.WriteLine("PropertyChanged.remove(...)");
}
}
#endregion
*******
В наш прогрессивный век чтобы купить билет на поезд или самолет не требуется ехать на вокзал и стоять в очереди: достаточно поднять трубку и позвонить в интернет магазин, который все сделает за вас. О том, какие магазины торгуют билетами в вашем регионе, расскажет каталог Uashops.com.
JVM Hosting
Chabster Wednesday, January 2, 2008 C++ & Java & skeleton
Хостинг усправляемого кода в программных продуктах уже не редкость. Oracle поддерживает выполнение байткода JVM еще с 8-й версии и периодически выполняет апгрейд виртуальной машины (11g поставляется с версией 1.5). Сравнительно недавно добавили поддержку .NET 1.1 и .NET 2.0. Microsoft SQL Server 2005 имеет поддержку CLR 2.0 и, кстати, внутренняя архитектура последнего была усовершенствована (скорее "заточена") под задачи хостинга. Дальше я расскажу, как добавить возможность выполнять JVM-код в приложение, написанное на С++.
Подготовка проекта для хостинга виртуальной машины Java.
Первое, что необходимо сделать, - поставить Java Development Kit версии 6. Далее в настройках проекта прописать папку поиска заголовочных файлов JDK - "C:\Program Files\Java\jdk1.6.0_03\include\", папку с библиотечными файлами - "C:\Program Files\Java\jdk1.6.0_03\lib" и добавить путь поиска jvm.dll - C:\Program Files\Java\jdk1.6.0_03\jre\bin\client\ как написано здесь. Еще нужно закинуть файл "c:\Program Files\Java\jdk1.6.0_03\include\win32\jni_md.h" в папку с проектом и включить его в проект. Традиционно изменяем stdafx.h:
#include <jni.h>
#pragma comment(lib, "jvm.lib")
Инициализация виртуальной машины Java.
Перед использованием, виртуальную машину Java необходимо загрузить и инициализировать. По-сути, она является песочницей (sandbox) для выполняемого байт-кода, другими словами, мини-операционной системой в пределах хост-процесса. Инициализация предполагает создание управляющих структур, выделение пулов памяти и других ресурсов ОС, связанных с конкретным екземпляром JVM (да, их можно создать несколько).
jint jRet;
JavaVMOption options[1];
options[0].optionString = "-verbose:class,gc,jni";
JavaVMInitArgs jvmInitArgs;
jvmInitArgs.version = JNI_VERSION_1_6;
jvmInitArgs.nOptions = _countof(options);
jvmInitArgs.options = options;
jvmInitArgs.ignoreUnrecognized = JNI_TRUE;
JavaVM *pJvm;
JNIEnv *pEnv;
jRet = JNI_CreateJavaVM(&pJvm, reinterpret_cast<LPVOID*>(&pEnv), &jvmInitArgs);
if (jRet != JNI_OK) {
// Это залет, солдат!
cout << "JNI_CreateJavaVM returned " << jRet << endl;
return(jRet);
}
Структура JavaVMInitArgs содержит настройки создания екземпляра JVM - запрашиваемую версию и различные конфигурационные параметры. Функция JNI_CreateJavaVM, как нетрудно догадаться из ее названия, создает виртуальную машину и возвращает указатель на управляющий интерфейс - JNIEnv. Любые дальнейшие манипуляции выполняются посредством него.
Выполнение байт-кода JVM.
Не буду оригинален. Вашему вниманию представляется вариант "Hello World" в исполнении C++/Java:
void printHelloWorld(JNIEnv *pEnv) {
jclass classSystem = pEnv->FindClass("java/lang/System");
jfieldID fieldId = pEnv->GetStaticFieldID(classSystem, "out", "Ljava/io/PrintStream;");
jobject system_out = pEnv->GetStaticObjectField(classSystem, fieldId);
jclass classPrintStream = pEnv->FindClass("java/io/PrintStream");
jmethodID methodId = pEnv->GetMethodID(classPrintStream, "println", "(Ljava/lang/String;)V");
jvalue v;
v.l = pEnv->NewStringUTF("Hello World!");
pEnv->CallVoidMethodA(system_out, methodId, &v);
if (jthrowable ex = pEnv->ExceptionOccurred()) {
pEnv->ExceptionDescribe();
pEnv->ExceptionClear();
}
}
Завершение работы.
По завершению работы с JVM рантайм нужно удалить:
jRet = pJvm->DestroyJavaVM();
if (jRet != JNI_OK) {
cout << "pJvm->DestroyJavaVM() returned " << jRet << endl;
return(jRet);
}
TOKEN_PRIVILEGES oldTokenPrivileges = { 0 };
HANDLE impersonationToken = NULL;
HANDLE userToken = NULL;
LPVOID pEnvironment = NULL;
PROCESS_INFORMATION processInformation = { 0 };
__try {
bRet = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &processToken);
if (!bRet) {
hr = GetLastError();
return hr;
}
// This step might not be necessary because SeTcbPrivilege is enabled by default for Local System
LUID luid;
bRet = LookupPrivilegeValue(NULL, _T("SeTcbPrivilege"), &luid);
if (!bRet) {
hr = GetLastError();
return hr;
}
TOKEN_PRIVILEGES adjTokenPrivileges = { 0 };
adjTokenPrivileges.PrivilegeCount = 1;
adjTokenPrivileges.Privileges[0].Luid = luid;
adjTokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
DWORD dwOldTPLen;
bRet = AdjustTokenPrivileges(processToken, FALSE, &adjTokenPrivileges, sizeof(TOKEN_PRIVILEGES), &oldTokenPrivileges, &dwOldTPLen);
if (bRet) {
hr = GetLastError();
if (hr == ERROR_SUCCESS);
else if (hr == ERROR_NOT_ALL_ASSIGNED) {
// Enabled by default
}
}
else {
hr = GetLastError();
return hr;
}
DWORD conSessId = WTSGetActiveConsoleSessionId();
if (conSessId == 0xFFFFFFFF) {
// There is no session attached to the console
return ERROR_SUCCESS;
}
bRet = WTSQueryUserToken(conSessId, &impersonationToken);
if (!bRet) {
hr = GetLastError();
return hr;
}
bRet = DuplicateTokenEx(impersonationToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &userToken);
if (!bRet) {
hr = GetLastError();
return hr;
}
STARTUPINFO si = { 0 };
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = _T("winsta0\\default");
bRet = CreateEnvironmentBlock(&pEnvironment, userToken, TRUE);
if (!bRet) {
hr = GetLastError();
return hr;
}
bRet = CreateProcessAsUser(userToken, _T("C:\\Windows\\notepad.exe"), NULL, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, pEnvironment, NULL, &si, &processInformation);
if (!bRet) {
hr = GetLastError();
return hr;
}
}
__finally {
if (processInformation.hThread) {
CloseHandle(processInformation.hThread);
}
if (processInformation.hProcess) {
CloseHandle(processInformation.hProcess);
}
if (pEnvironment) {
bRet = DestroyEnvironmentBlock(pEnvironment);
}
if (userToken) {
CloseHandle(userToken);
}
if (impersonationToken) {
CloseHandle(impersonationToken);
}
if (processToken) {
bRet = AdjustTokenPrivileges(processToken, FALSE, &oldTokenPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
CloseHandle(processToken);
}
}
Код функций состоит исключительно из ассемблерных инструкций, поэтому они объявлены, как __declspec(naked). Конвенция вызова __fastcall подарит нам единственный аргумент (адрес состояния блокировки) в регистре ecx. xchg - атомарная инструкция, которая в нашем случае меняет местами значение в регистре eax (где всегда хранится единица) и по адресу памяти, который хранится в ecx. В результате выполнения одной неделимой инструкции, мы получим в eax нолик, если блокировка успешно захвачена. Состояние блокировки изменится соответственно. Если блокировка занята, в eax будет находится единица. Оставшиеся инструкции проверяют регистр eax и реагируют на успех или провал захвата блокировки.
Вы, наверное, не заметили, что в коде используется не xchg, а lock xchg. Это потому, что у меня многопроцессорная машина и две инструкции xchg могут выполнятся одновременно. Чтобы их синхронизировать, применяется конструкция lock, которая заставляет блокировать шину памяти на время выполнения инструкции.
Использование spinlock
Спин блокировку следует использовать исключительно:
Для синхронизации действительно маленьких кусков исходного кода.
Поток, ждущий освобождения блокировки, выполняет бесполезную работу, соответственно, расходует процессорное время. Основная цель - потребить меньше ресурсов, чем потребуется для использования примитивов синхронизации операционной системы, тоесть для переключения контекста выполнения.
На многопроцессорной машине.
Поскольку на однопроцессорной машине два потока не могут выполнятся одновременно, поток, который владеет блокировкой, во время попыток ее захвата другим потоком приостановлен и не выполняет никаких действий по освобождению ресурса. Вообще, можно добиться отсутствия переключений контекста потока в момент, когда блокировка захвачена. Но, это очень сложно контролировать.
Proof of concept
Следующим кодом я проверял корректность работы spinlock:
// CppTest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <Windows.h>
__declspec(naked) void __fastcall SpinLock(int *cookie) {
__asm {
mov eax, 1
spinLoop:
lock xchg eax, [ecx]
test eax, eax
jnz spinLoop
ret
}
}
__declspec(naked) void __fastcall SpinUnlock(int *cookie) {
__asm {
mov eax, 0
lock xchg eax, [ecx]
ret
}
}
int lockCookie = 0;
int i = 0;
int j = 0;
DWORD WINAPI Thread1(LPVOID lpParameter) {
while (true) {
SpinLock(&lockCookie);
i++;
j++;
if (i != j)
DebugBreak();
SpinUnlock(&lockCookie);
}
}
DWORD WINAPI Thread2(LPVOID lpParameter) {
while (true) {
SpinLock(&lockCookie);
i++;
j++;
if (i != j)
DebugBreak();
SpinUnlock(&lockCookie);
}
}
int _tmain(int argc, _TCHAR* argv[])
{
InterlockedIncrement(reinterpret_cast<volatile LONG*>(&argc));
DWORD thread1Id;
DWORD thread2Id;
HANDLE hThread1 = CreateThread(NULL, 100, Thread1, NULL, 0, &thread1Id);
HANDLE hThread2 = CreateThread(NULL, 100, Thread2, NULL, 0, &thread2Id);
Sleep(INFINITE);
return(0);
}
Кстати, глянув на ассемблерный код функции InterlockedIncrement я убедился, что на процессорах Core 2 Duo нужно использовать блокировку шины памяти. Как и на многопроцессорных станциях.
Зачем нужна операционная система, если блокировки так элементарно пишутся вручную?!
А затем, что ассемблерный код не является переносимым даже в пределах одной архитектуры! Скажем, будь у меня однопроцессорная машина, я бы не использовал конструкцию lock и мой код бы не работал корректно на двуядерном процессоре! Кстати, просматривая исходники Windows 2000 я заметил, что реализация всех Interlocked операций завязана на архитектуре и мультипроцессорности. Отсюда и непереносимость утановленной винды на другую платформу. Версий ядра то несколько!
2 comments
Type system covariance and contravariance
Chabster Thursday, January 17, 2008 C# & C++ & Java & programming languages
Полагаю, многие разработчики не знают, что такое ковариантность/контравариарность системы типов. К примеру, в версии 1.5 языка Java появилась возможность использовать тип класса-наследника в объявлении замещающей функции:
0: /**
1: *
2: */
3: package com.chabster.covariance;
4:
5: /**
6: * @author Chabster
7: */
8: public class Container
9: {
10: class A
11: {
12: A some() {
13: return (this);
14: }
15: }
16:
17: class B extends A
18: {
19: @Override
20: B some() {
21: return (this);
22: }
23: }
24:
25: }
В С++ такая возможность существует уже давно, а вот в C# все еще отсутствует. Полагаю, это связано с нестыковкой сигнатур методов в C# и IL. Вот, что говорит по этому поводу Standard ECMA-335 Common Language Infrastructure (CLI) 4th edition (June 2006):
"
A method signatures is composed of
a calling convention,
the number of generic parameters, if the method is generic,
a list of zero or more parameter signatures—one for each parameter of the method—and,
a type signature for the result value, if one is produced.
"
Получается, что IL код различает методы, которые отличаются лишь типом возврата, а C# - нет. Как и любой существующий на данный момент управляемый .NET-язык. С другой стороны этот недостаток можно элементарно восполнить. Вариант реализации предлогаю расценивать, как домашнее задание для читателя :).
В некоторых моментах C#, все же, поддерживает ковариантность и контравариантнось. Но для начала выясним что это такое.
Ковариантность (covariance) и контравариантнось (contravariance)
Ковариантностью называют сохранение формы при преобразовании. Соответсвенно, ковариантным называется преобразование, сохраняющее форму (свойства).
Например, оператор F(x) = x*2 является ковариантным касательно отношения % (делимость). Т.е. из x%y следует, что F(x)%F(y).
Контравариантностью называют обращение формы при преобразовании. Соответсвенно, контравариантным называется преобразование, обращающее форму.
Например, оператор F(x) = -x является контравариантным касательно отношения > (больше). Т.е. из x>y следует, что F(x)<F(y).
Оба этих понятия нельзя правильно обобщить поскольку в каждой науке оно выглядит по-своему. Рассмотрим их в самой интересной для нас области - программирование!
Ковариантность и контравариантнось в системе типов языков программирования
Рассмотрим пример C# кода:
class A {
A[] aArr = new B[] { };
}
class B : A {
}
Пусть « обозначает отношение "is a" для типов. Поэтому B«A. Оператор Arrize({T}) = {T[]} является ковариантным касательно отношения "is a" для ссылочных типов т.к. из B«A следует B[]«A[].
Возвращаясь к первому Java примеру можно сказать, что оператор Methodize({SuperT virtMethod(T1,T2,...)}) = {SubT virtMethod(T1,T2,...)} является ковариантным касательно отношения override между методами.
Ковариантность и контравариантнось в системе типов C#
В C# есть ковариация, связанная с делегатами:
class A {
}
class B : A {
}
delegate A SomeDelegate();
static B SomeMethod() { return (null); }
static SomeDelegate sd = SomeMethod;
Типы возвращаемых значений метода и делегата различны, но совместимы.
В C# есть и контравариация, связанная с делегатами:
class A {
}
class B : A {
}
delegate void SomeDelegate(B b);
static void SomeMethod(A a) { }
static SomeDelegate sd = SomeMethod;
Сигнатуры метода и делегата различны, но совместимы, правда, уже в обратном направлении.
Это все, конечно, хорошо, вот только...
class A {
}
class B : A {
}
delegate T SomeDelegate<T>();
static B SomeMethod() { return (null); }
static SomeDelegate<B> sdB = SomeMethod;
static SomeDelegate<A> sdA = sdB;
... последняя строчка вызывает ошибку компиляции.
В целом, вариантность - очень полезный механизм, если им правильно пользоваться.
********
Действительно качественные карбоновые чехлы для iPhone, которые будут отлично гармонировать с внешностью телефона от Apple, вы можете обнаружить на сайте Comunicado.ru
Если вы хотите купить определенный товар через интернет в Ижевске, но не знаете какие интернет-магазины занимаются его продажей, вам на выручку придет портал интернет новостей г.Ижевска, располагающийся по адресу Wmizhevsk.ru.
Если вас заботит то, какой будет погода в Турции в ближайшем будущем – рекомендую навестить сайт weather.turmir.com.
Run as interactive user from service
The code below runs a program on interactive desktop with logged on user privileges, as it was started by user himself. Must be executed by Local System, for example, by Windows service.
stdafx.h:
#include <WtsApi32.h>
#pragma comment(lib, "WtsApi32.lib")
#include <Userenv.h>
#pragma comment(lib, "Userenv.lib")
RunAsInteractiveUser function:
BOOL bRet;
HRESULT hr;
HANDLE processToken = NULL;
