Поиск:

Ответ в темуСоздание новой темы Создание опроса
> [Pro] Подключение нескольких DLL, содержащих одинаково названные процедуры 
V
    Опции темы
Cr@$h
  Дата 13.7.2006, 06:55 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Исследователь
***


Профиль
Группа: Участник Клуба
Сообщений: 1693
Регистрация: 3.4.2005
Где: Санкт-Петербург, Россия

Репутация: 1
Всего: 41



Эта тема посвящена специальному вопросу коллизии имёен процедур при подключении в Fortran нескольких DLL с одинаково названными процедурами. Она выделилась из темы "Вызов DLL из программы на фортране", в которой рассмотрены основы подключения без сингулярных случаев.

Постановка задачи


Итак, стоит следующая проблема. Проект на Fortran использует несколько DLL, в которых есть процедуры с одинаковыми именами. Как указать, из какой именно библиотеки используется процедура, имя которой присутствует сразу в нескольких?. smile  smile 

Мне видится пока три варианта решения вопроса, используя как явное, так и неявное подключения. smile  Без потери общности, как это принято говорить в академических книгах  smile , рассмотрим пример с двумя DLL: DLL1 и DLL2, каждая из которых содержит процедуру ImportedProcedure. Во всех случаях необходимо будет задавать интерфейс к процедурам. Тестовый комплекс: VS 2003 и IVFC 9.0. Аминь.

Изначально имеем две библиотеки: DLL1 и DLL2.
DLL1
Код

subroutine ImportedProcedure( number )
    !DEC$ ATTRIBUTES STDCALL, DLLEXPORT, DECORATE, ALIAS: "ImportedProcedure" :: ImportedProcedure
    
    implicit none
    
    integer, intent(in) :: number
    
    print '(1x, a, i)', "From DLL #1: ", number
end subroutine ImportedProcedure

DLL2
Код

subroutine ImportedProcedure( number )
    !DEC$ ATTRIBUTES STDCALL, DLLEXPORT, DECORATE, ALIAS: "ImportedProcedure" :: ImportedProcedure
    
    implicit none
    
    integer, intent(in) :: number
    
    print '(1x, a, i)', "From DLL #2: ", number
end subroutine ImportedProcedure

Вся заурядность их видна сразу: обе называют свою единственную процедуру одним и тем же именем ImportedProcedure. Ну что тут будешь делать. smile  У обоих процедур имеется один входной параметр -- целое значение, которое они выводят на экран. В примерах будет подаваться в качестве параметра тот номер, процедуру с которым ожидаем вызвать. Если числа совпадают, то мы вызывали то, что хотели: From DLL #2: 2. Подали 2, т.к. пытались вызвать процедуру из второй библиотеки, она и откликнулась, значит не промахнулись. smile 

Назначение директив

Запомним сразу:
  • DLLEXPORT -- значит эту процедуру мы хотим экспорировать в DLL.
  • DLLIMPORT -- значит эту процедуру мы хотим импортировать из DLL.
  • STDCALL -- процедуры созданы со стандартным соглашением о вызовах.
  • ALIAS: "ImportedProcedure" -- указываем, как именно с учётом регистра называется процедура. В кавычках пишется строка с учётом регистра. Если этого не писать, то в DLL такая процедура запишется под именем IMPORTEDPROCEDURE. По идее, используем мы DLL дальше в фортрановском коде, так что этот атрибут и необязателен. Но вот в C++ нам бы без него пришлось экспортировать имено IMPORTEDPROCEDURE, а с атрибутом ALIAS, как и указываем, -- ImportedProcedure.
  • DECORATE -- указывает компилятору, что в зависимости от платформы, IA-32 или Itanium, под которую проводится компиляция, истинные имена процедур в DLL могут содержать свои префиксы, которые он и добавит автоматически. При использовании DLL в интерфейсах внешних процедур его тоже можно указывать -- он сам будет решать, что добавлять. Это позволяет сделать исходный код кроссплатформенным для IA-32 и Itanium. Атрибут необязательный.

1. Разрешение коллизии имён при явном (динамическом) подключении DLL


При явном подключении используются специальные процедуры Win API. Вначале вызывается функция LoadLibrary, чтобы загрузить DLL, затем используется функция GetProcAddress, чтобы получить указатели на требуемые функции (или переменные), а по окончании работы с ними вызывается FreeLibrary, чтобы выгрузить библиотеку и освободить занимаемые ею ресурсы. Надеюсь, код достаточно документирован и всё будет понятно. Признаюсь, сейчас я сам первый раз использую явное подключение. При использовании Win API нужно указать use kernel32.

1.1. Использование одной из двух процедур

Хорошо. Используем две библиотеки: DLL1 и DLL2. Про них уже знаем, что обе содержат процедуру с именем ImportedProcedure. Ладно, чёрт с ним, хотим вызвать хотя бы обну из них, но чтобы точно указать какую именно. smile 

Идея проста: в процедуре GetProcAddress при связи адреса интерфейсной процедуры с истинным адресом загруженной процедуры и будем решать, из какой библиотеки её брать. Действуя так, сначала вызовем из первой DLL, а потом из второй, и всё под одним и тем же именем! Т.е. в исходном тексте программы будет объявлена всего одна внешняя (интерфейсная) процедура ImportedProcedure.

Вот код, процедуры, в которой это реализовано:
Код

subroutine SeparatelyOneOfThem
    ! Описание интерфейса для связи с процедурой.
    interface
        subroutine ImportedProcedure( number )
            !DEC$ ATTRIBUTES STDCALL :: ImportedProcedure
            integer, intent(in) :: number
        end subroutine ImportedProcedure
    end interface
    
    integer Lib1, Lib2, Status
    
    ! Связь целочисленного указателя с процедурой.
    pointer( ptrImportedProcedure, ImportedProcedure )
        
    ! Получение описателей на подключаемые явно библиотеки для дальнейшей ссылки
    ! на них.
    Lib1 = LoadLibrary( "DLL1.dll"C )
    Lib2 = LoadLibrary( "DLL2.dll"C )
    
    ! Получение адреса процедуры из первой библиотеки.
    ptrImportedProcedure = GetProcAddress( Lib1, "ImportedProcedure"C )
    ! Здесь адресу введённой в интерфесе процедуре ImportedProcedure через её
    ! целочисленный указатель ptrImportedProcedure присваивается адрес реально
    ! загруженной процедуры с именем ImportedProcedure, расположенной в DLL1:
    ! ImportedProcedure = ptrImportedProcedure => ImportedProcedure из DLL1.
    
    ! Вызов процедуры из первой библиотеки.
    call ImportedProcedure( 1 )
    
    ! Получение адреса процедуры из первой библиотеки.
    ptrImportedProcedure = GetProcAddress( Lib2, "ImportedProcedure"C )
    ! Здесь адресу введённой в интерфесе процедуре ImportedProcedure через её
    ! целочисленный указатель ptrImportedProcedure присваивается адрес реально
    ! загруженной процедуры с именем ImportedProcedure, расположенной в DLL2:
    ! ImportedProcedure = ptrImportedProcedure => ImportedProcedure из DLL2.
    
    ! Вызов процедуры из второй библиотеки.
    call ImportedProcedure( 2 )
    
    ! Т.о. под одним именем ImportedProcedure здесь вызывались поочередно одинаково
    ! названные процедуры, расположенные в двух библиотеках: DLL1 и DLL2. Выбор
    ! библиотеки осуществлялся в процедуре GetProcAddress.
    
    Status = FreeLibrary( Lib1 )
    Status = FreeLibrary( Lib2 )
end subroutine SeparatelyOneOfThem

Последнние комментарии подытожили.
В результате имеем то, что имеем:
Код

 Separately one of them
 From DLL #1:            1
 From DLL #2:            2

Задача при явном подключении решена: удалось вызвать процедуры с одинаковым именем из двух разных библиотек, да ещё и указать когда какую.

1.2. Использование двух процедур сразу

Теперь хотим использовать две процедуры независимо одна от другой -- т.е. иметь в исходном коде две процедуры: одна из одной DLL, другая -- из другой.

Нет проблем. smile Объявляем две внешние процедуры с разными именами. Для каждой из них заводим свой целочисленный указатель. Каждому указателю присваивается адрес процедуры из той бибилиотеки, из которой нужно.

Процедурка, реализующая это подключение:
Код

subroutine SimultaneouslyBoth
    ! Описание интерфейсов для связи с процедурами.
    ! Не может одновременно существовать две процедуры с одинаковыми именами.
    ! Поэтому здесь им нужно дать "осмысленные" разные имена.
    interface
        subroutine ImportedProcedure1( number )
            !DEC$ ATTRIBUTES STDCALL :: ImportedProcedure1
            integer, intent(in) :: number
        end subroutine ImportedProcedure1
        
        subroutine ImportedProcedure2( number )
            !DEC$ ATTRIBUTES STDCALL :: ImportedProcedure2
            integer, intent(in) :: number
        end subroutine ImportedProcedure2
    end interface
    
    integer Lib1, Lib2, Status
    
    ! Связь целочисленных указателей с процедурами.
    pointer( ptrImportedProcedure1, ImportedProcedure1 )
    pointer( ptrImportedProcedure2, ImportedProcedure2 )
        
    ! Получение описателей на подключаемые явно библиотеки для дальнейшей ссылки на них.
    Lib1 = LoadLibrary( "DLL1.dll"C )
    Lib2 = LoadLibrary( "DLL2.dll"C )
    
    ! Получение адресов необходимых процедур.
    ptrImportedProcedure1 = GetProcAddress( Lib1, "ImportedProcedure"C )
    ptrImportedProcedure2 = GetProcAddress( Lib2, "ImportedProcedure"C )
    ! Здесь адресам введённых в интерфесе процедур ImportedProcedure1 и
    ! ImportedProcedure2 через их целочисленные указатели ptrImportedProcedure1 и
    ! ptrImportedProcedure2 присваиваются адреса реально загруженных процедур с
    ! изначально одинаковыми именами ImportedProcedure, расположенных в DLL1 и DLL2:
    ! ImportedProcedure1 = ptrImportedProcedure1 => ImportedProcedure из DLL1;
    ! ImportedProcedure2 = ptrImportedProcedure1 => ImportedProcedure из DLL2.
    
    ! Вызов процедуры из первой библиотеки.
    call ImportedProcedure1( 1 )
    
    ! Вызов процедуры из второй библиотеки.
    call ImportedProcedure2( 2 )
    
    Status = FreeLibrary( Lib1 )
    Status = FreeLibrary( Lib2 )
end subroutine SimultaneouslyBoth


Всё работает правильно:
Код

 Simultaneously both
 From DLL #1:            1
 From DLL #2:            2

Вторая задача при явном подключении решена: удалось вызвать процедуры независимо друго от друга, представив их разными внешними процедурами, хотя в своих DLL они имеют одно и то же имя.

2. Разрешение коллизии имён при неявном (статическом) подключении DLL


Хорошо. Мы порезвились на славу. Собственно, при явном подключении и делать было нечего: всю гибкость этого дела задавала функция GetProcAddress, в ней мы указывали, из какой библиотеки брать процедуру. А дальше дело техники: заводить ли одну процедуру и её целочисленный указатель, указывая им на определённую библиотечную процедуру, или заводить по принципу "каждой твари по паре": своя пара процедура--указатель для каждой DLL.

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

2.1. Задание интерфейса внешней процедуры

Самое ответственное здесь -- задание интерфейса внешней процедуры. К обоим процедурам DLL это делается одинаковым образом, т.к. они имеют одно и то же имя.
Код

interface
    subroutine ImportedProcedure( number )
        !DEC$ ATTRIBUTES STDCALL, DLLEXPORT, DECORATE, ALIAS: "ImportedProcedure" :: ImportedProcedure
        integer, intent(in) :: number
    end subroutine ImportedProcedure
end interface

Но из-за их одинаковости компилятор и не будет знать, из какой именно DLL брать ImportedProcedure.

2.2. Два одинаковых интерфейса. Какую библиотеку предпочтёт компилятор?

Хорошо. Поступим для эксперимента, как можем: задаим для двух внешних процедур одинаковый интерфейс и посмотрим из какой DLL будет грузить компилятор.
Вот процедурка:
Код

subroutine SimultaneouslyBoth
    ! Описание интерфейсов для связи с процедурами.
    interface
        subroutine ImportedProcedure1( number )
            !DEC$ ATTRIBUTES STDCALL, DLLEXPORT, DECORATE, ALIAS: "ImportedProcedure" :: ImportedProcedure1
            integer, intent(in) :: number
        end subroutine ImportedProcedure1
        
        subroutine ImportedProcedure2( number )
            !DEC$ ATTRIBUTES STDCALL, DLLEXPORT, DECORATE, ALIAS: "ImportedProcedure" :: ImportedProcedure2
            integer, intent(in) :: number
        end subroutine ImportedProcedure2
    end interface
    
    ! Вызов процедуры из первой библиотеки.
    call ImportedProcedure1( 1 )
    
    ! Вызов процедуры из второй библиотеки.
    call ImportedProcedure2( 2 )
end subroutine SimultaneouslyBoth

Оба интерфейса задают обращение к одной и той же внешней процедуре ImportedProcedure. При этом к проекту подключаются статически через .lib обе DLL (через Project Dependencies...). Проект компилируется и собирается без ошибок. Из какой же DLL будет грузить процедуру компилятор? Смотрим:
Код

 Simultaneously both
 From DLL #1:            1
 From DLL #1:            2

Выбрал первую DLL! Она дважды вывела строки. Ну, его понять можно -- любовь с первого взгляда  smile . Хотя, уважающий себя компилятор не должен был и вовсе собирать проект, раз коллизия имеет место. smile 

2.3. Гипотетический атрибут DLLNAME, и дело в шляпе.

К сожалению, разработчики почему-то не предусмотрели директиву типа DLLNAME, которая бы позволяла указать, из какой именно DLL экспортируется данная процедура. Вот гипотетический пример такого интерфейсного объявления.
Код

interface
    subroutine ImportedProcedure1( number )
        !DEC$ ATTRIBUTES STDCALL, DLLEXPORT, DECORATE, ALIAS: "ImportedProcedure", DLLNAME: "DLL1" :: ImportedProcedure1
        integer, intent(in) :: number
    end subroutine ImportedProcedure1

    subroutine ImportedProcedure2( number )
        !DEC$ ATTRIBUTES STDCALL, DLLEXPORT, DECORATE, ALIAS: "ImportedProcedure", DLLNAME: "DLL2" :: ImportedProcedure2
        integer, intent(in) :: number
    end subroutine ImportedProcedure2
end interface

Благодаря этому, можно было бы объявить две разные внешние процедуры, которые представляются в своих DLL одинаковым именем. Но так делать незя. smile 

2.4. Ну, а если по делу? smile 

Единственным выходом здесь вижу использование, а точнее редактирование, .lib файла -- статической библиотеки импорта у каждой DLL. Её следует изменить так, чтобы в каждой заменить имя в таблице ImportedLibrary на ImportedLibrary1 и ImportedLibrary2, соответственно для каэждой из библиотек. Сами DLL менять не будут, просто статическая библиотека будет разруливать ImportedLibrary1 в ImportedLibrary от DLL1, а другая ImportedLibrary2 тоже в ImportedLibrary от DLL2. Это можно попробовать сделать с помощью LIB.exe. Возможно, потребуется .obj файл, т.к. на входе она требует: объектные файлы COFF, 32-битные объектные файлы OMF или существующие COFF библиотеки. Опять же смотрим первую ссылку абзаца. Как экспортировать из DLL, используя файл экспорта .def, который задаётся вручную, описано здесь.

Это уже высший пилотаж менять .lib без исходных кодов. Возможно, я разберусь ещё с этим или мне помогут. Для неявного связывания ставлю здесь... Наверное, это один из редких случаев, когда неявное связывание проигрывает в гибкости явному, но советую читать дальше.

3. Разрешение коллизии имён при наличии исходного кода обеих DLL


Более административный метод. Вообще, это не очень хорошо иметь библиотеки, содержащие одинаковые процедуры, если они чем-то различаются, то это лучше отразить в их именах. Оба следующих метода не зависят от типа подключения, т.к. меняют имена в самих DLL.

3.1. Переименовывание процедур в исходном тексте

Изменение имён можно сделать вручную. Вот как будут выглядеть библиотеки.
DLL1
Код

subroutine ImportedProcedure1( number )
    !DEC$ ATTRIBUTES STDCALL, DLLEXPORT, DECORATE, ALIAS: "ImportedProcedure1" :: ImportedProcedure1
    
    implicit none
    
    integer, intent(in) :: number
    
    print '(1x, a, i)', "From DLL #1: ", number
end subroutine ImportedProcedure1

DLL2
Код

subroutine ImportedProcedure2( number )
    !DEC$ ATTRIBUTES STDCALL, DLLEXPORT, DECORATE, ALIAS: "ImportedProcedure2" :: ImportedProcedure2
    
    implicit none
    
    integer, intent(in) :: number
    
    print '(1x, a, i)', "From DLL #2: ", number
end subroutine ImportedProcedure2

Т.е. мы сознательно сами дали процедурам разные имена: ImportedProcedure1 и ImportedProcedure2. Код проекта приводить не имеет смысла, т.к. сама коллизия теперь не имеет места: две библиотеки имеют по-разному названные процедуры. Их теперь можно вызывать по их уникальным именам и никто ругаться на нас не будет.

3.2. Задание разных псевдонимов процедур в исходном тектсе

Это более гибкое, решение, чем первое. Раз уж мы итак используем псевдонимы (ALIAS), чтобы учесть регистр букв, то почему бы в нём же не задать номер процедуры -- её единственное отличие в имени.
DLL1
Код

subroutine ImportedProcedure( number )
    !DEC$ ATTRIBUTES STDCALL, DLLEXPORT, DECORATE, ALIAS: "ImportedProcedure1" :: ImportedProcedure
    
    implicit none
    
    integer, intent(in) :: number
    
    print '(1x, a, i)', "From DLL #1: ", number
end subroutine ImportedProcedure

DLL2
Код

subroutine ImportedProcedure( number )
    !DEC$ ATTRIBUTES STDCALL, DLLEXPORT, DECORATE, ALIAS: "ImportedProcedure2" :: ImportedProcedure
    
    implicit none
    
    integer, intent(in) :: number
    
    print '(1x, a, i)', "From DLL #2: ", number
end subroutine ImportedProcedure

Здесь всего два изменения: ALIAS: "ImportedProcedure1" в первой и ALIAS: "ImportedProcedure2" во второй процедурах. Эффект будет тот же, но при желании всё можно будет быстрее вернуть на прежние места. В первом случае псевдонимы "совпадали" с именами в исходном тектсе: ImportedProcedure1 для IMPORTEDPROCEDURE1 и ImportedProcedure2 для IMPORTEDPROCEDURE2. На этот раз мы изменили только псевдонимы -- они отличаются от имён в исходных кодах библиотек, и под ними процедуры и будут расположены в DLL'ах: ImportedProcedure1 и ImportedProcedure2 для имени в исходном тектсе IMPORTEDPROCEDURE у обоих процедур.

Заключение


Ну что.  smile На уровне явного подключения разобрались -- всё работает, как и сама гибкость этого метода на полную катушку.

Можно ожидать новых директив от разработчиков, т.е. у моря погоды.

Если имеются исходные коды библиотек, то неплохо бы изменить имена процедур в них, либо вручную, либо, задавая свои уникальные псевдонимы процедурам. Думайте об организации проекта.

Если исходных текстов нет или вы не хотите их менять, но хотите использовать статическое подключение, то могу предложить начать изучать LIB.exe. В принципе это реально. Нужно будет написать свой .def файл с информацией об экспоритуемых процедурах. Похоже понадобится не только .lib и .dll файлы, но и .obj файл библиотеки. Это ещё не исходный код, но объектные файлы не всегда идут с библиотеками. Сам иду отдыхать, сидел всю ночь, и до конца в MSDN этот вопрос изучить не успел. Кто добьёт -- репа ++.

P.S. Я прикрепляю решение, в котором сегодня это дело исследовал. Для проекта статического подключения (неявного) зависимости (dependency) уже установлены. Если собираетесь менять DLL'ки не забудьте их скопировать из папки debug в папки debug других двух проектов. Удачи.   

Это сообщение отредактировал(а) Cr@$h - 13.7.2006, 07:21

Присоединённый файл ( Кол-во скачиваний: 6 )
Присоединённый файл  Dlls.rar 9,49 Kb
PM MAIL ICQ   Вверх
Karabas
Дата 14.7.2006, 12:11 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 18
Регистрация: 14.6.2006

Репутация: нет
Всего: нет



Наверное, зависит от компилятора, но мой power station 4 не хочет собирать окончательный проект. 
Я создал консольное приложение, добавил use msfwin, а остальное копи-паст из файла ImplicitLinking. 
И заменил !DEC$ATTRIBUTE на !MS$ATTRIBUTE . 

во-первых, ругается на строчку 
pointer( ptrImportedProcedure, ImportedProcedure)
Error: pointer-based variable IMPORTEDPROCEDURE of INTEGER POINTER can't be a function call

А если переделать этот поинтер в просто integer то выдает: 
unresolved external symbol _IMPORTEDPROCEDURE@4

Даже подключение к проекту библиотек .lib не помогает (я скомпилил заново обе длл-ки тоже)

Или может быть моя версия фортрана совсем старая?  smile  
PM MAIL   Вверх
Cr@$h
Дата 14.7.2006, 21:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Исследователь
***


Профиль
Группа: Участник Клуба
Сообщений: 1693
Регистрация: 3.4.2005
Где: Санкт-Петербург, Россия

Репутация: 1
Всего: 41



Цитата(Karabas @  14.7.2006,  13:11 Найти цитируемый пост)
Я создал консольное приложение, добавил use msfwin, а остальное копи-паст из файла ImplicitLinking. 

Цитата(Karabas @  14.7.2006,  13:11 Найти цитируемый пост)
во-первых, ругается на строчку 
pointer( ptrImportedProcedure, ImportedProcedure)

Погоди. Это же в файле ExplicitLinking, а не ImplicitLinking smile .

Цитата(Karabas @  14.7.2006,  13:11 Найти цитируемый пост)
Error: pointer-based variable IMPORTEDPROCEDURE of INTEGER POINTER can't be a function call

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

Цитата(Karabas @  14.7.2006,  13:11 Найти цитируемый пост)
Даже подключение к проекту библиотек .lib не помогает (я скомпилил заново обе длл-ки тоже)

Они в Implicit (неявном) проекте должны использоваться, там должно всё работать. Можешь тоже нижнее подчёркивание использовать:
Код

!DEC$ ATTRIBUTES STDCALL, DLLEXPORT, DECORATE, ALIAS: "_ImportedProcedure" :: ImportedProcedure2


Цитата(Karabas @  14.7.2006,  13:11 Найти цитируемый пост)
Или может быть моя версия фортрана совсем старая?  

Это может только по части указателей (Cray) -- это расширение над стандартом, но думаю, всё же должно как-то работать.
Другое дело, он может игнорировать некоторые атрибуты. HELP можешь выслать, если есть? Можно покапаться посмотреть специфику.

Цитата(Karabas @  14.7.2006,  13:11 Найти цитируемый пост)
А если переделать этот поинтер в просто integer то выдает: 
unresolved external symbol _IMPORTEDPROCEDURE@4

Попробуй нижний пробел поставить:
Код

ptrImportedProcedure = GetProcAddress( Lib1, "_ImportedProcedure"C )

или даже так
Код

ptrImportedProcedure = GetProcAddress( Lib1, "_ImportedProcedure@4"C )

Но тоже не уверен. Просто эта ошибка сигнализирует о том, что внешняя процедура ImportedProcedure не подключилась. Что-то с работой указателей. Погоди, саму ImportedProcedure объявлять как integer не надо. Это внешняя процедура, к которой задан интерфейс. 
PM MAIL ICQ   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Fortran | Следующая тема »


 




[ Время генерации скрипта: 0.0748 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.