UNICODE и API

Программирование на Visual Basic, главный форум. Обсуждение тем программирования на VB 1—6.
Даже если вы плохо разбираетесь в VB и программировании вообще — тут вам помогут. В разумных пределах, конечно.
Правила форума
Темы, в которых будет сначала написано «что нужно сделать», а затем просьба «помогите», будут закрыты.
Читайте требования к создаваемым темам.
ANDLL
Великий гастроном
Великий гастроном
Аватара пользователя
 
Сообщения: 3450
Зарегистрирован: 29.06.2003 (Вс) 18:55

UNICODE и API

Сообщение ANDLL » 04.08.2004 (Ср) 17:52

Вопрос такой:
если я пишу

Dim A as String
A="string"

то в A содержиться строка типа WCHAR.
Соответственно есть API

Public Declare Function StrRest(ByVal string1 as String) as Long

В переводе на C это выглядит как

long StrRest(WCHAR* string1)

Ан нет. Почему-то нужный результат виден только при использвании LPSTR. А с использованием WCHAR происходят проблемы. Как будто VB приобразует строку перед передачей функции в ANSI? Так ли это, и как от этого избавиться :?:

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 04.08.2004 (Ср) 18:01

Да, это так. Преобразует всегда во всех функциях, объявленных через declare. Лечится просто - объявить параметр as long, а передавать strptr(string1).
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

ANDLL
Великий гастроном
Великий гастроном
Аватара пользователя
 
Сообщения: 3450
Зарегистрирован: 29.06.2003 (Вс) 18:55

Сообщение ANDLL » 04.08.2004 (Ср) 19:42

Это совершенно плохо. Не подходит такой вариант с VarPtr, т.к. передаеться сразу значение, т.е. "параметр", а не переменная(к пр. SimpleFunc("parametr"))

Можно конечно использовать StrConv, но это тоже не подходит. Может можно как-то поменять объявление?
Гастрономия - наука о пище, о ее приготовлении, употреблении, переварении и испражнении.
Блог

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 04.08.2004 (Ср) 20:28

Ты будешь удивлён, но StrPtr("literal data") прекрасно работает.
Изображение

ANDLL
Великий гастроном
Великий гастроном
Аватара пользователя
 
Сообщения: 3450
Зарегистрирован: 29.06.2003 (Вс) 18:55

Сообщение ANDLL » 04.08.2004 (Ср) 21:51

Пардон. Что есть StrPtr? В чем отличие от VarPtr?
Гастрономия - наука о пище, о ее приготовлении, употреблении, переварении и испражнении.
Блог

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 04.08.2004 (Ср) 23:18

Указатель на данные строки, тогда как VarPtr - указатель на дескриптор (BSTR) строки.

BTW, VarPtr("literal data") тоже прекрасно работает.
Изображение

TEH3OP
Продвинутый пользователь
Продвинутый пользователь
 
Сообщения: 143
Зарегистрирован: 12.12.2003 (Пт) 20:19
Откуда: Москва

Есть ещё пара способов.

Сообщение TEH3OP » 05.08.2004 (Чт) 12:44

Сабж.

1) Прописать функцию так:
Код: Выделить всё
Public Declare Function StrRest(ByRef string1 as Byte) as Long

, а работать с функцией так:
Код: Выделить всё
Dim abytTest() as Byte
Dim lngAPIReturn as Long

abytTest = "Мама мыла папу." & vbNullChar
lngAPIReturn = StrRest(abytTest(0))


2) Писать TLB'шку... но это долго рассказывать. Если интересно поищи в инете инфу про "IDL"-язык.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Re: Есть ещё пара способов.

Сообщение tyomitch » 05.08.2004 (Чт) 14:53

TEH3OP писал(а):Сабж.

1) Прописать функцию так:
Код: Выделить всё
Public Declare Function StrRest(ByRef string1 as Byte) as Long

, а работать с функцией так:
Код: Выделить всё
Dim abytTest() as Byte
Dim lngAPIReturn as Long

abytTest = "Мама мыла папу." & vbNullChar
lngAPIReturn = StrRest(abytTest(0))


Ну нахрена, нахрена, НАХРЕНА городить байтовые массивы?! :shock:
Всё же уже GSerg правильно сказал!
Объявление:
Код: Выделить всё
Public Declare Function StrRest(ByVal string1 as Long) as Long

Вызов:
Код: Выделить всё
Dim lngAPIReturn as Long

lngAPIReturn = StrRest(StrPtr("Мама мыла папу."))

ANDLL
Великий гастроном
Великий гастроном
Аватара пользователя
 
Сообщения: 3450
Зарегистрирован: 29.06.2003 (Вс) 18:55

Сообщение ANDLL » 05.08.2004 (Чт) 19:25

tyomitch писал(а):Указатель на данные строки, тогда как VarPtr - указатель на дескриптор (BSTR) строки


Т.е. StrPtr=VarPtr+4???
Гастрономия - наука о пище, о ее приготовлении, употреблении, переварении и испражнении.
Блог

TEH3OP
Продвинутый пользователь
Продвинутый пользователь
 
Сообщения: 143
Зарегистрирован: 12.12.2003 (Пт) 20:19
Откуда: Москва

Хм...

Сообщение TEH3OP » 06.08.2004 (Пт) 0:34

2 tyomitch: я не говорю, что этот способ самый наилучший, зачем делать такое лицо. Я просто рассказал про то, что есть такой способ.
Кстати, BSTR -- это НЕ дескриптор. >;-\

2 ANDLL: Там всё немного хитрее. Главное, что такой код
Код: Выделить всё
Dim lngPtr as long
Dim bstTest as String
bstTest = "Здесь был Я!"
lngPtr = StrPtr(bstTest)
StrRest lngPtr

-- опасен и може привести к неожиданному результату, вплоть до Memory Exeption'на.
А вот такой:
Код: Выделить всё
Dim bstTest as String
bstTest = "Здесь был Я!"
StrRest StrPtr(bstTest)

или
Код: Выделить всё
Dim bstTest as String
bstTest = "Здесь был Я!"
StrRest VarPtr(VarPtr(ByVal bstTest)) 'вроде так... сорры VB нет под рукой, чтобы проверить.

будет вполне нормально работать.

ANDLL
Великий гастроном
Великий гастроном
Аватара пользователя
 
Сообщения: 3450
Зарегистрирован: 29.06.2003 (Вс) 18:55

Сообщение ANDLL » 06.08.2004 (Пт) 7:55

AfxWin.h писал(а):typedef WCHAR OLECHAR;
...
typedef OLECHAR* BSTR;

Т.е. BSTR это все-таки дескриптор(указатель).

Господа! Чтите MSDN - це великая рукопись! К примеру там указано простейшее решение моей проблемы(сам недавно нашел):

Код: Выделить всё
Public Declare Function StrRest(ByRef str as String) as Long


И соответственно:

Код: Выделить всё
long StrRest(BSTR*str)
{
   str++;//Учитываем длину заголовка
   ...
}


Вот как все иногда просто! VB не преобразует строку в ASCII если она передаеться по адресу.
Между тем спасибо всем, кто принимал участие в обсуждении.
Гастрономия - наука о пище, о ее приготовлении, употреблении, переварении и испражнении.
Блог

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 06.08.2004 (Пт) 8:55

ANDLL писал(а):
tyomitch писал(а):Указатель на данные строки, тогда как VarPtr - указатель на дескриптор (BSTR) строки

Т.е. StrPtr=VarPtr+4???

StrPtr==*VarPtr
т.е.
Код: Выделить всё
Option Explicit

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Sub Main()
Dim s As String, l As Long
s = "Hello!"
CopyMemory l, ByVal VarPtr(s), 4
Debug.Print l, StrPtr(s)
End Sub

- вернёт два одинаковых числа.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Re: Хм...

Сообщение tyomitch » 06.08.2004 (Пт) 9:04

TEH3OP писал(а):2 tyomitch: я не говорю, что этот способ самый наилучший, зачем делать такое лицо. Я просто рассказал про то, что есть такой способ.
Кстати, BSTR -- это НЕ дескриптор. >;-\

Вопрос терминологии. Я специально назвал BSTR, чтобы было понятно, что я имею в виду.

TEH3OP писал(а):2 ANDLL: Там всё немного хитрее. Главное, что такой код
Код: Выделить всё
Dim lngPtr as long
Dim bstTest as String
bstTest = "Здесь был Я!"
lngPtr = StrPtr(bstTest)
StrRest lngPtr

-- опасен и може привести к неожиданному результату, вплоть до Memory Exeption'на.

С чего это вдруг? Жду аргументации.

TEH3OP писал(а):
Код: Выделить всё
Dim bstTest as String
bstTest = "Здесь был Я!"
StrRest VarPtr(VarPtr(ByVal bstTest)) 'вроде так... сорры VB нет под рукой, чтобы проверить.

будет вполне нормально работать.

Такой точно не будет. Ты передаёшь адрес адреса BSTR-а, т.е. OLECHAR***

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 06.08.2004 (Пт) 9:12

ANDLL писал(а):И соответственно:

Код: Выделить всё
long StrRest(BSTR*str)
{
   str++;//Учитываем длину заголовка
   ...
}


Непонятно, почему нужно увеличивать str. Ты этот код проверял? ;-)
Мне кажется, что в данном случае (BSTR*str) *str указывает на первый символ строки, (*str + 2) - на второй и т.д.
У тебя же получается нечётный адрес, где имхо ничего лежать не может.

Vi
Постоялец
Постоялец
 
Сообщения: 739
Зарегистрирован: 25.01.2002 (Пт) 11:03
Откуда: Россия, Ижевск

Сообщение Vi » 06.08.2004 (Пт) 9:53

ANDLL писал(а):Господа! Чтите MSDN - це великая рукопись! К примеру там указано простейшее решение моей проблемы(сам недавно нашел):
Код: Выделить всё
Public Declare Function StrRest(ByRef str as String) as Long

И соответственно:
Код: Выделить всё
long StrRest(BSTR*str)
{
   str++;//Учитываем длину заголовка
   ...
}

Вот как все иногда просто! VB не преобразует строку в ASCII если она передаеться по адресу.


Ты заблуждаешься! VB всегда преобразует строки, передаваемые во ВНЕШНИЕ процедуры как по "ByVal str As String", так и по "ByRef str As String". Это так называемый формат ABSTR (вот про него можешь поискать в MSDNе), т.е. указатель BSTR, но данные там в ANSI.
Vita
Выше головы не прыгнешь, ниже земли не упадешь, дальше границы не убежишь! (с) КВН

Ennor
Конструктивный критик
Конструктивный критик
 
Сообщения: 2504
Зарегистрирован: 18.12.2001 (Вт) 3:58
Откуда: Калуга -> Москва

Сообщение Ennor » 06.08.2004 (Пт) 20:26

Брюс МакКинни в своей книге "Hardcore Visual Basic" утверждает, что басик в принципе не позволяет работать с юникод-версиями АПИ-шных функций, за исключением одного-единственного способа - через TLB. Так что... думайте сами, нужно оно вам или нет.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 06.08.2004 (Пт) 21:26

Ennor писал(а):Брюс МакКинни в своей книге "Hardcore Visual Basic" утверждает, что басик в принципе не позволяет работать с юникод-версиями АПИ-шных функций, за исключением одного-единственного способа - через TLB. Так что... думайте сами, нужно оно вам или нет.

Код: Выделить всё
Option Explicit

Private Declare Function MessageBoxW Lib "user32" (ByVal hWnd As Long, ByVal lpText As Long, ByVal lpCaption As Long, ByVal wType As Long) As Long

Sub Main()
MessageBoxW 0, StrPtr("VB прекрасно может работать с Unicode API!"), StrPtr("Наглый гон!"), vbInformation
End Sub

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 06.08.2004 (Пт) 23:09

:)
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

Ennor
Конструктивный критик
Конструктивный критик
 
Сообщения: 2504
Зарегистрирован: 18.12.2001 (Вт) 3:58
Откуда: Калуга -> Москва

Сообщение Ennor » 10.08.2004 (Вт) 9:28

2 tyomitch:
Хм. А кто тебе сказал, что туда передается действительно Unicode-данные? Вот иероглифов или диакритики туда насуй и посмотри... А корректное отображение русского - это не доказательство.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 10.08.2004 (Вт) 11:42

Ennor писал(а):2 tyomitch:
Хм. А кто тебе сказал, что туда передается действительно Unicode-данные? Вот иероглифов или диакритики туда насуй и посмотри... А корректное отображение русского - это не доказательство.

Эх ты, конструктивный критик... А самому проверить в облом? Работает!
Код: Выделить всё
Option Explicit

Private Declare Function MessageBoxW Lib "user32" (ByVal hWnd As Long, ByVal lpText As Long, ByVal lpCaption As Long, ByVal wType As Long) As Long
Sub Main()
Dim Proof As String
Proof = "Диакритика: " & ChrW(&HC1) & ChrW(&HD5) & ChrW(&HE9) & vbCrLf & _
        "Греческий: " & ChrW(&H39E) & ChrW(&H3B3) & ChrW(&H3C7) & vbCrLf & _
        "Еврейский: " & ChrW(&H5D0) & ChrW(&H5E4) & ChrW(&H5E9) & vbCrLf & _
        "Арабский: " & ChrW(&H626) & ChrW(&H63A) & ChrW(&H64A) & vbCrLf & _
        "Тайский: " & ChrW(&HE04) & ChrW(&HE18) & ChrW(&HE2C) & vbCrLf & _
        "Ещё надо?"
MessageBoxW 0, StrPtr(Proof), StrPtr("Наглый гон!"), vbInformation
End Sub

Ennor
Конструктивный критик
Конструктивный критик
 
Сообщения: 2504
Зарегистрирован: 18.12.2001 (Вт) 3:58
Откуда: Калуга -> Москва

Сообщение Ennor » 10.08.2004 (Вт) 12:00

Хм. Действительно, работает. Кстати, насчет моего утверждения - МакКинни писал о VB5, вообще-то. Интересно, кто-нить может проверить это на пятом басике?

NetVille
Начинающий
Начинающий
 
Сообщения: 8
Зарегистрирован: 04.08.2004 (Ср) 9:13
Откуда: Альфа центавра знаешь? Тамошние мы.

Сообщение NetVille » 10.08.2004 (Вт) 12:24

2Ennor: Проверил на 5-м и на 6-м - всё работает.

Ennor
Конструктивный критик
Конструктивный критик
 
Сообщения: 2504
Зарегистрирован: 18.12.2001 (Вт) 3:58
Откуда: Калуга -> Москва

Сообщение Ennor » 10.08.2004 (Вт) 13:01

Ну что я могу сказать... Значит, МакКинни облажался. :) С другой стороны, лично мне еще ни разу не требовалось посылать WCHAR-строки апишкам, хотя то, что это принципиальное ограничение снято, не может не радовать. Правда, остается еще один важный вопрос: как передавать юникод, если строка является частью структуры? Думаю, опять-таки потребуется эксперимент.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 10.08.2004 (Вт) 13:50

Ennor писал(а):Ну что я могу сказать... Значит, МакКинни облажался. :)

Когда я обнаружил, что в книге Аппельмана есть грубые ошибки, для меня это тоже было потрясением...
Так что: авторы книг не боги, и к тому, что они пишут, нужно подходить со здоровым недоверием.

Ennor писал(а):С другой стороны, лично мне еще ни разу не требовалось посылать WCHAR-строки апишкам, хотя то, что это принципиальное ограничение снято, не может не радовать. Правда, остается еще один важный вопрос: как передавать юникод, если строка является частью структуры? Думаю, опять-таки потребуется эксперимент.

Если передавать не их сами, а их адрес - всё работает.
Код: Выделить всё
Option Explicit

Private Declare Function MessageBoxW Lib "user32" (ByVal hWnd As Long, ByVal lpText As Long, ByVal lpCaption As Long, ByVal wType As Long) As Long
Type SameAsString
    Data As String * 256
End Type

Sub Main()
Dim Proof As SameAsString
Proof.Data = "VB прекрасно может работать с Unicode-строками даже в структурах!"
MessageBoxW 0, VarPtr(Proof), StrPtr("МакКинни нас всех нае***!"), vbInformation
End Sub

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 10.08.2004 (Вт) 14:30

Маленькое уточнение: varptr(proof.data). Это только в данном конкретном случае указатели совпадают :)

ЗЫ: А я вот что-то вообще книжек этих не читал :?
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 10.08.2004 (Вт) 16:20

GSerg писал(а):Маленькое уточнение: varptr(proof.data). Это только в данном конкретном случае указатели совпадают :)
Ты не понял сути проблемы. Надо не передать Unicode-строку, хранящуюся в структуре, а передать структуру, содержащую в себе Unicode-строку. Так что у меня всё правильно. И действительно, это просто в данном конкретном случае указатели совпадают.

ANDLL
Великий гастроном
Великий гастроном
Аватара пользователя
 
Сообщения: 3450
Зарегистрирован: 29.06.2003 (Вс) 18:55

Сообщение ANDLL » 10.08.2004 (Вт) 17:20

tyomitch писал(а):
ANDLL писал(а):И соответственно:

Код: Выделить всё
long StrRest(BSTR*str)
{
   str++;//Учитываем длину заголовка
   ...
}


Непонятно, почему нужно увеличивать str. Ты этот код проверял? ;-)
Мне кажется, что в данном случае (BSTR*str) *str указывает на первый символ строки, (*str + 2) - на второй и т.д.
У тебя же получается нечётный адрес, где имхо ничего лежать не может.


Да, проверял. Если к примеру str=15, то после str++, str будет равно 17.(Так работает оператор инкремента с указателями).
Сдвигать надо, что бы учесть длинну заголовка BSTR.(т.е. *str указывает на число символов в строке, а дальше уже идет сама строка)
Гастрономия - наука о пище, о ее приготовлении, употреблении, переварении и испражнении.
Блог

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 10.08.2004 (Вт) 19:44

ANDLL писал(а):
tyomitch писал(а):
ANDLL писал(а):И соответственно:

Код: Выделить всё
long StrRest(BSTR*str)
{
   str++;//Учитываем длину заголовка
   ...
}


Непонятно, почему нужно увеличивать str. Ты этот код проверял? ;-)
Мне кажется, что в данном случае (BSTR*str) *str указывает на первый символ строки, (*str + 2) - на второй и т.д.
У тебя же получается нечётный адрес, где имхо ничего лежать не может.

Да, проверял. Если к примеру str=15, то после str++, str будет равно 17.(Так работает оператор инкремента с указателями).
Сдвигать надо, что бы учесть длинну заголовка BSTR.(т.е. *str указывает на число символов в строке, а дальше уже идет сама строка)

Действительно, то, что инкрементируется по два, я выпустил из виду. Но всё равно непонятно: BSTR-то указывает на первый символ строки, т.е. длина должна лежать в *(*str - 4).
Да и вообще, у тебя получается, что длина строки занимает два байта. Так не бывает.
Проверь, пожалуйста, тщательнее. Мне кажется, ты теряешь первый символ строки. BSTR указывает не на начало строки в памяти, а на её первый символ - это правда.

ANDLL
Великий гастроном
Великий гастроном
Аватара пользователя
 
Сообщения: 3450
Зарегистрирован: 29.06.2003 (Вс) 18:55

Сообщение ANDLL » 10.08.2004 (Вт) 21:44

Длина строки конечно, по-идее должна быть DWORD(4 байта), а не WORD(2 байта).

В-общем так:
Код: Выделить всё
long StrRest(BSTR*str)
{
   str++;
   MessageBoxW(NULL,*str,L"Строка",0);
   return 0;
   ...
}

И:
Код: Выделить всё
Private Declare Function StrRest Lib "X-Lib.dll" (ByRef xStr As String) As Long

StrRest "VB-String"


И сее работает. Если убрать str++, то не работает.

Я пока еще не совсем вник, но могу сказать, что операция str++ увеличивает str не на два байта.
Объяснение: str есть BSTR* -> OLECHAR**. sizeof(OLECHAR*) равно DWORD(т.е. 4, т.к. указатель)
Не проверял, но по идее так.
Гастрономия - наука о пище, о ее приготовлении, употреблении, переварении и испражнении.
Блог

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 10.08.2004 (Вт) 22:42

ANDLL писал(а):Длина строки конечно, по-идее должна быть DWORD(4 байта), а не WORD(2 байта).

В-общем так:
Код: Выделить всё
long StrRest(BSTR*str)
{
   str++;
   MessageBoxW(NULL,*str,L"Строка",0);
   return 0;
   ...
}

И:
Код: Выделить всё
Private Declare Function StrRest Lib "X-Lib.dll" (ByRef xStr As String) As Long

StrRest "VB-String"


И сее работает. Если убрать str++, то не работает.

Я пока еще не совсем вник, но могу сказать, что операция str++ увеличивает str не на два байта.
Объяснение: str есть BSTR* -> OLECHAR**. sizeof(OLECHAR*) равно DWORD(т.е. 4, т.к. указатель)
Не проверял, но по идее так.

Ааа! Слушай, здорово! :-)
До меня доходит :-)
Попробуй так:
Код: Выделить всё
long StrRest(BSTR*str)
{
   MessageBoxA(NULL,*str,"Строка",0);
   return 0;
   ...
}

След.

Вернуться в Visual Basic 1–6

Кто сейчас на конференции

Сейчас этот форум просматривают: AhrefsBot, Yandex-бот и гости: 31

    TopList  
cron