Форум программистов, компьютерный форум, киберфорум
C# .NET
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.66/29: Рейтинг темы: голосов - 29, средняя оценка - 4.66
12 / 12 / 2
Регистрация: 06.06.2012
Сообщений: 97

Обертка над libipriv.dll

22.01.2018, 10:35. Показов 5618. Ответов 9

Студворк — интернет-сервис помощи студентам
Здравствуйте уважаемые форумчане. Помогите пожалуйста. Использую библиотеку libipriv.dll для расшифровки/шифровки данных. И тут понадобилось сгенерировать новый ключ. До меня работал человек, и была какая то программа для генерации карточек. Но человека нет, проги нет. В общем все печально. Нашел, все таки я что то похожее, и попытался сгенерировать. Код привожу ниже. И выдает вот такую ошибку.
Помощник по отладке управляемого кода "PInvokeStackImbalance" : "Вызов функции PInvoke "WindowsFormsApp2!WindowsFormsApp2.IPriv ::Crypt_GenKeyCard" разбалансировал стек. Вероятно, это вызвано тем, что управляемая сигнатура PInvoke не совпадает с неуправляемой целевой сигнатурой. Убедитесь, что соглашение о вызовах и параметры сигнатуры PInvoke совпадают с неуправляемой целевой сигнатурой."

Вот экспорт библиотеки
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
 public class IPriv
    {
        // for internal usage only
        #region dll functions
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_Initialize();
 
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_Done();
 
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_OpenSecretKeyFromFile(
            int eng,
            [MarshalAs(UnmanagedType.LPStr)]string path,
            [MarshalAs(UnmanagedType.LPStr)]string passwd,
            [MarshalAs(UnmanagedType.LPArray)]byte[] pkey);
 
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_OpenPublicKeyFromFile(
            int eng,
            [MarshalAs(UnmanagedType.LPStr)]string path,
            uint keyserial,
            [MarshalAs(UnmanagedType.LPArray)]byte[] pkey,
            [MarshalAs(UnmanagedType.LPArray)]byte[] сakey);
 
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_Sign(
            [MarshalAs(UnmanagedType.LPStr)]string src,
            int nsrc,
            [MarshalAs(UnmanagedType.LPStr)]StringBuilder dst,
            int ndst,
            [MarshalAs(UnmanagedType.LPArray)]byte[] pkey);
 
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_Verify(
            [MarshalAs(UnmanagedType.LPStr)]string src,
            int nsrc,
            [MarshalAs(UnmanagedType.LPArray)]byte[] pdst,
            [MarshalAs(UnmanagedType.LPArray)]byte[] pndst,
            [MarshalAs(UnmanagedType.LPArray)]byte[] pkey);
 
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_CloseKey(
            [MarshalAs(UnmanagedType.LPArray)]byte[] pkey);
 
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_GenKeyCard(
            [MarshalAs(UnmanagedType.LPStr)]StringBuilder dst,
            int ndst,
            [MarshalAs(UnmanagedType.LPStr)]string userid,
            long keyserial);
 
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_GenKeyCardToFile(
            [MarshalAs(UnmanagedType.LPStr)]string path,
            [MarshalAs(UnmanagedType.LPStr)]string userid,
            long keyserial);
 
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_GenKey(
            int eng,
            [MarshalAs(UnmanagedType.LPStr)]string src,
            int nsrc,
            [MarshalAs(UnmanagedType.LPArray)]byte[] skey,
            [MarshalAs(UnmanagedType.LPArray)]byte[] pkey,
            int bits);
 
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_GenKeyFromFile(
            int eng,
            [MarshalAs(UnmanagedType.LPStr)]string keycardpath,
            [MarshalAs(UnmanagedType.LPArray)]byte[] skey,
            [MarshalAs(UnmanagedType.LPArray)]byte[] pkey,
            int bits);
 
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_ExportPublicKey(
            [MarshalAs(UnmanagedType.LPStr)]StringBuilder dst,
            int ndst,
            [MarshalAs(UnmanagedType.LPArray)]byte[] key,
            [MarshalAs(UnmanagedType.LPArray)]byte[] cakey);
 
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_ExportPublicKeyToFile(
            [MarshalAs(UnmanagedType.LPStr)]string path,
            [MarshalAs(UnmanagedType.LPArray)]byte[] key,
            [MarshalAs(UnmanagedType.LPArray)]byte[] cakey);
 
        // Экспорт закрытого ключа (может не поддерживаться криптопровайдером).
        // dst: выходной, буфер для приема закрытого ключа
        // ndst: входной, максимальная длина приемного буфера
        // path: входной, путь к файлу для закрытого ключа
        // passwd: входной, кодовая фраза для шифрования закрытого ключа
        // key: входной, закрытый ключ
        // Возвращает: длина тела ключа или код ошибки
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_ExportSecretKey(
            [MarshalAs(UnmanagedType.LPStr)]StringBuilder dst,
            int ndst,
            [MarshalAs(UnmanagedType.LPStr)]string passwd,
            [MarshalAs(UnmanagedType.LPArray)]byte[] key);
 
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_ExportSecretKeyToFile(
            [MarshalAs(UnmanagedType.LPStr)]string path,
            [MarshalAs(UnmanagedType.LPStr)]string passwd,
            [MarshalAs(UnmanagedType.LPArray)]byte[] key);
 
        #endregion
 
 
        /// <summary>
        /// Инициализирует криптосистему. Запускать один раз за все приложение.
        /// </summary>
        public static void Initialize()
        {
            Crypt_Initialize();
        }
 
        /// <summary>
        /// Закрывает криптосистему. Запускать один раз за все приложение.
        /// </summary>
        public static void Done()
        {
            Crypt_Done();
        }
 
        /// <summary>
        /// Загружает секретный ключ
        /// </summary>
        /// <param name="path">Путь к файлу ключа</param>
        /// <param name="passwd">Пароль к ключу</param>
        /// <returns></returns>
        public static IPrivKey openSecretKey(string path, string passwd)
        {
            IPrivKey k = new IPrivKey();
            int rc = Crypt_OpenSecretKeyFromFile(0, path, passwd, k.getKey());
            if (rc != 0)
                throw (new IPrivException(rc));
            return k;
        }
 
        /// <summary>
        /// Загружает открытый ключ
        /// </summary>
        /// <param name="path">Файл ключа</param>
        /// <param name="keyserial">Серийный ключ</param>
        /// <returns></returns>
        public static IPrivKey openPublicKey(string path, uint keyserial)
        {
            IPrivKey k = new IPrivKey();
            int rc = Crypt_OpenPublicKeyFromFile(0, path, keyserial, k.getKey(), null);
            if (rc != 0)
                throw (new IPrivException(rc));
            return k;
        }
 
        /// <summary>
        /// Подписывает сообщение секретным ключом
        /// </summary>
        /// <param name="src">Сообщение</param>
        /// <param name="key">Секретный ключ</param>
        /// <returns></returns>
        public static string signText(string src, IPrivKey key)
        {
            string dst;
            StringBuilder tmp = new StringBuilder(Encoding.UTF8.GetByteCount(src) + 1024);
            int rc = Crypt_Sign(src, src.Length, tmp, tmp.Capacity, key.getKey());
            if (rc < 0)
                throw (new IPrivException(rc));
            dst = tmp.ToString(0, rc);
            return dst;
        }
 
        /// <summary>
        /// Проверяет сообщение открытым ключом
        /// </summary>
        /// <param name="src">Сообщение</param>
        /// <param name="key">Открытый ключ</param>
        /// <returns></returns>
        public static string verifyText(string src, IPrivKey key)
        {
            int rc = Crypt_Verify(src, -1, null, null, key.getKey());
            if (rc != 0)
                throw (new IPrivException(rc));
            return "";
        }
 
        /// <summary>
        /// Закрывает ключ
        /// </summary>
        /// <param name="key"></param>
        public static void closeKey(IPrivKey key)
        {
            Crypt_CloseKey(key.getKey());
        }
 
        /// <summary>
        /// Генерирует карточку ключа на основе идентификатора пользователя и его ключа
        /// </summary>
        /// <param name="userId">Идентификатор пользователя, для которого генерируется карточка</param>
        /// <param name="serialKey">Серийный ключ карточки</param>
        /// <returns></returns>
        public static string GenerateCard(string userId, int serialKey)
        {
            string card = "";
            StringBuilder tmp = new StringBuilder(10048);
            int rc = Crypt_GenKeyCard(tmp, tmp.Capacity, userId, (long)serialKey);
            if (rc < 0)
                throw (new IPrivException(rc));
            card = tmp.ToString(0, rc);
            return card;
        }
 
        /// <summary>
        /// Генерирует карточку в файл
        /// </summary>
        /// <param name="filename">Имя файла</param>
        /// <param name="userId">Id пользователя</param>
        /// <param name="serialKey">Серийный ключ</param>
        public static void GenerateCardToFile(string filename, string userId, int serialKey)
        {
            int rc = Crypt_GenKeyCardToFile(filename, userId, (long)serialKey);
            if (rc < 0)
                throw (new IPrivException(rc));
        }
 
        /// <summary>
        /// Генерирует ключи на основе раннее сгенерированной карточки
        /// </summary>
        /// <param name="card">Карточка ключа</param>
        /// <param name="skey">Секретный ключ</param>
        /// <param name="pkey">Открытый ключ</param>
        public static void GenerateKey(string card, IPrivKey skey, IPrivKey pkey)
        {
            IPrivKey SKey = new IPrivKey();
            IPrivKey PKey = new IPrivKey();
 
            Crypt_GenKey(0, card, -1, SKey.getKey(), PKey.getKey(), 512);
 
            skey = SKey;
            pkey = PKey;
        }
 
        /// <summary>
        /// Экспорт закрытого ключа
        /// </summary>
        /// <param name="skey">Ключ</param>
        /// <param name="passwd">Пароль</param>
        /// <returns></returns>
        public static string ExportSecretKey(IPrivKey skey, string passwd)
        {
            StringBuilder sb = new StringBuilder(10048);
            Crypt_ExportSecretKey(sb, sb.Capacity, passwd, skey.getKey());
 
            return sb.ToString();
        }
 
        /// <summary>
        /// Экспорт закрытого ключа в файл
        /// </summary>
        /// <param name="filename">Имя файла</param>
        /// <param name="key">Ключ</param>
        /// <param name="passwd">Пароль</param>
        public static void ExportSecretKeyToFile(string filename, IPrivKey key, string passwd)
        {
            Crypt_ExportSecretKeyToFile(filename, passwd, key.getKey());
        }
 
        /// <summary>
        /// Экспорт открытого ключа
        /// </summary>
        /// <param name="pkey">Ключ</param>
        /// <param name="skey">Серийный ключ</param>
        /// <returns></returns>
        public static string ExportPublicKey(IPrivKey pkey, IPrivKey skey)
        {
            StringBuilder sb = new StringBuilder(10048);
            Crypt_ExportPublicKey(sb, sb.Capacity, pkey.getKey(), skey.getKey());
            return sb.ToString();
        }
    };
А вот сам код
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
 private void button1_Click(object sender, EventArgs e)
        {
                IPriv.Initialize();
                string val = "";
 
                try
                {
                    val = IPriv.GenerateCard(textBox1.Text, Convert.ToInt32(numericUpDown1.Value));
                    IPriv.GenerateCardToFile(textBox1.Text + ".dat", textBox1.Text, Convert.ToInt32(numericUpDown1.Value));
 
                    IPrivKey skey = new IPrivKey();
                    IPrivKey pkey = new IPrivKey();
 
 
                    IPriv.GenerateKey(val, skey, pkey);
 
                    pkey.closeKey();
                    skey.closeKey();
 
                }
                catch (IPrivException ex)
                {
                    MessageBox.Show(ex.ToString());
                }
 
               IPriv.Done();
        }
Что интересно функции шифрования, таких проблем в этом же проекте не вызывают. Манифест о вызове неуправляемого кода, пытался добавлять
C#
1
CallingConvention = CallingConvention.StdCall
. Ошибка та же. Помогите будьте добры. Не силен в использовании нативных библиотек.
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
22.01.2018, 10:35
Ответы с готовыми решениями:

Асинхронная обертка над классом
Доброго времени суток! Заранее извиняюсь если не в ту ветку. Суть в следующем - лазил недавно в поисках задач по C# для саморазвития и...

Обертка над INotifyPropertyChanged
Я пытаюсь написать (если это возможно) обертку над INotifyPropertyChanged. Просто во первых при его реализации в классах приходится писать...

Обертка над Си библиотекой
Добрый день, имеется библиотека написанная на Си (API для работы с оборудованием), хочу обернуть ее в код C#. Вот код написанный на...

9
Администратор
Эксперт .NET
 Аватар для OwenGlendower
18267 / 14190 / 5368
Регистрация: 17.03.2014
Сообщений: 28,877
Записей в блоге: 1
22.01.2018, 13:07
Лучший ответ Сообщение было отмечено OwenGlendower как решение

Решение

speed5.Mexanik, попробуй вот такую сигнатуру для Crypt_GenKeyCard - у последнего аргумента uint вместо long
C#
1
2
3
4
5
6
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_GenKeyCard(
            [MarshalAs(UnmanagedType.LPStr)]StringBuilder dst,
            int ndst,
            [MarshalAs(UnmanagedType.LPStr)]string userid,
            uint keyserial);
1
12 / 12 / 2
Регистрация: 06.06.2012
Сообщений: 97
22.01.2018, 14:22  [ТС]
Цитата Сообщение от OwenGlendower Посмотреть сообщение
speed5.Mexanik, попробуй вот такую сигнатуру для Crypt_GenKeyCard - у последнего аргумента uint вместо long
C#
1
2
3
4
5
6
        [DllImport("libipriv.dll")]
        internal static extern int Crypt_GenKeyCard(
            [MarshalAs(UnmanagedType.LPStr)]StringBuilder dst,
            int ndst,
            [MarshalAs(UnmanagedType.LPStr)]string userid,
            uint keyserial);
Сработало. Ошибок про управляемый и не управляемый код нет. Но вернулся код case -14: return "Вызов не поддерживается криптосредством";
Буду искать, что это такое. Спасибо.
0
12 / 12 / 2
Регистрация: 06.06.2012
Сообщений: 97
21.05.2018, 12:01  [ТС]
Продолжу в своей же теме. Здравствуйте.
Нужно сгенерировать карточку ключа. Но выдает ошибку. В чем проблема, не могу понять. Хоть кол на голове чеши. Может быть кто то сталкивался с этой библиотекой. Проект прилагаю.
Вложения
Тип файла: zip GenerateKey.zip (301.5 Кб, 10 просмотров)
0
Администратор
Эксперт .NET
 Аватар для OwenGlendower
18267 / 14190 / 5368
Регистрация: 17.03.2014
Сообщений: 28,877
Записей в блоге: 1
21.05.2018, 12:57
Лучший ответ Сообщение было отмечено speed5.Mexanik как решение

Решение

Цитата Сообщение от speed5.Mexanik Посмотреть сообщение
Но выдает ошибку.
Было бы очень неплохо если бы ты сразу выложил описание ошибки и при вызове какого метода из dll она происходит.

Насколько я вижу вызов Crypt_GenKeyCard возвращает ошибку с кодом -14 (CRYPT_ERR_NOT_SUPPORT) "Вызов не поддерживается криптосредством". Судя по исходнику метода Crypt_GenKeyCard
C++
1
2
3
4
5
6
7
8
int IPRIVAPI Crypt_GenKeyCard(char *dst, int ndst, const char *userid, unsigned long keyserial)
{
#ifdef WITHOUT_KEYGEN
    return CRYPT_ERR_NOT_SUPPORT;
#else
    return CryptWriteKeyCard(dst, ndst, keyserial, userid);
#endif
}
причина в том что DLL откомпилирована без поддержки генерации ключей. Нужна версия DLL с включенной поддержкой.
1
12 / 12 / 2
Регистрация: 06.06.2012
Сообщений: 97
21.05.2018, 17:41  [ТС]
Цитата Сообщение от OwenGlendower Посмотреть сообщение
Было бы очень неплохо если бы ты сразу выложил описание ошибки и при вызове какого метода из dll она происходит.

Насколько я вижу вызов Crypt_GenKeyCard возвращает ошибку с кодом -14 (CRYPT_ERR_NOT_SUPPORT) "Вызов не поддерживается криптосредством". Судя по исходнику метода Crypt_GenKeyCard
C++
1
2
3
4
5
6
7
8
int IPRIVAPI Crypt_GenKeyCard(char *dst, int ndst, const char *userid, unsigned long keyserial)
{
#ifdef WITHOUT_KEYGEN
    return CRYPT_ERR_NOT_SUPPORT;
#else
    return CryptWriteKeyCard(dst, ndst, keyserial, userid);
#endif
}
причина в том что DLL откомпилирована без поддержки генерации ключей. Нужна версия DLL с включенной поддержкой.
Да так точно. Ошибка -14. А подскажите, как ее перекомпилировать, как надо? С++ я вообще никак не разбираюсь ((
0
Администратор
Эксперт .NET
 Аватар для OwenGlendower
18267 / 14190 / 5368
Регистрация: 17.03.2014
Сообщений: 28,877
Записей в блоге: 1
22.05.2018, 12:28
speed5.Mexanik, необязательно именно перекомпилировать. Посмотри там где ты взял эту библиотеку. Возможно там есть разные версии.
1
12 / 12 / 2
Регистрация: 06.06.2012
Сообщений: 97
22.05.2018, 14:01  [ТС]
Цитата Сообщение от OwenGlendower Посмотреть сообщение
speed5.Mexanik, необязательно именно перекомпилировать. Посмотри там где ты взял эту библиотеку. Возможно там есть разные версии.
Спасибо. Все нашел. Все заработало.
0
Администратор
Эксперт .NET
 Аватар для OwenGlendower
18267 / 14190 / 5368
Регистрация: 17.03.2014
Сообщений: 28,877
Записей в блоге: 1
22.05.2018, 14:02
speed5.Mexanik, ссылку дай где нашел, чтобы другие не тратили время на поиск
0
12 / 12 / 2
Регистрация: 06.06.2012
Сообщений: 97
22.05.2018, 15:42  [ТС]
Цитата Сообщение от OwenGlendower Посмотреть сообщение
speed5.Mexanik, ссылку дай где нашел, чтобы другие не тратили время на поиск
Логично. Скачать libipriv.dll
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
22.05.2018, 15:42
Помогаю со студенческими работами здесь

Обертка над массивом double[]
Здравствуйте, подскажите пожалуйста с решением задания. Само задание: Нужно организовать различные виды доступа к одним и тем же...

Обертка над исключением - Сделать так, чтобы блоки try/catch не дублировались
У меня есть примерно такой код: for (...) { if (...) { if (...) { double d; try { d = (double)someobject; ...

Обёртка над WinAPI
В архиве обычная обёртка над формой на WinApi, (ничего лишнего только класс формы); Чтобы создать форму в main.cpp объявлен класс MyWnd...

Обертка над C библиотекой
Всем здравствуйте. Есть dll написанная на C и заголовочные файлы. Хочу написать обертку на C++/CLI, чтобы в дальнейшем использовать в C#....

GUI обертка над консолью
Господа, доброго времени суток. Пытаюсь сейчас перевести свою консольную программу, в гуи приложение. Никак не могу разобраться как сделать...


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

Или воспользуйтесь поиском по форуму:
10
Ответ Создать тему
Новые блоги и статьи
Инструменты COM: Сохранение данный из VARIANT в файл и загрузка из файла в VARIANT
bedvit 28.01.2026
Сохранение базовых типов COM и массивов (одномерных или двухмерных) любой вложенности (деревья) в файл, с возможностью выбора алгоритмов сжатия и шифрования. Часть библиотеки BedvitCOM Использованы. . .
Загрузка PNG с альфа-каналом на SDL3 для Android: с помощью SDL_LoadPNG (без SDL3_image)
8Observer8 28.01.2026
Содержание блога SDL3 имеет собственные средства для загрузки и отображения PNG-файлов с альфа-каналом и базовой работы с ними. В этой инструкции используется функция SDL_LoadPNG(), которая. . .
Загрузка PNG с альфа-каналом на SDL3 для Android: с помощью SDL3_image
8Observer8 27.01.2026
Содержание блога SDL3_image - это библиотека для загрузки и работы с изображениями. Эта пошаговая инструкция покажет, как загрузить и вывести на экран смартфона картинку с альфа-каналом, то есть с. . .
Влияние грибов на сукцессию
anaschu 26.01.2026
Бифуркационные изменения массы гриба происходят тогда, когда мы уменьшаем массу компоста в 10 раз, а скорость прироста биомассы уменьшаем в три раза. Скорость прироста биомассы может уменьшаться за. . .
Воспроизведение звукового файла с помощью SDL3_mixer при касании экрана Android
8Observer8 26.01.2026
Содержание блога SDL3_mixer - это библиотека я для воспроизведения аудио. В отличие от инструкции по добавлению текста код по проигрыванию звука уже содержится в шаблоне примера. Нужно только. . .
Установка Android SDK, NDK, JDK, CMake и т.д.
8Observer8 25.01.2026
Содержание блога Перейдите по ссылке: https:/ / developer. android. com/ studio и в самом низу страницы кликните по архиву "commandlinetools-win-xxxxxx_latest. zip" Извлеките архив и вы увидите. . .
Вывод текста со шрифтом TTF на Android с помощью библиотеки SDL3_ttf
8Observer8 25.01.2026
Содержание блога Если у вас не установлены Android SDK, NDK, JDK, и т. д. то сделайте это по следующей инструкции: Установка Android SDK, NDK, JDK, CMake и т. д. Сборка примера Скачайте. . .
Использование SDL3-callbacks вместо функции main() на Android, Desktop и WebAssembly
8Observer8 24.01.2026
Содержание блога Если вы откроете примеры для начинающих на официальном репозитории SDL3 в папке: examples, то вы увидите, что все примеры используют следующие четыре обязательные функции, а. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru