Форум программистов, компьютерный форум, киберфорум
locm
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  

Пишем драйвер на PureBasic

Запись от locm размещена 26.12.2014 в 21:46
Показов 13689 Комментарии 0

Обычно драйверы для Windows пишут на языке C++, возможно потому что DDK фирма Майкрософт предоставила именно для этого языка. Но энтузиасты портировали DKK для других языков. Сейчас мы поговорим о создании драйверов на языке бейсик, который на первый взгляд не подходит для этого (хотя бы потому что бейсик считается простым языком, не предназначенным для системного программирования), но это только на первый взгляд. Теперь давайте определится с диалектом бейсика и компилятором, который будем использовать при разработке драйверов. Наверное стоит выбрать бейсик от фирмы Майкрософт, ведь по логике, он должен лучше всего подходить для разработки под Windows. Претендентами были выбраны Small Basic, VB и VB.NET.
Small Basic и VB.NET были исключены почти сразу. Первый из-за ограниченных возможностей и наличия внешнего рантайма, а второй из-за компиляции только в управляемый код, что нам не подходит, ведь на уровне ядра нет .NET Framework.
VB для создания драйверов в принципе подходит и в соседнем блоге это доказали, но методы создания драйвера, а так же внешний рантайм, ограничивают возможности и снижают удобство разработки.
Поэтому был продолжен поиск требуемого компилятора бейсика, но уже от сторонних фирм, не имеющих прямого отношения к Майкрософт. Подходящим компилятором оказался PureBasic, фирмы Fantaisie Software. Правда он не бесплатный. На данный момент, стоимость индивидуальной лицензии на все версии и платформы составляет 79€.
PureBasic подошел в первую очередь потому, что в его дистрибутиве есть почти все что нужно для компиляции драйвера и необходимо лишь немного изменить ключи ликовки чтобы получить не приложение, а драйвер. Но обо всем по порядку. Прежде рассмотрим процесс компиляции в PureBasic. Он проходит в несколько этапов. Сначала бейсик-код транслируется в ассемблер, а затем при помощи компилятора ассемблера FASM создается объектный файл, который линкуется программой Polink, с функциями PureBasic, находящимися в статических библиотеках и в результате создается исполняемый файл. Чтобы получился драйвер, а не приложение, достаточно изменить ключ линковки с
Code
1
/SUBSYSTEM:Windows
на
Code
1
/SUBSYSTEM:native /driver
При этом будет создан драйвер, но он окажется не рабочим, т. к. не решена еще одна проблема - несмотря на то что получился драйвер, он использует WinAPI функции вместо функций ядра. Точнее, это вшитый в него рантайм вызывает некоторые WinAPI функции при инициализации и завершении работы. Есть как минимум два варианта решения этой проблемы.
  1. Исключить рантайм из кода.
  2. Заставить рантайм использовать функции ядра вместо WinAPI.
Первый вариант реализовать не сложно. В процессе компиляции получаем ассемблерный код и несложный препроцессор, находящийся между транслятором бейсик кода (pbcompiler.exe) и ассемблером FASM справится с этой задачей. Но у такого решения есть большой минус - многие возможности станут недоступными. Например, станут недоступными строковые переменные, динамические и ассоциативные массивы, связные списки, перестанут работать многие функции и т. д. Это не лучшее решение, ведь теряется вся простота бейсика.

Второй вариант выглядит трудноосуществимым, ведь функции PureBasic скомпилированы и хранятся в статических библиотеках. Их декомпиляция, модификация и сборка, займут много времени и велика вероятность допустить ошибку, что сделать довольно просто учитывая что придется модифицировать ассемблерный код многих функций.
Так как же быть, ведь без рантайма отсутствуют многие возможности языка, но и использовать его в драйвере не представляется возможным из-за использования WinAPI функции вместо функций ядра?
На самом деле есть довольно простой способ без модификации кода рантайма и функций PureBasic, "отучить" их от WinAPI, причем не просто "отучить", но заставить использовать функции ядра. Звучит невероятно, правда? Но это возможно. Напомню, что рантайм и все функции PureBasic скомпилированы и находятся в статических библиотеках. Причем импорт WinAPI функций осуществляется посредством импорта символов из других статических библиотек и явно не указано из каких. Ничего не мешает подменить статические библиотеку из которой импортируются символы WinAPI функций (это например user32.lib, kernel32.lib и др.), на нашу из которой экспортируются символы с именами как в WinAPI функций. Таким образом чтобы отвязать рантайм и функции от WinAPI, требуется написать n-ое количество функций-переходников, которые являются в той или иной мере, аналогами WinAPI, но из них будут вызываться функции ядра. Конечно не для всех WinAPI функций есть аналоги в ядре, но во многих случаях это решаемо.
Приведу пример аналога WinAPI функции Sleep(), которая вызывает функцию ядра KeDelayExecutionThread().
PureBasic
1
2
3
4
5
6
7
8
9
10
11
12
ProcedureDLL RndLib_Sleep(TimeMS)
  Protected Time.q
 
  Time=TimeMS*10000  ; Перевод времени из миллисекунд и сотни наносекунд.
  Time=Time-(Time*2) ; Для задержки относительно текущего времени, число должно быть отрицательным.
 
  ProcedureReturn KeDelayExecutionThread(#KernelMode, #False, @Time)
  !public _Procedure0 as '_Sleep@4'
  !public __imp__Sleep
  !__imp__Sleep:
  !dd _Procedure0
EndProcedure
Как наверное поняли, код написан на PureBasic, но есть проблема, PureBasic не поддерживает создание статических библиотек, что странно, учитывая что в его дистрибутиве есть все необходимое для этого. Поэтому сборка статической библиотеки производилась в два этапа. Сначала утилитой coffIT из этого кода создавался в объектный файл (утилита при этом использовала pbcompiler и fasm), а затем, собиралась статическая библиотека при помощи утилиты polib, находящейся в папке Compilers дистрибутива PureBasic.
В этом коде, все что находится после символа "!" не обрабатывается компилятором PureBasic, а передается fasm в неизменном виде. Оператор public создает общедоступные метки, которые не будут удалены при компиляции и останутся в статической библиотеке. Именно это нам и нужно. Линкер вместо WinAPI функции Sleep() использует нашу, а поскольку есть вызов функции KeDelayExecutionThread, то линкер попытается ее найти в статических библиотеках и обнаружив в "ntoskrnl.lib", добавит функцию в импорт драйвера. WinAPI функция Sleep() туда не попадет, поскольку она заменена на нашу. Аналогичным образом подменяются и другие функции. Это позволило не только успешно инициализировать рантайм, но и заставить работать многие функции PureBasic, среди которых оказались и те, которые в драйвере вряд ли понадобятся. Например функции получения хеша (CRC32, MD5, SHA1 и т. д.) данных в памяти, а так же функции работы с регулярными выражениями.

В результате всего этого, был создан пакет файлов, который находится во вложении.
Установка.
  1. Установить PureBasic 5.11 x86 в любую папку.
  2. Открыть папку с установленным PureBasic и в папке "Compilers" переименовать файлы Fasm.exe и Polink.exe в Fasm_.exe и Polink_.exe.
  3. Открыть папку "PureLibraries\Windows\Libraries\" и удалить все файлы.
  4. Извлечь архив в папку с установленным PureBasic 5.11 x86.
  5. Запустить программу "IDE PB5.11 x86 Patch.exe", которая изменит расширение сохраняемых исполняемых файлов с .exe на .sys.
Подчеркну что нужна версия PureBasic именно 5.11 x86. С другими версиями этот пакет может быть не совместим.

Отдельно нужно сказать про память ядра. Она существует двух типов - подкачиваемая и неподкачиваемая.
Цитата с wasm.ru
Системные кучи (к пользовательским кучам не имеют никакого отношения) представлены двумя так называемыми пулами памяти, которые, естественно, располагаются в системном адресном пространстве:
Пул неподкачиваемой памяти (Nonpaged Pool). Назван так потому, что его страницы никогда не сбрасываются в файл подкачки, а значит, никогда и не подкачиваются назад. Т. е. этот пул всегда присутствует в физической памяти и доступен при любом IRQL. Одна из причин его существования в том, что обращение к такой памяти не может вызвать ошибку страницы (Page Fault). Такие ошибки приводят к краху системы, если происходят при IRQL >= DISPATCH_LEVEL.
Пул подкачиваемой памяти (Paged Pool). Назван так потому, что его страницы могут быть сброшены в файл подкачки, а значит должны быть подкачаны назад при последующем к ним обращении. Эту память можно использовать только при IRQL строго меньше DISPATCH_LEVEL.
Оба пула находятся в системном адресном пространстве, а значит, доступны из контекста любого процесса. Для выделения памяти в системных пулах существует набор функций ExAllocatePoolXxx, а для возвращения выделенной памяти всего одна - ExFreePool.
По умолчанию используется только неподкачиваемая память что необходимо для инициализации рантайма, которая хоть и происходит на уровне PASSIVE_LEVEL, но ведь в дальнейшем может произойти обращение к рантайму при более высоком IRQL и если он окажется равен или выше DISPATCH_LEVEL и "очень повезет" что требуемая область памяти окажется в файле подкачки - получим BSoD "на ровном месте". Но использовать для всего только неподкачиваемую память, тоже нерационально. Для возможности выбора типа памяти (подкачиваемая или нет), был добавлен макрос.
PureBasic
1
SetPoolMode(Mode)
Возможные значения.
PureBasic
1
2
3
4
5
Enumeration
  #Pool_NonPaged ; Использовать только неподкачиваемую память. Это по умолчанию.
  #Pool_Paged    ; Использовать только подкачиваемую память.
  #Pool_Auto     ; Автоматический выбор типа памяти в зависимости от IRQL.
EndEnumeration
Узнать текущий тип памяти можно с помощью макроса.
PureBasic
1
GetPoolMode()
Смена типа памяти действует только на последующие выделения памяти, а тип уже выделенной памяти не меняется. И кроме того, это действует только на функции PureBasic. На вызов функции ядра ExAllocatePool() и ей подобных, не распространяется. Так же нужно следить за освобождением памяти. Даже если драйвер был выгружен из памяти, это не освободит ресурсы и память будет занята до перезагрузки системы. Если память неподкачиваемая, это равносильно уменьшению размера физической памяти!
В конце процедуры DriverUnload() нужно добавить строку, которая освободит память используемую рантаймом PureBasic.
Assembler
1
!CALL _PB_EOP
Теперь рассмотрим простой пример драйвера, который только информирует о своей загрузке и выгрузке.
PureBasic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Declare DriverEntry(*DriverObject, *RegistryPath)
*Point=@DriverEntry()
!jmp [p_Point]
 
IncludePath #PB_Compiler_Home+"DDK\"
XIncludeFile "ntddk.pbi"
XIncludeFile "ntstatus.pbi"
XIncludeFile "ntfunct.pbi"
 
 
Procedure DriverUnload(*DriverObject.DRIVER_OBJECT)
  
  DbgPrint("Unload Driver")
  
  !CALL _PB_EOP ; Освобождение ресурсов.
EndProcedure
 
Procedure DriverEntry(*DriverObject.DRIVER_OBJECT, *RegistryPath.UNICODE_STRING)
  
  DbgPrint("Load Driver")
  
  *DriverObject\DriverUnload = @DriverUnload()
  ProcedureReturn #STATUS_SUCCESS
EndProcedure
Рассмотрим код подробнее. Оператор Declare объявляет процедуру с именем DriverEntry(). Это необходимо потому что компилятор однопроходный, а процедура расположена ниже по коду относительно обращения к ней. Затем в переменную *Point помещается указатель на процедуру DriverEntry() и происходит переход по адресу в переменной *Point, т. е. в процедуру DriverEntry(). Возможно возникнет вопрос, почему не указать в качестве точки входа процедуру DriverEntry() и избавится от подобного прыжка по адресу? Сделать такое конечно можно, но при этом рантайм останется не инициализированным.
Оператор IncludeFile определяет путь к подключаемым файлам, а оператор XIncludeFile подключает их, причем делает это только один раз, т. е. если в исходнике окажется несколько подключений одного и того же файла, то будет подключен только один экземпляр, а остальные проигнорированы.
При выполнении кода процедуры DriverEntry() выводится отладочное сообщение "Load Driver", записывается в структуру *DriverObject адрес процедуры выгрузки драйвера и возвращается значение успешного выполнения.
Когда драйвер выгружается, будет выполнена процедура адрес которой сохранен в поле DriverUnload структуры *DriverObject. В данном коде это процедура DriverUnload(). В ней отсылается отладочное сообщение "Unload Driver", а затем, вызывается подпрограмма "_PB_EOP". Это необходимо чтобы рантайм "убрал за собой", т. е. освободил все те ресурсы что были использованы им при инициализации.
В этой же процедуре нужно освободить все ресурсы, используемые драйвером (например, память, различные хендлы и т. д.). Потому что даже если драйвер был выгружен, это не освободит ресурсы и они будут заняты до перезагрузки системы. В случае памяти, если она неподкачиваемая, это равносильно уменьшению размера физической памяти!
Все глобальные объекты (строки, массивы, связанные списки) так же нужно уничтожать при выгрузке драйвера. Наиболее просто это сделать если их поместить в общую структуру. Тогда все сведется к одной строке - очистке структуры функцией ClearStructure().

Компиляция драйвера не сложнее создания обычного приложения. В меню "Компилятор", нужно кликнуть по пункту "Создать драйвер" и получим скомпилированный драйвер в указанной папке.

Нажмите на изображение для увеличения
Название: Compile.PNG
Просмотров: 908
Размер:	23.0 Кб
ID:	2934

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

Нажмите на изображение для увеличения
Название: PE_Explorer_Import_1.PNG
Просмотров: 872
Размер:	3.8 Кб
ID:	2937 Нажмите на изображение для увеличения
Название: PE_Explorer_Import_2.PNG
Просмотров: 884
Размер:	4.5 Кб
ID:	2938

Драйвером импортируется функция KeGetCurrentIrql() из hal.dll, используемая для определения текущего IRQL при автоматическом выборе типа памяти (подкачиваемая или нет). Из ntoskrnl.exe импортируются функции DbgPrint(), memset(), ExAllocatePool() и ExFreePool(). Первую можно видеть в коде драйвера и она используется для вывода отладочных сообщений. Вторая функция предназначена для заполнения памяти заданными данными. В нашем случае, она используется для обнуления (очистки) памяти. Третья и четвертая функция, выделяют и освобождают память. Этих функций достаточно для инициализации рантайма, причем действительно нужны только две ExAllocatePool() и ExFreePool(), а остальные можно или исключить при определенных условиях (функция KeGetCurrentIrql()) или заменить на небольшой участок кода (функция memset()). Довольно неплохо если учитывать что рантайм PureBasic изначально рассчитан на WinAPI, а не на функции ядра.
Размер файла драйвера получился равным 2 КБ. Возможно покажется что это много для такого простого драйвера, но в этот объем входит статически прилинкованный рантайм и пара функций-переходников эмулирующих WinAPI и вызывающих функции ядра. Если отказаться это всего этого и вырезать рантайм, то размер драйвера получится около 570 байт. Но нужно ли это? Отсутствие рантайма не позволит использовать многие возможности языка.

Теперь давайте запустим драйвер и посмотрим как он работает. Лучше это делать на виртуальной машине. Для этого понадобятся утилиты KmdManager и Dbgview. Первая запускает драйвер, а вторая отображает то, что было отправлено функцией DbgPrint(). После установки на PureBasic предлагаемого архива, эти утилиты будут доступны через меню "Инструменты". Их следует запускать с правами администратора. Для Windows 7 нужно выполнить эту рекомендацию.
Цитата Сообщение от Убежденный Посмотреть сообщение
Далее по поводу DbgPrint(Ex). Как известно, на Windows Vista и выше весь
отладочный вывод фильтруется, поэтому чтобы увидеть свои отладочные сообщения,
нужно включить соответствующий флаг в реестре на гостевой машине.
Ключ HKLM\SYSTEM\CurrentControlSet\Control\Se ssion Manager\Debug Print Filter.
Я использую параметр DEFAULT (тип REG_DWORD) со значением 0xf (15), это включает полный отладочный вывод. Хотя возможны и другие варианты.
Также нужно убедится что в меню "Capture" программы Dbgview есть галочка в пункте "Capture Kernel". Если ее там нет, ставим и перезапускаем программу.
В программе KmdManager указываем путь к драйверу и последовательно нажимаем на кнопки "Register", "Run", "Stop" и "Unregister". Если все сделано правильно, увидим примерно такую картину.

Нажмите на изображение для увеличения
Название: TestDriver.PNG
Просмотров: 1181
Размер:	19.9 Кб
ID:	2939

Это довольно простой драйвер, но его код можно еще сильнее упростить до такого.
PureBasic
1
2
3
4
5
ImportC "ntoskrnl.lib" ; Импорт cdecl-функций.
  DbgPrint(String.s)
EndImport
 
DbgPrint("Load Driver")
Код максимально прост. Импортируется функция DbgPrint() из "ntoskrnl.lib", а затем она вызывается отправляя отладочное сообщение "Load Driver".

Нажмите на изображение для увеличения
Название: TestDriver_2.PNG
Просмотров: 1126
Размер:	19.4 Кб
ID:	2940

Возможно те "кто в теме" подумают что этот код нормально работать не будет и вызовет BSoD и они будут по своему правы. Дело вот в чем. При выходе из функции DriverEntry() производится освобождение 8 байт из стека (2 локальных переменных - параметра процедуры, по 4 байта каждый), а затем возврат по текущему адресу в стеке. В этом коде ничего подобного нет. Как же он тогда работает? На самом деле все это есть. Препроцессор, находящийся между pbcompiler и fasm добавляет такой ассемблерный код.
Assembler
1
2
3
 CALL _PB_EOP
 MOV eax,-1073741438
 RET 8
Чтобы было понятней, привожу весь ассемблерный код драйвера, передаваемый FASMу.
Assembler
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
format MS COFF
extrn _DbgPrint
extrn _ExitProcess@4
extrn _GetModuleHandleA@4
extrn _HeapCreate@12
extrn _HeapDestroy@4
extrn _memset
extrn PB_StringBase
extrn _SYS_InitString@0
extrn _SYS_FreeStrings@0
extrn _PB_StringBasePosition
public _PB_Instance
public _PB_ExecutableType
public _PB_OpenGLSubsystem
public _PB_MemoryBase
public PB_Instance
public PB_MemoryBase
public _PB_EndFunctions
 
macro pb_public symbol
{
 public  _#symbol
 public symbol
_#symbol:
symbol:
}
 
macro    pb_align value { rb (value-1) - ($-_PB_DataSection + value-1) mod value }
macro pb_bssalign value { rb (value-1) - ($-_PB_BSSSection  + value-1) mod value }
 
public PureBasicStart
section '.code' code readable executable           
PureBasicStart:
 PUSH dword I_BSSEnd-I_BSSStart
 PUSH dword 0
 PUSH dword I_BSSStart
 CALL _memset
 ADD esp,12
 PUSH dword 0
 CALL _GetModuleHandleA@4
 MOV [_PB_Instance],eax
 PUSH dword 0
 PUSH dword 4096
 PUSH dword 0
 CALL _HeapCreate@12
 MOV [PB_MemoryBase],eax
 CALL _SYS_InitString@0
; 
 PUSH dword _S1
 CALL _DbgPrint
 ADD esp,4
 CALL _PB_EOP
 MOV eax,-1073741438
 RET 8
_PB_EOP_NoValue:
; PUSH dword 0
_PB_EOP:
 CALL _PB_EndFunctions
 CALL _SYS_FreeStrings@0
 PUSH dword [PB_MemoryBase]
 CALL _HeapDestroy@4
 RET
; CALL _ExitProcess@4
_PB_EndFunctions:
 RET
section '.data' data readable writeable
_PB_DataSection:
_PB_OpenGLSubsystem: db 0
pb_public PB_DEBUGGER_LineNumber
 dd -1
pb_public PB_DEBUGGER_IncludedFiles
 dd 0
pb_public PB_DEBUGGER_FileName
 db 0
pb_public PB_Compiler_Unicode
 dd 0
pb_public PB_Compiler_Thread
 dd 0
pb_public PB_Compiler_Purifier
 dd 0
_PB_ExecutableType: dd 0
public _SYS_StaticStringStart
_SYS_StaticStringStart:
_S1: db "Load Driver",0
pb_public PB_NullString
 db 0
public _SYS_StaticStringEnd
_SYS_StaticStringEnd:
align 4
align 4
s_s:
 dd 0
 dd -1
align 4
section '.bss' readable writeable
_PB_BSSSection:
align 4
I_BSSStart:
_PB_MemoryBase:
PB_MemoryBase: rd 1
_PB_Instance:
PB_Instance: rd 1
align 4
PB_DataPointer rd 1
align 4
align 4
align 4
align 4
I_BSSEnd:
section '.data' data readable writeable
SYS_EndDataSection:
Теперь рассмотрим драйвер по сложнее, в котором используются функции PureBasic, которые не работали бы без инициализации рантайма и подмены некоторых WinAPI функций на их аналоги в ядре, т. к. было бы невозможно использование двусвязного списка и строк.
PureBasic
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
Declare DriverEntry(*DriverObject, *RegistryPath)
*Point=@DriverEntry()
!jmp [p_Point]
 
IncludePath #PB_Compiler_Home+"DDK\"
XIncludeFile "ntddk.pbi"
XIncludeFile "ntstatus.pbi"
XIncludeFile "ntfunct.pbi"
 
Procedure DriverUnload(*DriverObject.DRIVER_OBJECT)
  !CALL _PB_EOP ; Освобождение ресурсов.
EndProcedure
 
Procedure DriverEntry(*DriverObject.DRIVER_OBJECT, *RegistryPath.UNICODE_STRING)
  
  SetPoolMode(#Pool_Auto)    ; Автоматический выбор типа памяти в зависимости от IRQL.
  
  NewList x()
  For i=1 To 10
    If AddElement(x())
      x()=i*10
    EndIf
  Next i
  
  DbgPrint("Size list "+ListSize(x())+" items")
  ForEach x()
    DbgPrint(Str(x()))
  Next
  
  *DriverObject\DriverUnload = @DriverUnload()
  ProcedureReturn #STATUS_SUCCESS
EndProcedure
В процедуре DriverEntry() в первую очередь включатся автоматический выбор типа памяти (по умолчанию используется только неподкачиваемая память). Затем создается связный список с именем x, после чего в цикле в него добавляется 10 элементов. Далее формируется строка сообщающая о количестве элементов в списке и в цикле ForEach выводится текущее содержимое элементов списка.

В этом примере связный список является локальным и он автоматически уничтожается при завершении работы процедуры в которой он создан. Если бы он был бы глобальным, то необходимо было самим его очищать, т. е. вызывать функцию FreeList() когда список становился не нужным. Но в любом случае, чтобы избежать утечки памяти, это потребовалось бы сделать в процедуре DriverUnload() вызываемой при выгрузке драйвера.

Если запустить драйвер, увидим такую картину.

Нажмите на изображение для увеличения
Название: LinkedList.PNG
Просмотров: 827
Размер:	20.7 Кб
ID:	2942

В папке Examples архива, есть много примеров различных драйверов, как простых, только показывающих работу функций PureBasic, переведенных с WinAPI на функции ядра, так и примеров по сложнее, среди которых можно найти коды драйверов, предоставляющих доступ к портам компьютера, защищающих процесс от завершения, скрывающих указанные процессы в диспетчере задач, а так же драйвер обрабатывающий прерывания от LPT порта.
Вложения
Тип файла: zip PB_5.11_x86_DriverPack_v2.2.zip (1.11 Мб, 611 просмотров)
Размещено в Драйверы
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Нейросеть на алгоритме "эстафета хвоста" как перспектива.
Hrethgir 06.05.2026
На десерт, когда запущу сервер. Статья тут https:/ / habr. com/ ru/ articles/ 1030914/ . Автор я сам, нейросеть только помогает в вопросах которые мне не известны - не знаю людей которые знали-бы. . .
Асинхронный приём данных из COM-порта
Argus19 01.05.2026
Асинхронный приём данных из COM-порта Купил на aliexpress термопринтер QR701. Он оказался странным. Поключил к Arduino Nano. Был очень удивлён. Наотрез отказывается печатать русские буквы. Чтобы. . .
попытка написать игровой сервер на C++
pyirrlicht 29.04.2026
попытка написать игровой сервер на плюсах с открытым бесконечным миром. возможно получится прикрутить интерпретатор питон для кастомизации игровой логики. что есть на текущий момент:. . .
Контроль уникальности выбранного документа-основания при изменении реквизита
Maks 28.04.2026
Алгоритм из решения ниже разработан на примере нетипового документа "ЗаявкаНаРемонтСпецтехники", разработанного в КА2. Задача: уведомлять пользователя, если указанная заявка (документ-основание). . .
Благородство как наказание
Maks 24.04.2026
У хорошего человека отношения с женщинами всегда складываются трудно. А я человек хороший. Заявляю без тени смущения, потому что гордиться тут нечем. От хорошего человека ждут соответствующего. . .
Валидация и контроль данных табличной части документа перед записью
Maks 22.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа, разработанного в КА2. Задача: контроль и валидация данных табличной части документа перед записью с учетом регламента компании. . .
Отчёт о затраченных материалах за определенный период с макетом печатной формы
Maks 21.04.2026
Отчёт из решения ниже размещён в конфигурации КА2. Задача: разработка отчёта по затраченным материалам за определённый период, с возможностью вывода печатной формы отчёта с шапкой и подвалом. В. . .
Отчёт о спецтехнике находящейся в ремонте
Maks 20.04.2026
Отчёт из решения ниже размещен в конфигурации КА2. Задача: отобразить спецтехнику, которая на данный момент находится в ремонте. Есть нетиповой документ "Заявка на ремонт спецтехники" который. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru