Форум программистов, компьютерный форум, киберфорум
Наши страницы

C++

Войти
Регистрация
Восстановить пароль
 
 
Рейтинг: Рейтинг темы: голосов - 19, средняя оценка - 4.74
Izual
94 / 119 / 6
Регистрация: 13.11.2012
Сообщений: 1,555
#1

Альтернативный вызов функции - C++

15.08.2014, 10:44. Просмотров 2835. Ответов 66
Метки нет (Все метки)

1. Интересует метод вызова функции через указатель(или по другому).
2. Интересует метод взятия кол-ва аргументов функции и их типов, а так же тип возвращяемого значения.

По второму пункту вообще ничего не нашёл, а по первому есть некоторые вопросы.
C++
1
2
3
4
5
6
7
8
9
#include "FTD2XX.h" // библиотека от FTDI
typedef FT_STATUS (*pFT_Open) (int, FT_HANDLE *); // тип данных "функция FT_OPEN"
 
HMODULE hMod = LoadLibrary ("FTD2XX.dll"); // загрузка библиотеки - д. б. не ноль
pFT_Open pOpen = GetProcAddress (hMod, "FT_Open"); // получили адрес функции - также д. б. не ноль
 
FT_STATUS st = pOpen (0, &hDev);    // вызываем функцию
 
FreeLibrary (hMod); // закрыли библиотеку
Нашёл вот такой вот вызов функции через строку названия функции "FT_Open". Меня интересует, если не создавать DLL, то можно ли как то вызвать подобным способом стандартную функцию (например из winuser.h)?

Ещё заинтересовало такое:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h>
void check(char *a, char *b, int (*cmp) (const char *, const char *));
int main(void)
{
char s1 [80], s2[80];
int (*p) (const char*, const char*);
p = strcmp; /* получение адреса strcmp() */
gets(s1);
gets (s2);
check(s1, s2, p);
return 0;
}
 
void check (char *a, char *b, int (*cmp) (const char *, const char *))
{
printf("Testing for equality.\n");
if(!(*cmp) (a, b)) printf("Equal");
else printf("Not equal");
}
Однако тут явно указывается кол-во членов и их тип :
C++
1
int (*p) (const char*, const char*);
Что мне не подходит, можно как то это преодолеть? И ещё меня не устраивает что надо явно писать:
C++
1
p = strcmp;
А не строковой (char *) переменной, что дало бы возможность динамики (ну именно это в конечном итоге меня и интересует).

П.С. Если это возможно, приводите пожалуйста примеры без использования STL и классов, а более приближенными к Си методами.

Вообще по поводу пункта 2: подумалось, что если реализовать пример1, то нужен будет список функций, который я в принципе могу хранить в txt фале вместе с кол-вом аргументов и возвращяемым значением, хотя это будет тупое копирование строк из .h файлов, к примеру из winuser будут подобия:
C++
1
WINUSERAPI LRESULT WINAPI SendMessageA(HWND,UINT,WPARAM,LPARAM);
От сюда я могу "запарсить" и искомое извлечь для выполнения операции. Не думаю что будет смысл париться над пунктом2, т.к. игра не стоит свеч, значит стаётся открытым вопрос первый.

Добавлено через 29 минут
Узнал что это:
C++
1
typedef FT_STATUS (*pFT_Open) (int, FT_HANDLE *);
являет прототипом функции. В общем вот эту инициализацию прототипа можно обойти? Т.е. подразумеваю что например у меня будет динамический массив с переменными, после парсинга я получу типы, и согласно им - заполню массив. Далее если бы можно было бы реализовать вызов GetProcAddress с сылкой на массив, то хотелось бы чтоб автоматически передавались в аргументы значения массива от [1] и до кол-ва аргументов ( [0] аргумент думаю будет идти как возвращяемое значение функции)... (может как то можно использовать в данном случае переменное кол-во аргументов в функции?)
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
15.08.2014, 10:44
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Альтернативный вызов функции (C++):

Вызов функции из dll - C++
Доброго времени суток! Пытаюсь вызвать функцию, которая находится в библиотеке следующим способом: HINSTANCE dllhandle =...

Функции в Assembler, вызов функции в C++ - C++ Builder
Здравствуйте, я составил функции на языке Assembler и вставил ее в код на C++: extern &quot;C&quot; { int INCREMENT(int a); } _asm {...

Вызов функции - C++ Builder
Собственно интересует как можно один раз вписать функцию с выполнением определенного алгоритма, так, чтоб потом когда понадобится этот...

Вызов функции - C++ Builder
В коде создания формы используется процедура: ... DrawSurface(A, B, C, T, drawpoint, X0, Y0, 10); ... Объявление DrawSurface ...

Вызов функции в событии - C++ Builder
На форме есть различные компоненты (баттоны, трекболлы, комбобоксы, пейджконтрол). Подскажите пожалуйста какое общее событие использовать...

BASS_ChannelSetSync вызов функции - C++ Builder
Может название не совсем верно. Есть переменная HSYNC PlaySync есть две функции void FreeStream (HSTREAM Stream) { ...

66
Izual
94 / 119 / 6
Регистрация: 13.11.2012
Сообщений: 1,555
15.08.2014, 20:44  [ТС] #16
Voivoid, противоречите сами себе, как раз из за выразительности кода и хочу сделать так как положено (через GetProcAddress), а вот делать ещё и доп .h файл - вариант будет самый последний ежели не найдётся других решений, согласно исключениям которые я написал.(без stl и векторов ^^)

Кстати, наткнулся на это: http://www.cyberguru.ru/cpp/cpp-language-straustrup2-page67.html
C++
1
typedef int (*CFT)(void*,void*);
При условии что память под void* будет не меньше чем сама переменная (на сколько я понял).
И
Отметим, что неявное преобразование указателя на что-то в указатель типа void* не выполняется для параметра функции, вызываемой через указатель на нее. Поэтому функцию
C++
1
int cmp3(const mytype*, const mytype*);
нельзя использовать в качестве параметра для sort(). Поступив иначе, мы нарушаем заданное в описании условие, что cmp3() должна вызываться с параметрами типа mytype*. Если вы специально хотите нарушить это условие, то должны использовать явное преобразование типа.
Может что то из этого решит проблему?
0
DrOffset
7376 / 4453 / 1009
Регистрация: 30.01.2014
Сообщений: 7,304
15.08.2014, 20:45 #17
Цитата Сообщение от Izual Посмотреть сообщение
Может что то из этого решит проблему?
Это тоже самое, о чем я говорил выше, только вид сбоку.
0
Voivoid
675 / 278 / 12
Регистрация: 31.03.2013
Сообщений: 1,339
15.08.2014, 20:50 #18
Цитата Сообщение от Izual Посмотреть сообщение
из за выразительности кода и хочу сделать так как положено (через GetProcAddress),
Я вообще не понимаю, какая тут связь, это раз. Два - что значит так, как положено? Ты какой цели хочешь вообще всем этим достичь?

Цитата Сообщение от Izual Посмотреть сообщение
Может что то из этого решит проблему?
Если ты считаешь, что от статической типизации вреда больше чем пользы ( ловить ошибки в compile-time? не, лучше падать в рантайме ), то думаю имеет смысл взять динамически типизированный язык, ruby какой-нибудь, там наверняка есть решение твоего вопроса
0
Програмер_80лвл
15 / 15 / 1
Регистрация: 17.10.2012
Сообщений: 96
Записей в блоге: 1
15.08.2014, 20:54 #19
Оффтоп.
Кликните здесь для просмотра всего текста

Izual, Я не пойму одного что ты блин такое пишешь что ты пренебрёгся к динамическим функциям?
я как то предполагаю... но будет лучше если ты сам объяснишь что ты пытаешься сделать, тогда можно будет посмотреть на данную задачу как проблему и по возможности найти другое решение.
0
DrOffset
15.08.2014, 20:55
  #20

Не по теме:

Цитата Сообщение от Voivoid Посмотреть сообщение
там наверняка есть решение твоего вопроса
Да решение его вопроса было еще на первой странице. Но вот хочет человек сам пройти по всем граблям
Ладно, что-то я устал, пойду-ка я отсюда.

0
Izual
94 / 119 / 6
Регистрация: 13.11.2012
Сообщений: 1,555
15.08.2014, 21:24  [ТС] #21
Цитата Сообщение от DrOffset Посмотреть сообщение
тем более зачем бацать хидер, когда он уже есть
Видимо имелось в виду что мне придётся делать хедер с указанием на каждую функцию своего прототипа. Но это не вариант, слишком много получится, да и наверно к 1000 функциям одни эти прототипы займут тучу памяти или времени для поиска...
Цитата Сообщение от DrOffset Посмотреть сообщение
... В этом случае главное только, чтобы фактический тип их аргументов не сильно различался с тем, что передано. Естественно никакого контроля не будет
В смысле double и float можно эквивалентно равными считать? (ну я думаю что разница в 7-ом знаке после запятой не сильно скажется на точности, ну я не стремлюсь к такой скурпулёзности, важен сам принцип скорее)
Контроль наверно будет исходя из сравнения аргументов функции и передаваемых типов, тут уж сам накалякаю. =)

По поводу питона - это уже бредово, заново изучать целый язык, опять куча синтаксических отличий... не, я хочу с компоновкой данных разобраться и перейти к графике и звуку, заново начинать не разумно.

Цитата Сообщение от DrOffset Посмотреть сообщение
только вид сбоку
Ну не совсем, например я не понял что это за " const mytype* ", это что же я могу свой enum TYPE тоже впихнуть чтоль?
Кстати, а можно в качестве возвращяемого значения поставить void, а потом переопределить согласно возвращяемому значению? Типа чтоб написать:
C++
1
typedef void* (*Make_func) (...);
Добавлено через 9 минут
Цитата Сообщение от Voivoid Посмотреть сообщение
какая тут связь
Связь в 1000 функциях, и к каждой писать прототип - "нет, спасибо, я пойду" =)

Цитата Сообщение от Voivoid Посмотреть сообщение
так, как положено
Не через "кривую ногу", на столько кривую, что меня от такого кода стошнит. (это весьма вероятно, я уже представил себе хидер с 1000 прототипами)

Цитата Сообщение от Voivoid Посмотреть сообщение
какой цели хочешь вообще всем этим достичь?
и
Цитата Сообщение от Програмер_80лвл Посмотреть сообщение
объяснишь что ты пытаешься сделать
ответ в том что я хочу к своей динамической матрице(ссылка в 5 посте) переменных привязать возможность использования функций, но писать стандартно каждую функцию я не хочу, хочу динамики, хотя бы мнимой (как мнимая 3д графика, которая один хрен выводится на плоский монитор) В общем, нужны указатели на функции.

Добавлено через 10 минут
Цитата Сообщение от DrOffset Посмотреть сообщение
решение его вопроса было еще на первой странице
Решение так же есть в STL и векторах, на сколько я понял(хотя я не понимаю их, ну не нравится синтаксис, гадко выглядит), но я их не беру априори, потому что.(надоело повторять)
То что челы чё та там сделали - не решает сути того что я хочу не просто чтоб всё работало, а чтоб это выглядело понятно.(опять повторяюсь)

Цитата Сообщение от Voivoid Посмотреть сообщение
от статической типизации вреда больше чем пользы ( ловить ошибки в compile-time? не, лучше падать в рантайме )
Дело не во "вреде и пользе", а в том что "я на месте не стою, щяс пойду и попляшу" Это я к тому что хочу чтоб программа сама плясала, а я ей только название танца давал бы. Ловить ошибки - с чего это? Если я правильно типы буду указывать и проверять, функции с непонятными типами - пропускать (ну доработаю если надо будет).
0
Програмер_80лвл
15 / 15 / 1
Регистрация: 17.10.2012
Сообщений: 96
Записей в блоге: 1
15.08.2014, 21:27 #22
Цитата Сообщение от Izual Посмотреть сообщение
но писать стандартно каждую функцию я не хочу, хочу динамики, хотя бы мнимой
Вам нужен подход ООП. То есть создать Object и что бы все типы принебригались к нему(то есть как в шарпе, всё наследовано от Obkect)
тогда уже можно использовать неограниченое кол-во параметров(вроде в плюсах есть такая хрень, "..." )
Возможно я не прав, потому что давно плюсы изучал.
0
Voivoid
675 / 278 / 12
Регистрация: 31.03.2013
Сообщений: 1,339
15.08.2014, 21:29 #23
Цитата Сообщение от Izual Посмотреть сообщение
Ловить ошибки - с чего это? Если я правильно типы...
В этом 'если' вся суть. Звучит как 'давайте просто не будем делать ошибки, делов-то'. По поводу плясок, кстати получается обратная ситуация. Ты отказываешься от того, чтобы плясал компилятор ( проверяя типы ) и вместо этого хочешь плясать сам ( все указывая руками )
0
Izual
94 / 119 / 6
Регистрация: 13.11.2012
Сообщений: 1,555
15.08.2014, 21:38  [ТС] #24
Voivoid, ты мне предлагаеш для каждой функции писать прототип или что? Ты если не согласен с моим методом - скажи свой, а то звучит как то странно. Но думаю что и ты не согласишся под 1000 функций писать прототипы.(даже если это сделает парсер, то как ни крути сколько же памяти сожрёт этот метод?)

Цитата Сообщение от Програмер_80лвл Посмотреть сообщение
Вам нужен подход ООП
Так я пытаюсь реализовать принципы ООП, но без векторов, STL и классов. А так сказать старыми методами. (вот например ссыль в 5 посте показывает как можно без вектора добиться динамического массива с изменяемыми типами переменных, а ведь таким сейчас ~никто не пользуется, у всех шаблоны, *bored*)
0
DrOffset
7376 / 4453 / 1009
Регистрация: 30.01.2014
Сообщений: 7,304
15.08.2014, 21:39 #25
Цитата Сообщение от Izual Посмотреть сообщение
Ну не совсем, например я не понял что это за " const mytype* ", это что же я могу свой enum TYPE тоже впихнуть чтоль?
Неа. Это основано на том, что mytype * кастится к void*, поэтому и общий каст прототипа функции void(*)(mytype*,mytype*) относительно безопасно можно скастить к void(*)(void*,void*).
Цитата Сообщение от Izual Посмотреть сообщение
Кстати, а можно в качестве возвращаемого значения поставить void, а потом переопределить согласно возвращяемому значению? Типа чтоб написать:
void* - это только указатели. Все что угодно так не вернешь. Тем более как ты узнаешь что там на самом деле лежит? Даже если память выделять динамически и возвращать на нее указатель, то ее же надо как-то освобождать, да и понять что же там на самом деле в общем случае будет невозможно без дополнительной информации.
0
Izual
94 / 119 / 6
Регистрация: 13.11.2012
Сообщений: 1,555
15.08.2014, 21:48  [ТС] #26
Цитата Сообщение от DrOffset Посмотреть сообщение
Тем более как ты узнаешь что там на самом деле лежит?
А это нужно? То есть, я то узнаю, я же прочту из файла тип возвращяемого значения и v[o].t установлю в INT, далее запишу значение после выполнения.
Цитата Сообщение от DrOffset Посмотреть сообщение
понять что же там на самом деле
Так же как и в той теме(наверно, хотя с void* я ещё не работал, честно говоря синтасис "char *s" я вообще в первые попробовал, всегда явно указывал на переменную):
C++
1
2
3
4
 if(v[i].t == STR)
            {
                delete[] v[i].d.s;
            }
0
DrOffset
7376 / 4453 / 1009
Регистрация: 30.01.2014
Сообщений: 7,304
15.08.2014, 21:57 #27
Цитата Сообщение от Izual Посмотреть сообщение
А это нужно?
А как ты с ним будешь работать, если не знаешь что это?
Цитата Сообщение от Izual Посмотреть сообщение
Так же как и в той теме(наверно, хотя с void* я ещё не работал, честно говоря синтасис "char *s" я вообще в первые попробовал, всегда явно указывал на переменную):
А если там не массив? То тогда уже надо вызвать delete без []. Уже это ты не отличишь без доп инфы. А если там вообще память не через new выделена была? Например через malloc, тогда уже надо free вызывать. А если runtime, с которым собиралась библиотека другой версии, то тогда нужно будет функцию освобождения из того же runtime использовать, а не из своего. А если там библиотека вообще на другом языке написана? А если там не указатель на самом деле возвращается?
В общем - это тупиковый путь. Все эти "если" ты, имея только void*, не распознаешь.

Добавлено через 4 минуты
Izual, В общем я считаю, что тебе информации тут дали вагон и маленькую тележку. Разбирайся.
По мне, так та библиотека - это лучший выход, если хочешь остаться в рамках С\С++.
Но решать тебе. Короче, удачи.
0
Voivoid
675 / 278 / 12
Регистрация: 31.03.2013
Сообщений: 1,339
15.08.2014, 22:00 #28
Цитата Сообщение от Izual Посмотреть сообщение
Voivoid, ты мне предлагаеш для каждой функции писать прототип или что?
Я предлагаю в статически типизированном языке оставаться в рамках системы типов и не пытаться головой пробить стену. Я честно говоря так и не понял что ты хочешь получить ( может я невнимательно читаю конечно, хз ), поэтому что-то конкретно посоветовать не могу.

Цитата Сообщение от Izual Посмотреть сообщение
вот например ссыль в 5 посте показывает как можно без вектора добиться динамического массива с изменяемыми типами переменных, а ведь таким сейчас ~никто не пользуется, у всех шаблоны, *bored*)
Да уж все новое - это хорошо забытое старое
В 80-х все мучились с динамическими массивами и вариантными типами из-за бедности языковых средств. Чтобы уменьшить сложность разработки ( ну и как следствие сократить количество испытываемой программистами боли ) в 90-х придумали шаблоны. В 2014 году читать, про то, что это все не нужно и надо все делать руками это, ну-ну-ну, кхм, весьма странно.

PS: Если что, я тут с хронологией весьма вольно обошелся, но идея должна быть ясна.
0
Izual
94 / 119 / 6
Регистрация: 13.11.2012
Сообщений: 1,555
16.08.2014, 15:44  [ТС] #29
Так, выкладываю первые тесты.
Тест первый, просто статически описал загрузку функции MessageBoxA. (хотя мучался долго, пока не дописал "_stdcall" в прототипе функция вызывалась, но на возврате значения вылезала ошибка и прога рушилась) Прикрепил архив, где Test1.exe это он и есть. Тест 1 работает.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//Test 1
#include "stdafx.h"
#include <iostream>
#include <conio.h>
#include <math.h>
#include "windows.h"
#include "windowsx.h"
#include <iostream>
#include <string.h>
 
using namespace std;
 
//typedef int(_stdcall *Make_func)(...);
typedef int(_stdcall *Make_func)(HWND, char *, char *, UINT);
 
int _tmain(int argc, _TCHAR* argv[])
{
    char fn[50]="MessageBoxA";
    int res=0;
    HMODULE hMod = LoadLibrary ("user32.dll");
    if(!hMod)
        cout << "Library not loaded" << endl;
    else
    {
        Make_func me = (Make_func)GetProcAddress(hMod,fn);
        if(!me)
            cout << "Function not loaded" << endl;
        else
        {
            res = me(NULL,"hi","message",NULL);
            cout << res << endl;
        }
        FreeLibrary (hMod);
    }
    getch();
    return 0;
}
Далее Тест2:
C++
1
2
typedef int(_stdcall *Make_func)(...);
//typedef int(_stdcall *Make_func)(HWND, char *, char *, UINT);
Просто заменил прототип, остальное всё так же.
Результат выполнения: MessageBox появляется, но как только его закрываеш вылезает ошибка:
Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.
Видимо на возврате значения, хоть подобная ошибка вылезала и в первом тесте до тех пор пока я stdcall не дописал. Но в тесте 2 я уже не знаю куда копать. =(
Тест 2.exe так же в архиве.

П.С. Извиняюсь сразу за кучу инклюдов, я обычно не помню какие нужны, так что пихаю все подряд)))
0
Вложения
Тип файла: rar Test.rar (17.8 Кб, 1 просмотров)
DrOffset
7376 / 4453 / 1009
Регистрация: 30.01.2014
Сообщений: 7,304
16.08.2014, 17:45 #30
Цитата Сообщение от Izual Посмотреть сообщение
Но в тесте 2 я уже не знаю куда копать.
Провел небольшое исследование. VS игнорирует stdcall для прототипа такого вида (причем на моей VS 2012 это происходит только в отладочной сборке).
Вот код:
Assembler
1
2
3
4
5
6
7
8
9
10
    mov esi, esp
    push    0
    push    OFFSET ??_C@_07ONPBMBOP@message?$AA@
    push    OFFSET ??_C@_02PCEFGMJL@hi?$AA@
    push    0
    call    DWORD PTR _me$1[ebp]
    add esp, 16                 ; Проблема тут(!)
    cmp esi, esp
    call    __RTC_CheckEsp
    mov DWORD PTR _res$[ebp], eax
При stdcall вызове очистку стека производит вызываемая функция. Однако, как мы видим по коду, VS пытается произвести очистку еще раз, уже в нашем коде. Подробнее тут.
В общем, как я уже говорил, работающий во всех случаях вариант без асма (хотя бы без знания) сделать будет тяжело (или даже невозможно).

Добавлено через 34 минуты
Вот код, который гененирует VS в релизе (я вручную добавил дебажную проверку):
Assembler
1
2
3
4
5
6
7
8
9
    mov esi, esp ; for check
    push    0
    push    OFFSET ??_C@_07ONPBMBOP@message?$AA@
    push    OFFSET ??_C@_02PCEFGMJL@hi?$AA@
    push    0
    call    eax
    cmp esi, esp  ; for check
    call    __RTC_CheckEsp ; for check
    mov esi, esp  ;  for check
Этот код не падает. Кстати mingw ведет себя корректно (учитывает stdcall у прототипа с ...), генерирует для твоего примера вот такой код:
Assembler
1
2
3
4
5
6
    mov DWORD PTR [esp+12], 0
    mov DWORD PTR [esp+8], OFFSET FLAT:LC4
    mov DWORD PTR [esp+4], OFFSET FLAT:LC5
    mov DWORD PTR [esp], 0
    call    eax
    sub esp, 16
Здесь видно, что у него немного иной подход к изменению esp (когда кладем параметры на стек, то esp не меняется, зато потом, после вызова, мы корректируем его на правильное значение один раз).
Я переписал код для VS таким же способом и вставил проверку:
Assembler
1
2
3
4
5
6
7
8
9
10
    mov esi, esp ; for check
    mov DWORD PTR [esp+12], 0
    mov DWORD PTR [esp+8], OFFSET ??_C@_07ONPBMBOP@message?$AA@
    mov DWORD PTR [esp+4], OFFSET ??_C@_02PCEFGMJL@hi?$AA@
    mov DWORD PTR [esp], 0
    call    eax
    sub esp, 16
    cmp esi, esp ; for check
    call    __RTC_CheckEsp ; for check
    mov esi, esp ; for check
Все работает отлично.
Не знаю чего тут посоветовать: юзай mingw, если не хочешь связываться с ассемблером
1
16.08.2014, 17:45
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
16.08.2014, 17:45
Привет! Вот еще темы с ответами:

вызов функции main() - C++ Builder
хочу сделать чтоб в случае ошибки функция main() заново вызывалать(строка 30) при вводе неверного символа программа закрывается а нужно...

Вызов функции из DLL с AnsiString - C++ Builder
Можно ли вызывать функцию из dll, в качестве параметра которой будет AnsiString и возвращает значение AnsiString? CodeGuard ругается. Не...

Вызов функции из другого h или cpp - C++ Builder
Подскажите как правильно вызвать функцию из другого h или cpp использующего глобальные переменные из головного модуля и взаимодействуюшего...

Вызов функции внутри другой функции с передачей локальной переменной по ссылке - C++
Столкнулся с очень с интересной проблемой. Можно ли так делать? #include &lt;iostream&gt; using std::cout; void f(const int &amp;ref){...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
30
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru