Использование библиотек и компонентов
В проектах C++ часто требуется вызывать функции или обращаться к данным в двоичных файлах, таких как статические библиотеки (LIB-файлы), библиотеки DLL, компоненты среды выполнения Windows, компоненты COM или сборки .NET. В этих случаях необходимо настроить проект таким образом, чтобы он мог находить нужные двоичные файлы во время сборки. Конкретный способ сделать это зависит от типа вашего проекта, типа двоичного файла, а также от того, был ли этот двоичный файл собран в том же решении, что и ваш проект.
Использование скачанных библиотек с помощью vcpkg
Если вы хотите использовать скачанную библиотеку с помощью диспетчера пакетов vcpkg, то приведенные ниже инструкции можно пропустить. Дополнительные сведения см. в vcpkg.io .
Использование статических библиотек
Если проект статической библиотеки был создан в том же решении:
- #include файл заголовка для статической библиотеки с помощью кавычки. В типовом решении путь начинается с ../ . При поиске вы можете использовать предложения технологии IntelliSense.
- Добавьте ссылку на проект статической библиотеки. Щелкните правой кнопкой мыши элемент Ссылки в узле проекта приложения в обозревателе решений и выберите Добавить ссылку.
Если статическая библиотека не входит в состав решения:
- Щелкните правой кнопкой мыши узел проекта приложения в обозревателе решений и выберите Свойства.
- На странице свойств Каталоги VC++ добавьте в раздел Пути библиотек путь к каталогу, который содержит LIB-файл. Затем добавьте в раздел Включаемые каталоги путь к файлам заголовков библиотеки.
- На странице свойств компоновщика > добавьте имя LIB-файла в дополнительные зависимости.
Библиотеки динамической компоновки
Если библиотека DLL была собрана в рамках того же решения, что и приложение, выполните те же действия, что и для статической библиотеки.
Если библиотека DLL не входит в состав решения приложения, вам потребуются DLL-файл, заголовки с прототипами для экспортируемых функций и классов, а также LIB-файл, содержащий необходимую для компоновки информацию.
- Скопируйте DLL-файл в папку выходных данных проекта или другую папку, которая задана в качестве стандартной для поиска библиотек DLL в Windows. Дополнительные сведения см. в разделе «Порядок поиска библиотеки динамических ссылок».
- Выполните шаги с 1 по 3 для статических библиотек, чтобы задать пути к заголовкам и LIB-файлу.
COM-объекты
Если в собственном приложении C++ требуется использовать COM-объект и этот объект зарегистрирован, вам достаточно вызвать функцию CoCreateInstance и передать в нее CLSID объекта. Система выполнит поиск объекта в реестре Windows и загрузит его. В проекте C++/CLI COM-объект можно использовать таким же образом. Кроме того, он может использовать его, добавив ссылку на нее из списка добавления ссылок > COM и используя ее через вызываемую оболочку среды выполнения.
Сборки .NET с компонентами среды выполнения Windows
В проектах универсальной платформы Windows (UWP) или C++/CLI для использования сборок .NET или компонентов среды выполнения Windows можно добавить ссылку на сборку или компонент. В узле Ссылки проекта универсальной платформы Windows (UWP) или C++/CLI представлены ссылки на часто используемые компоненты. Щелкните правой кнопкой мыши узел Ссылки в Обозревателе решений, чтобы открыть диспетчер ссылок и просмотреть доступные в системе компоненты. Нажмите кнопку Обзор, чтобы перейти к папке, в которой находится нужный вам пользовательский компонент. Поскольку сборки .NET и компоненты среды выполнения Windows содержат встроенные сведения о типах, для просмотра их методов и классов можно щелкнуть правой кнопкой мыши и выбрать команду Просмотреть в обозревателе объектов.
Свойства ссылки
Каждый тип ссылки имеет свойства. Свойства можно просмотреть, выбрав ссылку в обозревателе решений и нажав клавиши Alt + ВВОД. Также можно щелкнуть ссылку правой кнопкой мыши и выбрать пункт Свойства. Одни свойства доступны только для чтения, другие можно изменять. Тем не менее обычно эти свойства не требуется изменять вручную.
Свойства ссылки ActiveX
Свойства ссылки ActiveX доступны только для компонентов COM. Данные свойства отображаются только тогда, когда в панели Ссылки выбран компонент COM. Эти свойства нельзя изменить.
- Управление полным путем Отображает путь к каталогу элемента управления, на который указывает ссылка.
- GUID элемента управления Отображает GUID для элемента управления ActiveX.
- Версия элемента управления Отображает версию элемента управления ActiveX, на который указывает ссылка.
- Имя библиотеки типов Отображает имя библиотеки типов, на которую указывает ссылка.
- Средство программы-оболочки Отображает средство, которое используется для создания сборки взаимодействия из указанной библиотеки COM или элемента управления ActiveX.
Свойства ссылки на сборку (C++/CLI)
Свойства ссылки на сборку доступны только для ссылок на сборки .NET Framework в проектах C++/CLI. Данные свойства отображаются только тогда, когда в панели Ссылки выбрана сборка .NET Framework. Эти свойства нельзя изменить.
- Относительный путь Отображает относительный путь от каталога проекта к сборке, на которую указывает ссылка.
Свойства сборки
Следующие свойства доступны для различных типов ссылок. Они позволяют задавать способ построения со ссылками.
- Копировать локальные Указывает, следует ли автоматически копировать сборку, на которую указывает ссылка, в целевое расположение во время сборки.
- Копировать локальные вспомогательные сборки (C++/CLI) Указывает, следует ли автоматически копировать вспомогательные сборки ссылочной сборки в целевое расположение во время сборки. Используется, только если для параметра Копировать локальные задано значение true .
- Выходные данные ссылочной сборки Указывает, что эта сборка используется в процессе сборки. true означает, что эта сборка используется в командной строке компилятора во время выполнения сборки.
Свойства ссылок проектов на проекты
Следующие свойства определяют ссылку проекта на проект из проекта, выбранного в панели Ссылки, на другой проект в том же решении. Дополнительные сведения см. в статье Управление ссылками в проекте.
- Компоновать зависимости библиотек Если это свойство имеет значение True, система проектов установит в зависимом проекте связь с LIB-файлами, создаваемыми независимым проектом. Обычно устанавливается значение True.
- Идентификатор проекта Уникальный идентификатор независимого проекта. Значение свойства — это GUID внутренней системы, который невозможно изменить.
- Использовать входные данные зависимостей библиотек Если это свойство имеет значение False, система проектов не установит в зависимом проекте связь с OBJ-файлами для библиотеки, созданной независимым проектом. Таким образом, это значение отключает инкрементную компоновку. Обычно указывается значение False, так как при наличии множества независимых проектов сборка приложения может занять длительное время.
Ссылочные свойства только для чтения (COM & .NET)
Следующие свойства существуют в ссылках на компоненты COM и сборки .NET, и их нельзя изменить.
- Имя сборки Отображает имя сборки для сборки, на которую указывает ссылка.
- Язык и региональные параметры Отображает язык и региональные параметры выбранной ссылки.
- Description Отображает описание выбранной ссылки.
- Полный путь Отображает путь к каталогу сборки, на которую указывает ссылка.
- Identity Для сборок .NET Framework отображает полный путь. Для компонентов COM отображает GUID.
- Подпись Отображает метку ссылки.
- Название Отображает имя ссылки.
- Токен открытого ключа Отображает токен открытого ключа для идентификации сборки, на которую указывает ссылка.
- Строгое имяtrue , если сборка, на которую указывает ссылка, имеет строгое имя. Сборка со строгим именем имеет уникальную версию.
- Версия Отображает версию сборки, на которую указывает ссылка.
Как добавить библиотеку в c
Использование библиотек
В прошлой статье, я рассказывал о том, как создаются библиотеки классов, написанные на C#. Прочитайте ту статью и соберите собственную библиотеку, если Вы не хотите ограничится только теорией в этой статье. В этой статье, я расскажу как использовать уже созданные библиотеки (DLL) в своих программах. И так, к делу!
Давайте создадим новый проект консольного приложения (как создавать проекты консольных приложений, я рассказывал в уроке № 3 базового курса).
А теперь, в окне Visual Studio, в обозревателе решений, ищем группу «Ссылки» и раскрываем её, как показано на рисунке ниже.

Список внешних ссылок нового проекта
А теперь, вызовем контекстное меню на пункте «Ссылки», и выберем в нем «Добавить ссылку», как показано на рисунке ниже.

Добавление ссылки в проект
В появившемся окне, выбираем, в левой области выбираем пункт «Обзор» и в низу окна, нажимаем на кнопку «Обзор», как показано на рисунке ниже.

Загрузка внешней библиотеки
В появившемся окне, перейти в папку, в которое лежит библиотека (DLL) созданная в предыдущей статье (ссылку на которую, я давал ранее), выбрать эту библиотеку и нажать на кнопку «Добавить», как показано на рисунке ниже.

Выбор нужной библиотеки
После чего, нажать на кнопку «OK» в предыдущем окне. В результате, список ссылок проекта, будет пополнен еще одной:

Пополненный список ссылок проекта
Теперь, мы можем использовать в налей программе класс «INIManager» из подключенной библиотеки. Но перед этим, мы должны выполнить еще одну операцию. Рассмотрим фрагмент кода подключенной библиотеки (он остался у нас после предыдущей статьи):
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Runtime.InteropServices; namespace FirstDLL < public class INIManager < //Конструктор, принимающий путь к INI-файлу public INIManager(string aPath)
Обратите внимание на выделенную строку, это объявление пространства имен (namespace), как бы некого контейнера, в котором находится класс «INIManager». И мы не сможем воспользоваться классом, пока не укажем системе что нужно взять во внимание это пространство имен. Т.е. нам нужно подключить пространство имен в нашем проекте. Для этого, добавим строку «using FirstDLL;» в конец блока директив using, который расположен в самом начале основного файла проекта. Подключение нужного пространства имен показано ниже (интересующая строка кода выделена):
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using FirstDLL;
Вот теперь, можно использовать класс из подключенной библиотеки, а сделать это можно написав в методе «Main» такой код:
//Создание объекта, для работы с файлом INIManager manager = new INIManager("C:\\my.ini"); //Получить значение по ключу name из секции main string name = manager.GetPrivateString("main", "name"); //Вывести полученное имя в консоль Console.WriteLine(name); //Записать значение по ключу age в секции main manager.WritePrivateString("main", "age", "21");
Данный код будет работать, при условии наличия файла «my.ini» на диске «C» нашего ПК. Вот такой вот пример работы со внешними библиотеками.
Как написать свою библиотеку на Си
Если ты встал на путь С/С++ разработчика, то скорее всего (помимо использования стандартной библиотеки — libc) рано или поздно вам потребуется занятся разработкой собственных библиотек. Зачем. Причин может быть несколько. Например вы написали свою структуру данных или свой алгоритм, и хотите использовать его повторно или распространять. Так же возможно вы написали несколько утилит и все они используют один и тот же кусок кода (например, как часто это бывает, логгер), и будет логично вынести этот кусок кода в отдельный модуль. Поскольку сопровождать такой код будет проще.
Реализация
И так, давайте начнем с примера. Создадим заголовочный файл somecode.h, который будет содержать объявление некоторой функции. Пусть будет простая функция которая разбивает предложение на слова и печатает каждое слово в новой строке. Простой синтетический пример.
1 2 3 4 5 6
#ifndef __SOMECODE__ #define __SOMECODE__ void print_split(char* str); #endif
И создадим файл somecode.c, в которой напишем реализацию нашей функции.
1 2 3 4 5 6 7 8 9 10 11 12
#include "somecode.h" #include #include void print_split(char* str) const char *word = strtok(str, " "); while (word != NULL) printf("> %s\n", word); word = strtok(NULL, " "); > >
Далее создадим файл main.c, где будем вызывать нашу функцию.
1 2 3 4 5 6 7 8 9 10
#include #include "somecode.h" int main(int argc, char** argv) if (argc > 1) print_split(argv[1]); > >
Давайте для начала скомпилируем это все самым обычным способом для проверки работоспособности.
- из исходных файлов получаем объектные файлы
- из объектных файлов получаем исполняемый файл
1 2 3 4 5 6 7 8 9 10 11 12
$ gcc -c -Wall -g -o somecode.o somecode.c $ gcc -c -Wall -g -o main.o main.c $ gcc -Wall -g -o a.out.1 main.o somecode.o $ ls -l $ ls -l total 40 -rwxr-xr-x 1 ainr ainr 19816 Aug 9 21:22 a.out.1 -rw-r--r-- 1 ainr ainr 123 Aug 9 21:20 main.c -rw-r--r-- 1 ainr ainr 3576 Aug 9 21:22 main.o -rw-r--r-- 1 ainr ainr 213 Aug 9 21:21 somecode.c -rw-r--r-- 1 ainr ainr 72 Aug 9 21:20 somecode.h -rw-r--r-- 1 ainr ainr 6224 Aug 9 21:21 somecode.o
Запускаем исполняемый файл и видим, что все работает.
1 2 3 4 5
$ ./a.out.1 "hello my little pony" > hello > my > little > pony
А теперь рассмотрим пример получения библиотеки и линковки его к исполняемому файлу. Для компиляции используем вызов gcc со следующими опциями.
$ gcc -Wall -g -shared -fpic -o libsomecode.so somecode.c
Из файла с расширением .c мы получаем файл с расширением .so. И так же обратите внимание, что библиотека имеет префикс lib. Еще мы видим, что появились два дополнительных аргумента -shared и -fpic. С помощью опции -shared мы говорим компилятору, что хотим получить а выходе библиотеку. А опция -fpic говорит компилятору, что объектные файлы должны содержать позиционно-независимый код (position independent code), который рекомендуется использовать для динамических библиотек.
Теперь скомпилируем наш исполняемый файл подключив к нему нашу библиотеку. Для этого нужно указать название библиотеки через опцию -l.
1 2 3
$ gcc -Wall -g -o a.out.2 main.c -lsomecode /usr/bin/ld: невозможно найти -lsomecode collect2: error: ld returned 1 exit status
И опс.. мы получили ошибку… Линкер говорит нам, что он не знает где лежит наша библиотека. С помощью опции -L указываем текущую директорию, где лежит наша библиотека и компиляция проходит успешно.
$ gcc -Wall -g -o a.out.2 main.c -lsomecode -L.
Пытаемся запустить нашу и программу и ловим еще одну ошибку в котором говорится, что в процессе загрузки динамических библиотек отсуствует наша библиотека.
$ ./a.out.2 "hello my little pony" ./a.out.2: error while loading shared libraries: libsomecode.so: cannot open shared object file: No such file or directory
По умолчанию в операционной системе есть некоторое количество стандартных директорий, где должны располагатся библиотеки. Посмотреть этот список можно так.
1 2 3 4 5 6 7 8 9 10 11 12 13
$ ld --verbose | grep SEARCH_DIR | sed "s/\;\ /\n/g" SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu") SEARCH_DIR("=/lib/x86_64-linux-gnu") SEARCH_DIR("=/usr/lib/x86_64-linux-gnu") SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64") SEARCH_DIR("=/usr/local/lib64") SEARCH_DIR("=/lib64") SEARCH_DIR("=/usr/lib64") SEARCH_DIR("=/usr/local/lib") SEARCH_DIR("=/lib") SEARCH_DIR("=/usr/lib") SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64") SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");
Так же есть возможность задавать дополнительные директории с библиотеками с помощью переменной окружения LD_LIBRARY_PATH. С помощью утилиты ldd посмотрим от каких библиотек зависит наша программа.
1 2 3 4 5
$ ldd a.out.2 linux-vdso.so.1 (0x00007ffffe4e7000) libsomecode.so => not found libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcf3b680000) /lib64/ld-linux-x86-64.so.2 (0x00007fcf3b897000)
Добавим в LD_LIBRARY_PATH текущую директорию.
$ export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$PWD"
Видим, что наша библиотека подгрузилась.
1 2 3 4 5
ldd a.out.2 linux-vdso.so.1 (0x00007fffe2115000) libsomecode.so (0x00007f4848a6c000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4848860000) /lib64/ld-linux-x86-64.so.2 (0x00007f4848a76000)
И теперь программа запускается и работает.
1 2 3 4 5
$ ./a.out.2 "hello my little pony" > hello > my > little > pony
Если сравнить два исполняемых файла, то видим, что программа, которая использует динамическую библиотеку имеет меньший размер.
1 2 3 4 5 6 7 8 9 10
$ ls -l total 92 -rwxr-xr-x 1 ainr ainr 19816 Aug 9 21:22 a.out.1 -rwxr-xr-x 1 ainr ainr 17864 Aug 9 21:25 a.out.2 -rwxr-xr-x 1 ainr ainr 18808 Aug 9 21:24 libsomecode.so -rw-r--r-- 1 ainr ainr 123 Aug 9 21:20 main.c -rw-r--r-- 1 ainr ainr 3576 Aug 9 21:23 main.o -rw-r--r-- 1 ainr ainr 213 Aug 9 21:21 somecode.c -rw-r--r-- 1 ainr ainr 72 Aug 9 21:20 somecode.h -rw-r--r-- 1 ainr ainr 6224 Aug 9 21:23 somecode.o
Это произошло как раз за счет того, что реализация нашей функции теперь лежит за пределами нашего исполняемого файла. С помощью утилиты objdump можем глянуть внутрь бинарных файлов. И увидим, что во втором бинарном файле реализация функции отсутствует, но присутствует в библиотеке.
1 2 3 4 5 6
$ objdump -t a.out.1 | grep print_split 000000000000119c g F .text 0000000000000061 print_split $ objdump -t a.out.2 | grep print_split 0000000000000000 F *UND* 0000000000000000 print_split $ objdump -t libsomecode.so | grep print_split 0000000000001139 g F .text 0000000000000061 print_split
Разница в нашем случае может и маленькая, но в масштабах десятков и сотен файлов разница будет значительной.
Так что же мы сделали?
У нас есть исходные файлы, мы скомпилировали их, получив из них динамическую библиотеку. Далее эту библиотеку можем использовать повторно в других проектах.
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
-------------------------------- --------------------------------- | Заголовочный файл | | Реализация | | (somecode.h) | | (somecode.c) | -------------------------------- --------------------------------- | | | | | void print_split(char* string);| | void print_split(char* string) | | | | < | | | | // . | | | | >| | | | | ------------------------------- --------------------------------- | | Компилируем \ / (gcc -shared -fpic -o liblog.so . ) | --------------------------------- | Динамическая библиотека | | (liblog.so) | --------------------------------- | | (gcc . -llog) --------------------------------------------- | | | ----------------------- ----------------------- ----------------------- | Утилита1 | | Утилита2 | | Утилита3 | ----------------------- ----------------------- ----------------------- | | | | | | | #include "somecode.h" | | #include "somecode.h" | | #include "somecode.h" | | | | | | | | print_split(". "); | | print_split(". "); | | print_split(". "); | | | | | | | ----------------------- ----------------------- -----------------------
Примерно те же действия вы будете делать и под windows и под мак, будут немного другие компиляторы, но идея одна.
FAQ: Как подключить библиотеку на C/C++? Зачем файлы .h, .lib, .dll?
Этот вопрос довольно часто возникает у начинающих, а также у программистов, которые привыкли работать с языками, имеющими встроенную поддержку импорта из модулей (например, Delphi/Pascal).
В языке C исторически сложилось иначе. Чтобы подключить библиотеку к программе на языке C или C++, нужно выполнить два действия:
- Включить в исходный текст заголовочные файлы библиотеки (.h или .hpp) директивой #include
- Обеспечить, чтобы при сборке программы использовались соответствующие объектные файлы библиотеки (в зависимости от системы они могут иметь расширения .lib, .a, .o, .obj и т.д.) В зависимости от используемого компилятора это делается разными способами, например:
– добавить файл(ы) в проект как объектные;
– в MS Visual Studio: добавить имя файла в Linker->Input->Additional Dependencies (если файл в другом каталоге, путь добавить в Linker->General->Additional Library Directories);
– при использовании make прописать файл в список файлов для линкера (обычно это LIBS= или т.п.).
А если библиотека представлена в исходных текстах, надо просто добавить все нужные .c (.cpp) файлы в проект программы.
Файлы .lib должны быть совместимы с используемым компилятором и ОС. Поэтому к библиотеке может прилагаться несколько наборов файлов для разных сред.
Файлы .dll (динамическая библиотека Windows) не нужно указывать при компиляции, они используются при выполнении программы (о динамических библиотеках см. ниже).
В чём смысл этих действий, зачем нужны все эти разные файлы?
Файл заголовков (.h) нужен для того, чтобы компилятор мог распознать идентификаторы (имена макросов, типов, переменных, функций), которые определены в библиотеке. Специального средства для импорта имён в стандартном C нет, и для этого используется директива препроцессора #include, которая вставляет включаемый файл как исходный текст, как если бы он был написан вместо директивы #include.
В файлах .h идентификаторы объявляются, но не определяются (decalared but not defined), т.е. под переменные не выделяется пространство, а функции не имеют кода. Это достигается использованием ключевого слова extern для переменных, а для функций даются прототипы, например:
/* mylib.h */
extern int mylib_global_variable;
int mylib_function(int x, int y);
Если эти объявления включить через #include, то компилятор сможет скомпилировать .c файл, в котором упоминаются mylib_global_variable и mylib_function, например:
/* myprog.c */
#include «mylib.h»
int main(void)
mylib_global_variable = 1;
return mylib_function(mylib_global_variable, 2);
>
После компиляции получится объектный файл (например, myprog.o), причем в нём эти идентификаторы будут описаны как внешние. Но линкер не сможет собрать такую программу в готовый исполнимый файл, потому что для этих идентификаторов нет определений, т.е. есть имена, но нет «тела».
При попытке собрать программу будет выдано сообщение об ошибке «undefined external».
Теперь нужно, чтобы при сборке программы нашёлся объектный файл, в котором реализованы все эти переменные и функции. Допустим, у нас есть исходный текст библиотеки – например, такой:
/* mylib.c */
int mylib_global_variable = 0;
int mylib_function(int x, int y)
return x + y;
>
Тогда при его компиляции получится объектный файл, который можно соединить с нашей основной программой (включить оба .c файла в проект или makefile), и всё готово.
Если же библиотека уже скомпилирована отдельно, то нужно взять готовые объектные файлы (.lib или .a – это по сути контейнеры объектных файлов) и передать их линкеру.
Это в общих чертах всё, что касается статических библиотек, т.е. таких, код которых попадает в исполнимый файл основной программы.
О динамических библиотеках следует сказать отдельно. Динамическая библиотека отличается тем, что её код хранится в отдельном файле (Windows – .dll, в системах типа GNU/Linux и FreeBSD – .so) и загружается операционной системой при запуске программы, либо самой программой по мере надобности.
Рассмотрим оба способа на примере Windows.
Чтобы подключить динамическую библиотеку с загрузкой при запуске, используются файлы .h и .lib – точно так же, как в случае статической библиотеки. Просто в данном случае .lib файл содержит не сам код, а ссылки на импортируемые функции из DLL, так что после компиляции получается .exe файл, в котором есть ссылки на нужную DLL. Файл .dll при компиляции не нужен, зато нужен при запуске (в каталоге с .exe файлом, в системных каталогах Windows, в каталогах, перечисленных в переменной окружения PATH и т.д.).
Также можно загружать динамическую библиотеку «вручную» функциями LoadLibrary и GetProcAddress. Тогда .lib файл не нужен вообще, а .h включить обычно нужно (чтобы иметь описания типов и констант-макросов), но к функциям обращаться нужно будет не по именам из .h, а через указатели соответствующих типов, например:
/* . */
typedef int (*my_funct_ptr_t)(int, int); /* тип указателя на функцию */
HINSTANCE h_dll;
my_func_ptr_t func;
int x = 1, y = 2, z;
h_dll = LoadLibrary(«mylib.dll»);
if (NULL == h_dll) return MY_ERROR_CANNOT_LOAD_DLL;
func = (my_func_ptr_t)GetProcAddress(h_dll, «mylib_function»);
if (NULL == func) return MY_ERROR_FUNCTION_NOT_FOUND;
z = func(x, y);
/* . */
| Перейти к другим статьям FAQ | Cтатья создана: | 14.08.2014 |
| Последняя редакция: | 04.08.2016 |
