Внешние компоненты Native API на языке Rust - Просто!

20.08.23

Разработка - Механизмы платформы 1С

Внешние компоненты для 1С можно разработывать очень просто, пользуясь всеми преимуществами языка Rust - от безопасности и кроссплатформенности до удобного менеджера библиотек.

Скачать исходный код

Наименование Файл Версия Размер
Исходный код примера
.zip 413,81Kb
54
.zip 413,81Kb 54 Скачать бесплатно

Хочу показать, как можно разрабатывать внешние компоненты для 1С на Native API с помошью языка Rust, используя вот эту библиотеку. Пост разделен на 3 части, где я поэтапно разработаю компоненту, которая читает файл в двоичные данные, тесты для нее и make файл:

  1. Разработка
  2. Тестирование
  3. Сборка и упаковка

 

1. Разработка

Для разработки должны быть установлены:

  • Компилятор - MSVC или gcc
  • Rust - https://www.rust-lang.org/tools/install
  • Toolchain'ы - https://rust-lang.github.io/rustup/concepts/toolchains.html
    • Для MSVC: stable-x86_64-pc-windows-msvc и stable-i686-pc-windows-msvc 
    • Для gcc: stable-x86_64-pc-windows-gnu и stable-i686-pc-windows-gnu
  • Редактор кода с плагином rust-analyzer (но, конечно, можно и в блокноте)

Для начала создадим пустой проект и установим необходимые пакеты:

> cargo init byte_reader_addin --lib
> cd byte_reader_addin
> cargo add native_api_1c utf16_lit

Изменим файл манифест Cargo.toml, добавив указание о том, что результатом компиляции будет dll:

# Cargo.toml 
.... 

[lib] 
crate-type = ["cdylib"]

Вставим содержание нашей компоненты:

use std::{ 
    fs::{metadata, File}, 
    io::Read, 
    sync::Arc, 
}; 
use native_api_1c::{
    native_api_1c_core::ffi::connection::Connection, 
    native_api_1c_macro::AddIn
}; 

#[derive(AddIn)] 
pub struct ByteReader { 
    #[add_in_con] // соединение с 1С для вызова внешних событий 
    connection: Arc<Option<&'static Connection>>, // Arc для возможности многопоточности 

    #[add_in_func(name = "ReadBytes", name_ru = "ПрочитатьБайты")] 
    #[arg(Str)] 
    #[returns(Blob, result)] 
    read_bytes: fn(&Self, String) -> Result<Vec<u8>, Box<dyn std::error::Error>>, 
} 

impl ByteReader { 
    pub fn new() -> Self { 
        Self { connection: Arc::new(None), 
        read_bytes: Self::read_bytes, } 
    } 

    pub fn read_bytes(&self, path: String) -> Result<Vec<u8>, Box<dyn std::error::Error>> { 
        let mut f = File::open(&path)?; 
        let metadata = metadata(&path)?; 
        let mut buffer = vec![0; metadata.len() as usize]; 
        f.read(&mut buffer)?; 
        Ok(buffer) 
    } 
}

Подробнее про структуру описания функции:

#[add_in_func(name = "ReadBytes", name_ru = "ПрочитатьБайты")] 
#[arg(Str)] 
#[returns(Blob, result)] 
read_bytes: fn(&Self, String) -> Result<Vec<u8>, Box<dyn std::error::Error>>, 
  • #[add_in_func(name = "ReadBytes", name_ru = "ПрочитатьБайты")]
    // Имена, по которым будем обращаться к функции из 1С
  • #[arg(Str)]
    // Тип параметра, который будет передан из 1С. Если будет 
    // передан другой параметр, то будет вызвано исключение.
    // В данном случае путь к файлу в виде строки
    
  • #[returns(Blob, result)]
    // Тип возвращаемого параметра, который будет получен в 1С.
    // Обвернут в результат, т.к. фунцкия может быть выполнена
    // с ошибкой, но не с критичной.
    
  • read_bytes: fn(&Self, String) -> Result<Vec<u8>, Box<dyn std::error::Error>>, 
    // описание функции фнутри Rust.
    

Подробнее про описание фходных параметров, возвращаемых параметров, а также свойств объекта компоненты можно посмотреть здесь.

Теперь попробуем собрать:

> cargo build --target=x86_64-pc-windows-gnu

Отлично, по пути target\x86_64-pc-windows-gnu\debug\byte_reader_addin.dll находится наша компонента. Попробуем подключить ее в 1С и проверить правильность работы. Для этого используем следующий код:

Если Не Ждать ПодключитьВнешнююКомпонентуАсинх(ПутьВК, "ByteReader", ТипВнешнейКомпоненты.Native) Тогда
	ВызватьИсключение("Ошибка подключение внешней компоненты");
	Возврат;
КонецЕсли;  

ОбъектВК = Новый("Addin.ByteReader.ByteReader"); 
БайтыВК = Ждать ОбъектВК.ПрочитатьБайтыАсинх(ПутьКФайлу);
БайтыВК = БайтыВК.Значение;
Байты1С = Новый ДвоичныеДанные(ПутьКФайлу);

Если Байты1С <> БайтыВК Тогда
	ВызватьИсключение("Двоичный данные не совпали");		      	
КонецЕсли;                                       

Прочиталось = Ложь;
Попытка 
	БайтыВК = Ждать ОбъектВК.ПрочитатьБайтыАсинх("неверный путь");
	Прочиталось = Ложь;
Исключение	          
	// 
КонецПопытки;          

Если Прочиталось Тогда
	ВызватьИсключение("Должно было выйти исключение");		      	
КонецЕсли;

 

 

 

2. Тестирование

Rust предоставляет возможность тестирования из коробки, поэтому напишем простой тест, который проверяет корректность работы функций нашей компоненты. Создадим в корне файл dummy_file.txt и запишем туда что-нибудь. В моем случае это будет просто "ABC". Теперь в файле lib.rs в конец:

#[cfg(test)]
mod tests {
    #[test]
    fn test_valid_file_path() {
        let byte_reader_obj = super::ByteReader::new();
        let path = String::from("dummy_file.txt");

        let result = byte_reader_obj.read_bytes(path);
        assert!(result.is_ok());

        let bytes = result.unwrap();
        assert_eq!(bytes.len(), 3);
        assert_eq!(&bytes, &[65, 66, 67]);
    }

    #[test]
    fn test_invalid_file_path() {
        let byte_reader_obj = super::ByteReader::new();
        let path = String::from("dummy_file_2.txt");
        
         let result = byte_reader_obj.read_bytes(path);
        assert!(result.is_err());
    }
}


Запустим тестирование:

> cargo test
...
running 2 tests
test tests::test_invalid_file_path ... ok
test tests::test_valid_file_path ... ok

 

3. Сборка и упаковка

Для сборки и упаковки будем использовать инструмент cargo make, который устанавливается так:

> cargo install --force cargo-make

Компоненту в рабочую базу 1С будем добавлять через макет, а для этого нужно упаковать ее в zip архив рядом с манифестом. Добавим в корень файл Manifest.xml:

<?xml version="1.0" encoding="UTF-8"?>
<bundle xmlns="http://v8.1c.ru/8.2/addin/bundle" name="ByteReader">
    <component os="Windows" path="ByteReader_x32.dll" type="native" arch="i386" />
    <component os="Windows" path="ByteReader_x64.dll" type="native" arch="x86_64" />
</bundle>

 

 

Добавим в корень файл Makefile.toml

 
[tasks.clean]
command = "cargo"
args = ["clean"]

[tasks.remove-out.linux]
command = "rm"
args = ["-rf", "out"]

[tasks.remove-out.windows]
script_runner = "powershell"
script_extension = "ps1"
script = '''
Remove-Item -LiteralPath "out" -Force -Recurse -ErrorAction SilentlyContinue
'''

[tasks.build-release-windows-32.linux]
command = "cargo"
args = ["build", "--release", "--target", "i686-pc-windows-gnu"]

[tasks.build-release-windows-64.linux]
command = "cargo"
args = ["build", "--release", "--target", "x86_64-pc-windows-gnu"]

[tasks.build-debug-windows-32.linux]
command = "cargo"
args = ["build", "--target", "i686-pc-windows-gnu"]

[tasks.build-debug-windows-64.linux]
command = "cargo"
args = ["build", "--target", "x86_64-pc-windows-gnu"]

[tasks.build-release-windows-32.windows]
command = "cargo"
args = ["build", "--release", "--target", "i686-pc-windows-msvc"]

[tasks.build-release-windows-64.windows]
command = "cargo"
args = ["build", "--release", "--target", "x86_64-pc-windows-msvc"]

[tasks.build-debug-windows-32.windows]
command = "cargo"
args = ["build", "--target", "i686-pc-windows-msvc"]

[tasks.build-debug-windows-64.windows]
command = "cargo"
args = ["build", "--target", "x86_64-pc-windows-msvc"]

[tasks.debug]
run_task = { name = [
    "build-debug-windows-32",
    "build-debug-windows-64",
], parallel = true }

[tasks.release]
run_task = { name = [
    "build-release-windows-32",
    "build-release-windows-64",
], parallel = true }

[tasks.pack-to-zip.linux]
script = '''
mkdir -p out
cp target/i686-pc-windows-gnu/release/byte_reader_addin.dll out/ByteReader_x32.dll
cp target/x86_64-pc-windows-gnu/release/byte_reader_addin.dll out/ByteReader_x64.dll
cp Manifest.xml out/
zip -r -j out/ByteReader.zip out/ByteReader_x64.dll out/ByteReader_x32.dll out/Manifest.xml
'''

[tasks.pack-to-zip.windows]
script_runner = "powershell"
script_extension = "ps1"
script = '''
New-Item -ItemType Directory -Path out -Force -ErrorAction SilentlyContinue
cp target/i686-pc-windows-msvc/release/byte_reader_addin.dll out/ByteReader_x32.dll
cp target/x86_64-pc-windows-msvc/release/byte_reader_addin.dll out/ByteReader_x64.dll
cp Manifest.xml out/
Compress-Archive -DestinationPath out/ByteReader.zip -Path out/ByteReader_x64.dll, out/ByteReader_x32.dll, out/Manifest.xml -Force
'''

[tasks.pack]
dependencies = ["clean", "release", "remove-out", "pack-to-zip"]

 

И запустим команду сборки:

> cargo make pack

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

 

Заключение

Я вижу такой вариант реализации ВК для 1С как хорошую альтернативу реализации на C++ в случае, когда еще нет большой уже реализованной кодовой базы на плюсах, тем более если целевая компонента не очень функциональна. На собственном опыте за 15 минут была сделана компонента для проигрывания звука, не блокирующая процесс 1С, тем самым закрыв требование заказчика.

Rust дает гарантию адресной безопасности, т.е. не случится такого, что ошибка в коде внешней компоненты будет вызывать исключение, которое будет аварийно закрывать 1С.

EDIT 1: 

Хочу уточнить, что компонента проверена только на десктопных клиентах Windows и Linux. До мобильных устройств руки пока не дошли :(

Rust Внешняя компонента NativeAPI

См. также

Сервисы интеграции без Шины и интеграции

Механизмы платформы 1С Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Пример использования «Сервисов интеграции» без подключения к Шине и без обменов.

13.03.2024    2708    dsdred    16    

59

Поинтегрируем: сервисы интеграции – новый стандарт или просто коннектор?

Перенос данных 1C Администрирование СУБД Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

В платформе 8.3.17 появился замечательный механизм «Сервисы интеграции». Многие считают, что это просто коннектор 1С:Шины. Так ли это?

11.03.2024    6256    dsdred    59    

86

Как готовить и есть массивы

Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

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

24.01.2024    6139    YA_418728146    25    

68

Планы обмена VS История данных

Перенос данных 1C Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

Вы все еще регистрируете изменения только на Планах обмена и Регистрах сведений?

11.12.2023    7179    dsdred    36    

114

1С-ная магия

Механизмы платформы 1С Бесплатно (free)

Язык программирования 1С содержит много нюансов и особенностей, которые могут приводить к неожиданным для разработчика результатам. Сталкиваясь с ними, программист начинает лучше понимать логику платформы, а значит, быстрее выявлять ошибки и видеть потенциальные узкие места своего кода там, где позже можно было бы ещё долго медитировать с отладчиком в поисках источника проблемы. Мы рассмотрим разные примеры поведения кода 1С. Разберём результаты выполнения и ответим на вопросы «Почему?», «Как же так?» и «Зачем нам это знать?». 

06.10.2023    19319    SeiOkami    46    

121

Дефрагментация и реиндексация после перехода на платформу 8.3.22

Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

Начиная с версии платформы 8.3.22 1С снимает стандартные блокировки БД на уровне страниц. Делаем рабочий скрипт, как раньше.

14.09.2023    13024    human_new    27    

76

Валидация JSON через XDTO (включая массивы)

WEB-интеграция Универсальные функции Механизмы платформы 1С Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

При работе с интеграциями рано или поздно придется столкнуться с получением JSON файлов. И, конечно же, жизнь заставит проверять файлы перед тем, как записывать данные в БД.

28.08.2023    9623    YA_418728146    6    

144

Все скопируем и вставим! (Буфер обмена в 1С 8.3.24)

Механизмы платформы 1С Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Рассмотрим новую возможность 8.3.24 и как её можно эффективно использовать

27.06.2023    17180    SeiOkami    31    

106
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. Oculta 20.08.23 12:05 Сейчас в теме
Если работает, то автору низкий поклон! На днях обязательно проверю.
s22; ivv1970; +2 Ответить
2. sebekerga 95 20.08.23 12:16 Сейчас в теме
(1) Отлично. Если будут какие-то ошибки, оставляйте их в ишйус репозитория https://github.com/Sebekerga/native_api_1c/issues, чтобы я их не потерял и посмотрел, как будет время
3. acces969 344 21.08.23 06:08 Сейчас в теме
А можно подробнее преимущества этого языка над плюсами? Сборщик мусора есть? Порог вхождения лучше, чем у плюсов? Язык и связанные библиотеки бесплатные?
4. chg 21.08.23 07:17 Сейчас в теме
(3)ненапряжно гуглится различия "отличия rust от c++"
13. tesseract 25.08.23 00:39 Сейчас в теме
(3)Преимущества у rust, как раз отсутствие сборщика мусора, там контроль видимости + счетчик ссылок, что значительно быстрее сборщика. Язык и библиотеки бесплатные естественно, но по порогу входа его синтаксис отпугивает многих, но всё равно проще шаблонов. Ну и кросс-компиляция легче чем в с++, а производительность почти одинаковая.
25. lmnlmn 69 01.09.23 12:48 Сейчас в теме
(13) Счетчик ссылок превращается в недостаток как только возникнет задача запилить иерархическую структуру типа дерева или графа. А так, да, круто все.
24. ivv1970 28 31.08.23 14:19 Сейчас в теме
(3) ИМХО, C++ проще и логичнее , Rust - навороченый и усложненный JavaScript, что не отнимает у него крутости.
5. Ilya_Balter 21.08.23 11:59 Сейчас в теме
Очень классная статья, большое спасибо автору, однако при реализации столкнулся с вещами, которые не были описаны.
При сборке dll-файла возникала ошибка с линкером, для решения этой проблемы установил через choco пакет (требуется запустить shell от имени администратора):
choco install mingw

Далее, в файле Makefile.toml указаны и x32 и x64 сборка, поэтому требуется выполнить следующие строчки в командной строке:
rustup target add i686-pc-windows-msvc
rustup target add i686-pc-windows-gnu
rustup target add x86_64-pc-windows-msvc
rustup target add x86_64-pc-windows-gnu
ur_girlfriend_deformed; scipion13; sebekerga; +3 Ответить
6. scipion13 21.08.23 12:18 Сейчас в теме
Огромная благодарность автору.
В примере вызывается функция "ПрочитатьБайтыАсинх", а в макросе rust'а "ПрочитатьБайты". Откуда взялся "Асинх"?
7. sebekerga 95 21.08.23 12:35 Сейчас в теме
(6) Из синтаксис помощника:
Объект внешней компоненты (Add-in object)
<ИмяМетода>Асинх (<MethodName>Async)
Доступен, начиная с версии 8.3.18.

Доступны и другие методы, можно подробнее найти по индексу "Объект внешней компоненты". Платформа 1С сама добавляет эти функции к объекту компоненты, со стороны Rust ничего не требуется для реализаии асинх методов. Если у кого-то есть больше инфы по тому, как это устроено, прошу поделиться, т.к. я сам нашел только инфу из Синтаксис-помощника
scipion13; +1 Ответить
8. scipion13 21.08.23 16:30 Сейчас в теме
(7) С крейтом tokio компоненты работают, не проверяли?
10. sebekerga 95 21.08.23 17:30 Сейчас в теме
(8) Если вы имеете в виду внутри компонеты работать с асинхронщиной основанной на tokio, то должно работать. Если вы имеете в виду экспортировать в 1С асинхронные функции rust, то увы, так не получится. Самая простая альтернатива, это запускать долгий функционал в потоке и из него же вызывать внешнее событие.
starik-2005; scipion13; +2 Ответить
9. triviumfan 93 21.08.23 17:17 Сейчас в теме
Познавательно. Надо попробовать...
ur_girlfriend_deformed; +1 Ответить
11. user799806 23.08.23 13:24 Сейчас в теме
Добрый день! Работает ли данный метод на сервере 1С или нужно добавлять dll каждому клиентскому приложению где нужно использовать добавленную ВК.
12. sebekerga 95 24.08.23 06:13 Сейчас в теме
(11) Если я правильно понял вопрос, то нет, никаких дополнительных .dll файлов добавлять на клиентский комп не нужно
15. user799806 25.08.23 15:00 Сейчас в теме
А если на сервере платформа под Линукс и тонкий клиент под Линукс будет ли работать? У меня на учебной (бесплатной) платформе под Линукс в файловом режиме не взлетело. В файловой версии на Винде работает. По всей видимости нужно ещё формат библиотек под Линукс добавить в текст
Makefile.toml и Manifest.xml, сможете добавить? На Винде все получилось! Огромное спасибо! И ещё можно ли добавить во входящие параметры например Массивы?
(12)
16. sebekerga 95 25.08.23 15:54 Сейчас в теме
(15) Нет, массивы как входные параметры добавить нельзя, это ограничение Native API. Разве что массив байтов, также известный как ДвоичныеДанные.

На линуксе у меня получалось запускать на учебной лицензии, попробуйте этот мейкфайл и манифес:
Makefile.toml

Manifest.xml

Собирается также через
> cargo make pack

Вы также можете собирать непосредственно библиотеку без упаковки, чтобы удобнее тестировать
> cargo build --target=x86_64-unknown-linux-gnu
и подключать по пути к (в случае линукса) .so файлу
17. user799806 25.08.23 17:04 Сейчас в теме
(16)Спасибо, я видимо не так сборку запускал для Линукс.
18. sebekerga 95 25.08.23 17:52 Сейчас в теме
(17) Там в библиотеке была ошибка, которую я исправил, но еще не выпустил в крейт, может вы с ней столкнулись? Попробуйте заменить в файле Cargo.toml строку зависимости
native_api_1c = какая-то версия
на
native_api_1c = { git = "https://github.com/Sebekerga/native_api_1c" }
19. user799806 26.08.23 17:42 Сейчас в теме
(18) Все попробовал, сборка проходит, но не работает на Линукс. Пробовал и через макет и через путь.
[cargo-make] INFO - Running Task: pack-to-zip
adding: Test_x64.dll (deflated 73%)
adding: Test_x32.dll (deflated 70%)
adding: Test_x32.so (deflated 74%)
adding: Test_x64.so (deflated 77%)
adding: Manifest.xml (deflated 53%)
[cargo-make] INFO - Build Done in 7.81 seconds.
В 1С
Попытка
УстановитьВнешнююКомпоненту("ОбщийМакет.VK");
Исключение
Сообщить("Не вышло");
КонецПопытки;
на Винде все ок.
20. sebekerga 95 26.08.23 19:15 Сейчас в теме
(19) Можете, пожалуйста, установить экспанд с помощью:
cargo install cargo-expand

И показать, что выдаст команда
cargo expand


А вообще лучше напишите о проблеме в ишьюс, там будет удобнее: https://github.com/Sebekerga/native_api_1c/issues
21. user799806 26.08.23 19:37 Сейчас в теме
(20)cargo expand
   Checking test1c v0.1.0 (/home/aleksandr/rust_pro/test1c)
    Finished dev [unoptimized + debuginfo] target(s) in 0.30s

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
use std::sync::Arc;
use native_api_1c::{
    native_api_1c_core::ffi::connection::Connection, native_api_1c_macro::AddIn,
};
pub struct Test {
    #[add_in_con]
    connection: Arc<Option<&'static Connection>>,
    #[add_in_func(name = "Test", name_ru = "ОбработатьДанные")]
    #[arg(Str)]
    #[arg(Str)]
    #[returns(Str, result)]
    read_bytes: fn(&Self, String, String) -> Result<String, Box<dyn std::error::Error>>,
}
impl native_api_1c::native_api_1c_core::interface::AddInWrapper for Test {
    fn init(
        &mut self,
        interface: &'static native_api_1c::native_api_1c_core::ffi::connection::Connection,
    ) -> bool {
        self.connection = std::sync::Arc::new(Some(interface));
        true
    }
    fn get_info(&self) -> u16 {
        2000
    }
    fn done(&mut self) {}
    fn register_extension_as(&mut self) -> &[u16] {
        &{
            const ABC678_PREFIX_THAT_SHOULD_NEVER_CLASH_WITH_OUTER_SCOPE_UTF8: &str = "Test";
            const ABC678_PREFIX_THAT_SHOULD_NEVER_CLASH_WITH_OUTER_SCOPE_LEN: usize = ::utf16_lit::internals::length_as_utf16(
                ABC678_PREFIX_THAT_SHOULD_NEVER_CLASH_WITH_OUTER_SCOPE_UTF8,
            ) + 1;
            const ABC678_PREFIX_THAT_SHOULD_NEVER_CLASH_WITH_OUTER_SCOPE_UTF16­: [u16; ABC678_PREFIX_THAT_SHOULD_NEVER_CLASH_WITH_OUTER_SCOPE_LEN] = {
                let mut buffer = [0u16; ABC678_PREFIX_THAT_SHOULD_NEVER_CLASH_WITH_OUTER_SCOPE_LEN];
                let mut bytes = ABC678_PREFIX_THAT_SHOULD_NEVER_CLASH_WITH_OUTER_SCOPE_UTF8
                    .as_bytes();
                let mut i = 0;
                while let Some((ch, rest))
                    = ::utf16_lit::internals::next_code_point(bytes) {
                    bytes = rest;
                    if ch & 0xFFFF == ch {
                        buffer[i] = ch as u16;
                        i += 1;
                    } else {
                        let code = ch - 0x1_0000;
                        buffer[i] = 0xD800 | ((code >> 10) as u16);
                        buffer[i + 1] = 0xDC00 | ((code as u16) & 0x3FF);
                        i += 2;
                    }
                }
                buffer
            };
            ABC678_PREFIX_THAT_SHOULD_NEVER_CLASH_WITH_OUTER_SCOPE_UTF16­
        }
    }
    fn get_n_props(&self) -> usize {
        0usize
    }
    fn find_prop(&self, name: &[u16]) -> Option<usize> {
        None
    }
    fn get_prop_name(&self, num: usize, alias: usize) -> Option<Vec<u16>> {
        None
    }
    fn get_prop_val(
        &self,
        num: usize,
        val: native_api_1c::native_api_1c_core::ffi::types::ReturnValue,
    ) -> bool {
        false
    }
    fn set_prop_val(
        &mut self,
        num: usize,
        val: &native_api_1c::native_api_1c_core::ffi::types::ParamValue,
    ) -> bool {
        false
    }
    fn is_prop_readable(&self, num: usize) -> bool {
        false
    }
    fn is_prop_writable(&self, num: usize) -> bool {
        false
    }
    fn get_n_methods(&self) -> usize {
        1usize
    }
    fn find_method(&self, name: &[u16]) -> Option<usize> {
        if native_api_1c::native_api_1c_core::ffi::utils::os_string_nil("Test") == name {
            return Some(0usize);
        }
        if native_api_1c::native_api_1c_core::ffi::utils::os_string_nil(
            "ОбработатьДанные",
        ) == name
        {
            return Some(0usize);
        }
        None
    }
    fn get_method_name(&self, num: usize, alias: usize) -> Option<Vec<u16>> {
        if num == 0usize && alias == 0 {
            return Some(
                native_api_1c::native_api_1c_core::ffi::utils::os_string_nil("Test")
                    .into(),
            );
        }
        if num == 0usize {
            return Some(
                native_api_1c::native_api_1c_core::ffi::utils::os_string_nil(
                        "ОбработатьДанные",
                    )
                    .into(),
            );
        }
        None
    }
    fn get_n_params(&self, num: usize) -> usize {
        if num == 0usize {
            return 2usize;
        }
        0
    }
    fn get_param_def_value(
        &self,
        method_num: usize,
        param_num: usize,
        value: native_api_1c::native_api_1c_core::ffi::types::ReturnValue,
    ) -> bool {
        if method_num == 0usize {
            return false;
        }
        false
    }
    fn has_ret_val(&self, method_num: usize) -> bool {
        if method_num == 0usize {
            return true;
        }
        false
    }
    fn call_as_proc(
        &mut self,
        method_num: usize,
        params: &[native_api_1c::native_api_1c_core::ffi::types::ParamValue],
    ) -> bool {
        if method_num == 0usize {
            let Some(param_data) = params.get(0i32 as usize) else {
                return false;
            };
            let native_api_1c::native_api_1c_core::ffi::types::ParamValue::Str(
                param_1,
            ) = param_data else {
                return false;
            };
            let param_1 = native_api_1c::native_api_1c_core::ffi::utils::from_os_string(
                param_1,
            );
            let Some(param_data) = params.get(1i32 as usize) else {
                return false;
            };
            let native_api_1c::native_api_1c_core::ffi::types::ParamValue::Str(
                param_2,
            ) = param_data else {
                return false;
            };
            let param_2 = native_api_1c::native_api_1c_core::ffi::utils::from_os_string(
                param_2,
            );
            let call_result = self
                .read_bytes(param_1.clone().into(), param_2.clone().into());
            let Ok(call_result) = call_result else {
                return false;
            };
            return true;
        }
        false
    }
    fn call_as_func(
        &mut self,
        method_num: usize,
        params: &[native_api_1c::native_api_1c_core::ffi::types::ParamValue],
        val: native_api_1c::native_api_1c_core::ffi::types::ReturnValue,
    ) -> bool {
        if method_num == 0usize {
            let Some(param_data) = params.get(0i32 as usize) else {
                return false;
            };
            let native_api_1c::native_api_1c_core::ffi::types::ParamValue::Str(
                param_1,
            ) = param_data else {
                return false;
            };
            let param_1 = native_api_1c::native_api_1c_core::ffi::utils::from_os_string(
                param_1,
            );
            let Some(param_data) = params.get(1i32 as usize) else {
                return false;
            };
            let native_api_1c::native_api_1c_core::ffi::types::ParamValue::Str(
                param_2,
            ) = param_data else {
                return false;
            };
            let param_2 = native_api_1c::native_api_1c_core::ffi::utils::from_os_string(
                param_2,
            );
            let call_result = self
                .read_bytes(param_1.clone().into(), param_2.clone().into());
            let Ok(call_result) = call_result else {
                return false;
            };
            val.set_str(
                &native_api_1c::native_api_1c_core::ffi::utils::os_string_nil(
                    String::from(&call_result).as_str(),
                ),
            );
            return true;
        }
        false
    }
    fn set_locale(&mut self, loc: &[u16]) {}
    fn set_user_interface_language_code(&mut self, lang: &[u16]) {}
}
pub static mut PLATFORM_CAPABILITIES: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(
    -1,
);
#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn GetAttachType() -> native_api_1c::native_api_1c_core::ffi::AttachType {
    native_api_1c::native_api_1c_core::ffi::AttachType::Any
}
#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn DestroyObject(
    component: *mut *mut std::ffi::c_void,
) -> std::ffi::c_long {
    native_api_1c::native_api_1c_core::ffi::destroy_component(component)
}
#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn GetClassObject(
    name: *const u16,
    component: *mut *mut std::ffi::c_void,
) -> std::ffi::c_long {
    match *name as u8 {
        b'1' => {
            let add_in_1 = Test::new();
            native_api_1c::native_api_1c_core::ffi::create_component(component, add_in_1)
        }
        _ => 0,
    }
}
#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn GetClassNames() -> *const u16 {
    {
        const ABC678_PREFIX_THAT_SHOULD_NEVER_CLASH_WITH_OUTER_SCOPE_UTF8: &str = "1";
        const ABC678_PREFIX_THAT_SHOULD_NEVER_CLASH_WITH_OUTER_SCOPE_LEN: usize = ::utf16_lit::internals::length_as_utf16(
            ABC678_PREFIX_THAT_SHOULD_NEVER_CLASH_WITH_OUTER_SCOPE_UTF8,
        ) + 1;
        const ABC678_PREFIX_THAT_SHOULD_NEVER_CLASH_WITH_OUTER_SCOPE_UTF16­: [u16; ABC678_PREFIX_THAT_SHOULD_NEVER_CLASH_WITH_OUTER_SCOPE_LEN] = {
            let mut buffer = [0u16; ABC678_PREFIX_THAT_SHOULD_NEVER_CLASH_WITH_OUTER_SCOPE_LEN];
            let mut bytes = ABC678_PREFIX_THAT_SHOULD_NEVER_CLASH_WITH_OUTER_SCOPE_UTF8
                .as_bytes();
            let mut i = 0;
            while let Some((ch, rest)) = ::utf16_lit::internals::next_code_point(bytes) {
                bytes = rest;
                if ch & 0xFFFF == ch {
                    buffer[i] = ch as u16;
                    i += 1;
                } else {
                    let code = ch - 0x1_0000;
                    buffer[i] = 0xD800 | ((code >> 10) as u16);
                    buffer[i + 1] = 0xDC00 | ((code as u16) & 0x3FF);
                    i += 2;
                }
            }
            buffer
        };
        ABC678_PREFIX_THAT_SHOULD_NEVER_CLASH_WITH_OUTER_SCOPE_UTF16­
    }
        .as_ptr()
}
impl Test {
    pub fn new() -> Self {
        Self {
            connection: Arc::new(None),
            read_bytes: Self::read_bytes,
        }
    }
    pub fn read_bytes(
        &self,
        imya: String,
        telefon: String,
    ) -> Result<String, Box<dyn std::error::Error>> {
        let f = imya + &telefon;
        Ok(f)
    }
}
Показать
22. sebekerga 95 29.08.23 14:48 Сейчас в теме
(21) Да, у вас судя по всему та самая проблема, она исправлена в основной ветке. Так же я сегодня выкатил версию 0.10.3, там это исправление есть. Можете обновиться, должно работать на линухе - если нет, то надо уже смотреть подробнее и пытаться воспроизвести. Если проблема продолжится, пожалуйста, оставьте ишьюс здесь: https://github.com/Sebekerga/native_api_1c/issues
23. user799806 31.08.23 10:43 Сейчас в теме
(22)Добрый день! Все таки не взлетело написал вам в github
14. tesseract 25.08.23 00:46 Сейчас в теме
(11) Если NativeApi правильно добавить по правилам БСП, она сама подключится в зависимости от контекста использования. Для этого и нужен файл Manifest.xml. Платформа сама найдет и загрузит нужную библиотеку из шаблона, даже если клиент и сервер на разных ОС. В статье описан только вариант под окошки - но мак/пингвин/окна проверено работает. Если конечно сами библиотеки по стандартам написаны.
ivv1970; sebekerga; +2 Ответить
26. I_G_O_R 69 06.09.23 13:26 Сейчас в теме
Кто-то уже использовал на проде?
поделитесь, как оно
sebekerga; +1 Ответить
30. sebekerga 95 19.09.23 11:32 Сейчас в теме
(26) У нас в проде есть, как я писал в статье, компонента для воспроизведения звуков для голосовых подсказок, работает отлично, сильно упрощает работу. Но, конечно, очень интересно было бы послушать опыт других людейб не меня :)
Я же правильно понимаю, что вы medigor на гитхабе? Или ошибаюсь?
31. I_G_O_R 69 19.09.23 12:16 Сейчас в теме
(30)
да, это я.
я кстати тоже гуглил и ничего не нагуглил, пришлось самому разбираться.
а решение оказывается уже было, мое решение похоже на tuplecats, только я обошелся без макросов, дженериков оказалось достаточно (для низкоуровнего апи)

У нас в проде есть

это супер, осталось получить отзыв от пользователей линукс серверов. У меня в проде нельзя к сожалению.
32. sebekerga 95 19.09.23 12:38 Сейчас в теме
(31) Я видел ваше решения, по сути мое решение это просто чуток доделанное ваше решение + макрос. Если вы будете еще работать над эти проектом, то надо будет что-то придумать, чтобы слепить два репозитория (ваше и мое "ядро"). В любом случае, спасибо, что ваш репозиторий хорошо задукоментировали, мне это ОЧЕНЬ помогло
27. user595654_Darkpaladin2000 15.09.23 13:46 Сейчас в теме
Круто сделал!
Занимался год назад тем же, только у меня более низкоуровневый подход (минимум макросов), и “архитектура» как в C++.
https://github.com/tuplecats/rust-native-1c
sebekerga; +1 Ответить
28. Гость 17.09.23 11:20
(27) Ваше решение плохо гуглится, надо каких-то тегов добавить и в описание добавить ключевых слов
29. sebekerga 95 19.09.23 11:30 Сейчас в теме
(27) Блин, если честно, теперь ощущение, что я сделал велосипед, потому что ваша либа могла бы быть хорошей основой для моего макроса. Я столько перегуглил про Rust Native API, но почему-то не наткнулся на вашу работу
33. quick 583 20.09.23 10:35 Сейчас в теме
Восхитительное решение! Просто нет слов!
Пробую писать драйвер оборудования, но столкнулся с проблемой. Надо реализовать метод с возращаемым параметром типа строка. Не пойму как правильно описать функцию

    #[add_in_func(name="GetDescription", name_ru="ПолучитьОписание")]
    #[arg(Str)]
    #[returns(Bool, result)]
    pub get_description: fn(&Self, &mut str) -> Result<bool, ()>

Так не получается


#[derive(AddIn)]
| ^^^^^ the trait `From<String>` is not implemented for `&mut str`
|
= help: the following other types implement trait `From<T>`:
<String as From<&String>>
<String as From<&mut str>>
<String as From<&str>>
<String as From<Box<str>>>
<String as From<Cow<'a, str>>>
<String as From<char>>
= note: required for `String` to implement `Into<&mut str>`
Показать


Как быть с этим?
34. sebekerga 95 20.09.23 12:48 Сейчас в теме
(33) Это, наверное, на моей стороне косяк, т.к. не очень репрезентативный флаг типа параметра, но #[arg(Str)] означает, что типа переменной может быть получен из String, но слайсы нельзя получить из String. Компилятор вам на это и наругался. Попробуйте поменять тип параметра с &mut str на String
35. quick 583 20.09.23 19:10 Сейчас в теме
Попробовал, не проходит.
Дело в том что бывают еще параметры типа out, в которые можно возвращать значения, но для этого arg должен позволять mut аргументы.
Не пойму как это добавить. Подскажите в каком модуле копнуть что бы добавить такую возможность

Попробовал разные варианты с типом String, пока что безуспешно.
    #[add_in_func(name="Test", name_ru="Тест")]
    #[arg(Str)]
    #[returns(Bool, result)]
    pub test: fn(&Self, String) -> Result<bool, ()>,

    fn test(&self, arg: String) -> Result<bool, ()> {
        arg = String::from("value"); << нельзя, т.к. не mut
        Ok(true)
    }
Показать


в модуле native_api_1c_core:interfaces есть метод
fn call_as_func(
        &mut self,
        method_num: usize,
        params: &[ParamValue],
        val: ReturnValue,
    ) -> bool;

По идее мне надо как раз изменять значение ссылки параметра ParamValue, это и будет out параметр.

Вижу что надо где то править в native_api_1c_macro:gen_functions, но в шаблонах еще не силен, пока что не осилил.
sebekerga; +1 Ответить
36. I_G_O_R 69 20.09.23 23:52 Сейчас в теме
(35) очевидно нужно начать отсюда:
params: &[ParamValue],

изменить на:
params: &mut [ParamValue],

и соответственно еще в куче мест, возможно надо перепроектировать тип ParamValue

но api на out параметрах ужасно выглядят, может лучше возвращать значение стандартным образом?
если значений больше 1, то вернуть просто json.
37. quick 583 21.09.23 08:42 Сейчас в теме
(36) с удовольствием обошелся бы без out, но спецификация разработки драйвера оборудования оперирует только out параметрами.

Хороший повод для меня изучить rust, пока что вижу что это один из самых удобных языков для таких задач.
39. sebekerga 95 21.09.23 11:08 Сейчас в теме
(37) Не читал документацию по драйверам, поэтому для меня сюрприз. Но реально там всё через out параметры
38. sebekerga 95 21.09.23 11:01 Сейчас в теме
(35) Из документации 1С по поводу метода API "CallAsProc" и "CallAsFunc":
Память для массива параметров выделяется и освобождается "1С:Предприятием"..
Как я это понял, 1С сама выделяет память на эти параметры и отдает нам указатель на массив с ними, и про out параметры не подумал. Чтобы реализовать такое, нужно вот тут добавить логику подмены указателя, переданного от 1С. В целом, как я вижу, сделать можно, и если вам несложно, можете пж создать ишьюс в репозитории, я добавлю это в очередь того, что хочу сделать, но не обещаю, что сделаю скоро :) Либо же, можете реализовать сами, буду только рад помощи
40. I_G_O_R 69 21.09.23 12:37 Сейчас в теме
(38)
нужно вот тут добавить логику подмены указателя, переданного от 1С

Нет, там не указатель нужно подменять, а в самом Variant изменять значения, типа как это сделано тут
Указатель от 1С - это указатель на массив параметров, его трогать не надо, надо обновлять значения этого массива.
41. sebekerga 95 21.09.23 13:35 Сейчас в теме
(40) Имел в виду, что конкретно для типа строка (ну и блоба, соотвественно) просто установить значение не получится - нужно выделять память через мем. менеджер заново, а старую подчищать, т.к. нет гарантии, что длинна изначальной переменной будет такой же, как длинна нового значения. Короче надо будет на выходных попробовать сделать.
42. sebekerga 95 22.09.23 07:49 Сейчас в теме
(40) (37) Попробовал, получилось сделать out параметры, в частности со строкой получилось. Потихоньку перенесу это в либу, думаю в таком формате:
#[add_in_func(name = "ОбновитьСтроку", name_ru = "TransformString")]
#[arg(Str, out)] <-- конкретно укажем, что этот параметр будет обработан в режиме "out"
#[returns(Bool, result)]
transform_str: fn(&Self, &mut String) -> String
Так как хочу оставить out параметры только как крайнюю меру, для которой нужно очень точно опеределить, что такой режим работы включен для переменной.
43. quick 583 22.09.23 08:32 Сейчас в теме
(42)
только как крайнюю меру, для которой нужно очень точно опеределить, что такой режим работы включен для переменной


Великолепно!!! Возращаемые переменные это скорее исключение из привычной практики и в таком виде отлично смотрится!
44. sebekerga 95 24.09.23 11:18 Сейчас в теме
(43) Можете пробовать, но пока что только через репу:
[dependencies]
native_api_1c = { git = "https://github.com/Sebekerga/native_api_1c", tag = "impl_out_params" }
Пример:
Код компоненты

Код для проверки
45. Ambakollajder 25.09.23 15:15 Сейчас в теме
В windows если метод компоненты возвращает строку и эту строку попробовать передать с сервера или на сервер , платформа выдает ошибку Текст XML содержит недопустимый символ в позиции 17 :
00:15:5D:38:01:00\0 , я так понимаю не нравится null терминатор строки. Можно как то побороть эту ошибку на стороне компоненты?

И второе на линукс в режиме отладки зависает подключение компоненты на сервере, если выполнять код вне отладки все работает. И второе в тонком клиенте на линукс заметил что через раз работает вызов метода на русском языке, если вызывать на английском проблем нет.
46. sebekerga 95 25.09.23 20:37 Сейчас в теме
47. quick 583 30.09.23 13:39 Сейчас в теме
(46) Что то все больше тащусь от rust, за эти несколько дней удалось его получше узнать.
Удалось разобраться с двумя проблемами, на одну смог сделать запрос на слияние, на второй коммит что то не разобрался как сделать пул реквест, видимо нужно было сначала сделать отдельную ветку.

1. Ситуация когда out параметр и &mut Self
https://github.com/Sebekerga/native_api_1c_macro/pull/2

2. Ошибка "не найден метод" на русском языке при повторных вызовах.
https://github.com/Sebekerga/native_api_1c/issues/8
48. superspy2008 03.10.23 00:57 Сейчас в теме
подскажите, пожалуйста, не сталкивались ли Вы с реализациями Rust библиотеки под COM компоненту? Все же, существует масса запросов на исполнение вне 1С тяжелых алгоритмов вроде сериализации или шифрования, где в методы необходимо передавать/возвращать объекты 1С. Native API работает только с примитивными типами данных, что резко сокращает количество сценариев использования
49. I_G_O_R 69 03.10.23 09:13 Сейчас в теме
(48) Для COM проще всего C# взять
50. superspy2008 03.10.23 16:56 Сейчас в теме
(49) это не "проще", а совершенно другой рантайм и требования к окружению, не путайте. Интересует конкретно Rust
51. sebekerga 95 11.10.23 09:14 Сейчас в теме
(48) Нет, к сожалению, не подскажу по COM API, т.к. в общем-то особо удобного инструмента для создания COM интерфейсов с помощью Rust нет (по крайней мере долгая сессия гуглежа не дала результатов).

Единственное, разве для задач сериализации, шифрования, хеширования и других подобных операций требуется передачи объекта 1С? Предположу, что это будет узким горлышком, и куда быстрее будет передавать в компоненту данные в текстовом виде? Тем более что Native API позволяет вам даже не клонировать данные в памяти, а работать напрямую с тем, что уже есть в памяти 1С.
52. I_G_O_R 69 11.10.23 09:59 Сейчас в теме
(51)
Вот тут примеры есть:
https://github.com/microsoft/com-rs/tree/master/examples/basic
сама репа устарела, я хз, в новой будут работать или нет.
superspy2008; sebekerga; +2 Ответить
53. superspy2008 13.10.23 12:35 Сейчас в теме
54. superspy2008 13.10.23 12:43 Сейчас в теме
(51)
работать напрямую с тем, что уже есть в памяти 1С

я не углублялся в тему, пока испытываю больше академический интерес. Пытаться разбирать кишки дерева значений по указателю, не имея представления о его ABI, выглядит как-то наивно, нужна очень серьезная подготовка. Значениевстрокувнутр может являться решением для передачи структур в Native API среде, но это лишняя сериализация, питонистам или типичным специалистам 1С, может, и все равно на это, но Rust не про такой подход к использованию ресурсов :) Спасибо за комментарии
Оставьте свое сообщение