diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c8e9e48
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+target
+Cargo.lock
+.vscode
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..ba93aff
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "addin"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib"]
+
+[profile.release]
+opt-level = "z" # Optimize for size.
+lto = true # Enable Link Time Optimization
+codegen-units = 1 # Reduce number of codegen units to increase optimizations.
+panic = "abort" # Abort on panic
+strip = true # Automatically strip symbols from the binary.
+
+[dependencies]
+utf16_lit="2.0"
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9d02919
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 https://github.com/medigor
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 86dcd74..f5f28d2 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,55 @@
-# example-native-add-rs
\ No newline at end of file
+# example-native-api-rs
+Пример внешней компоненты для **1С:Предприятие 8** по технологии **Native API** на языке rust
+
+[Документция на ИТС](https://its.1c.ru/db/metod8dev#content:3221:hdoc) |
+[Шаблон компоненты на c++ от Infactum](https://github.com/Infactum/addin-template)
+
+## Преимущества по сравнению с компонентой на c++
+* Преимущества самого языка rust и его экосистемы
+* Для Windows не требуется msvc, собирается полностью с использованием свободных инструментов
+
+## Обзор
+Компоненты по технологии Native API предполагают разработку на языке с++, т.к. компонента должна принимать и возвращать указатели на виртуальные классы c++. Компонента для windows должна собираться только компилятором msvc, а для linux и macos подойдет gcc/clang.
+Как известно, взаимодействие *rust* с *c++* из коробки не поддерживается.
+
+Одним из вариантов было использовать [cxx](https://github.com/dtolnay/cxx) или подобные библиотеки. Это также бы потребовало использовать msvc.
+
+Другой вариант - вручную реализовать виртуальные таблицы, именно этот вариант и реализован.
+На [godbolt](https://godbolt.org/z/KM3jaWMWs) можно посмотреть, как выглядят виртуальные таблицы для разных компиляторов. Виртуальные таблицы *msvc* отличаются от *gcc*/*clang*, при этом *gcc* и *clang* используют одинаковое ABI. Виртуальные таблицы реализованы в объеме достаточном для создания компоненты.
+
+## Описание файлов
+* src/lib.rs - корень крейта, здесь располагаются экспортные функции GetClassNames и др.
+* src/ffi.rs - в этом модуле всё что связано с взаимодействием, также здесь находится весь небезопасный код.
+* src/addin1.rs - здесь непосредственно реализация компоненты, причем весь код безопасный.
+* conf1c - конфигурация 1С (выгрузка из конфигуратора 8.3.22), минимальный тестовый код.
+
+## Разработка
+Я использую для разработки VS Code. Отлаживать и тестировать компоненту удобнее всего в файловой базе. Чтобы при нажатии F5 сразу запускалась 1С, нужно поместить в файл *.vscode/launch.json* примерно такой код:
+```json
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "lldb",
+ "request": "launch",
+ "name": "Debug 1С",
+ "program": "путь/к/файлу/1cv8c",
+ "args": [
+ "/IBName",
+ "Test1"
+ ],
+ "cwd": "${workspaceFolder}",
+ "preLaunchTask": "rust: cargo build"
+ }
+ ]
+}
+```
+Для разработки на linux я использую виртуальную машину Hyper-V, VS Code подключается по ssh. Чтобы запуск 1С работал из ssh, нужно в конфигурацию запуска добавить:
+```json
+"env": {"DISPLAY": ":1"}
+```
+Для разработки и тестирования также подходит [Учебная версия 1С](https://online.1c.ru/catalog/free/learning.php), но версия для windows только x32.
+
+MacOS не тестировал, думаю должно работать.
+
+Android/iOS/веб-клиент не реализовано и планов таких нет.
diff --git a/conf1c/ConfigDumpInfo.xml b/conf1c/ConfigDumpInfo.xml
new file mode 100644
index 0000000..9d17dd6
--- /dev/null
+++ b/conf1c/ConfigDumpInfo.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/conf1c/Configuration.xml b/conf1c/Configuration.xml
new file mode 100644
index 0000000..ec71127
--- /dev/null
+++ b/conf1c/Configuration.xml
@@ -0,0 +1,221 @@
+
+
+
+
+
+ 9cd510cd-abfc-11d4-9434-004095e12fc7
+ 29e7f5a4-dc26-408a-bd6e-083b5bf1384c
+
+
+ 9fcd25a0-4822-11d4-9414-008048da11f9
+ f76706d7-a451-49f0-a3b8-8c92dc9eb401
+
+
+ e3687481-0a87-462c-a166-9f34594f9bba
+ efad9454-c037-40e6-92f8-de6f995cb912
+
+
+ 9de14907-ec23-4a07-96f0-85521cb6b53b
+ 5a509aa9-2e5a-47a8-977a-8fbe609d782c
+
+
+ 51f2d5d8-ea4d-4064-8892-82951750031e
+ 3982e56b-1417-490c-8ac4-7672c7bffa80
+
+
+ e68182ea-4237-4383-967f-90c1e3370bc7
+ 862a5e5b-5378-4b7c-b567-0ab6725fc840
+
+
+ fb282519-d103-4dd3-bc12-cb271d631dfc
+ ed5cd24c-da69-4a84-a011-421e03c8dd71
+
+
+
+ Конфигурация
+
+
+
+ Version8_3_22
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ Taxi
+ Version8_3_22
+
+
+
+ Русский
+ Обработка1
+
+
+
\ No newline at end of file
diff --git a/conf1c/DataProcessors/Обработка1.xml b/conf1c/DataProcessors/Обработка1.xml
new file mode 100644
index 0000000..514d872
--- /dev/null
+++ b/conf1c/DataProcessors/Обработка1.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+ aa584d2a-1a1f-4807-b2e6-588aef5193b8
+ 620a7e0f-3713-4585-842f-262d1dfa18c0
+
+
+ e6189229-5433-466b-a435-ac6f2fa36046
+ 8606e276-c11b-4eaa-af7b-424d33285d14
+
+
+
+ Обработка1
+
+
+ true
+ DataProcessor.Обработка1.Form.Форма
+
+ false
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/conf1c/DataProcessors/Обработка1/Forms/Форма.xml b/conf1c/DataProcessors/Обработка1/Forms/Форма.xml
new file mode 100644
index 0000000..ba12bd1
--- /dev/null
+++ b/conf1c/DataProcessors/Обработка1/Forms/Форма.xml
@@ -0,0 +1,22 @@
+
+
+
+
\ No newline at end of file
diff --git a/conf1c/DataProcessors/Обработка1/Forms/Форма/Ext/Form.xml b/conf1c/DataProcessors/Обработка1/Forms/Форма/Ext/Form.xml
new file mode 100644
index 0000000..121f7ba
--- /dev/null
+++ b/conf1c/DataProcessors/Обработка1/Forms/Форма/Ext/Form.xml
@@ -0,0 +1,83 @@
+
+
\ No newline at end of file
diff --git a/conf1c/DataProcessors/Обработка1/Forms/Форма/Ext/Form/Module.bsl b/conf1c/DataProcessors/Обработка1/Forms/Форма/Ext/Form/Module.bsl
new file mode 100644
index 0000000..773023c
--- /dev/null
+++ b/conf1c/DataProcessors/Обработка1/Forms/Форма/Ext/Form/Module.bsl
@@ -0,0 +1,85 @@
+
+&НаКлиенте
+Процедура Тест1(Команда)
+ Тест1НаСервере(ИмяФайла);
+КонецПроцедуры
+
+&НаСервереБезКонтекста
+Процедура Тест1НаСервере(ИмяФайла)
+
+ Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
+
+ Если Не ПодключитьВнешнююКомпоненту(ИмяФайла, "Test", ТипВнешнейКомпоненты.Native, ТипПодключенияВнешнейКомпоненты.НеИзолированно) Тогда
+ Сообщить("Не удалось подключить");
+ Возврат;
+ КонецЕсли;
+
+ Сообщить("Подключена");
+ ОбъектКомпоненты = Новый ("AddIn.Test.Class1");
+ Test = ОбъектКомпоненты.Test;
+ Конец = ТекущаяУниверсальнаяДатаВМиллисекундах();
+ Сообщить(СтрШаблон("Test: %1", Test));
+ Сообщить(СтрШаблон("Длительность: %1", Конец - Начало));
+
+КонецПроцедуры
+
+
+&НаКлиенте
+Процедура Тест2(Команда)
+ Тест2НаСервере(ИмяФайла);
+КонецПроцедуры
+
+&НаСервереБезКонтекста
+Процедура Тест2НаСервере(ИмяФайла)
+
+ Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
+
+ Попытка
+ ОбъектКомпоненты = Новый ("AddIn.Test.Class1");
+ Исключение
+ Если Не ПодключитьВнешнююКомпоненту(ИмяФайла, "Test", ТипВнешнейКомпоненты.Native, ТипПодключенияВнешнейКомпоненты.НеИзолированно) Тогда
+ ВызватьИсключение "Не удалось подключить";
+ КонецЕсли;
+ ОбъектКомпоненты = Новый ("AddIn.Test.Class1");
+ КонецПопытки;
+
+ ОбъектКомпоненты.PropI32 = 123;
+ Если ОбъектКомпоненты.PropI32 <> 123 Тогда
+ ВызватьИсключение "Не удалось установить значение PropI32";
+ КонецЕсли;
+
+ ОбъектКомпоненты.PropF64 = 456.789;
+ Если ОбъектКомпоненты.PropF64 <> 456.789 Тогда
+ ВызватьИсключение "Не удалось установить значение PropF64";
+ КонецЕсли;
+
+ ОбъектКомпоненты.PropBool = Истина;
+ Если ОбъектКомпоненты.PropBool <> Истина Тогда
+ ВызватьИсключение "Не удалось установить значение PropBool";
+ КонецЕсли;
+
+ Date = ТекущаяДатаСеанса();
+ ОбъектКомпоненты.PropDate = Date;
+ Если ОбъектКомпоненты.PropDate <> Date Тогда
+ ВызватьИсключение "Не удалось установить значение PropDate";
+ КонецЕсли;
+
+ ОбъектКомпоненты.PropStr = "Привет!";
+ Если ОбъектКомпоненты.PropStr <> "Привет!" Тогда
+ ВызватьИсключение "Не удалось установить значение PropStr";
+ КонецЕсли;
+
+ Blob = ПолучитьДвоичныеДанныеИзСтроки("Привет!");
+ ОбъектКомпоненты.PropBlob = Blob;
+ Если ОбъектКомпоненты.PropBlob <> Blob Тогда
+ ВызватьИсключение "Не удалось установить значение PropBlob";
+ КонецЕсли;
+
+ Если ОбъектКомпоненты.Method1("11", "22", "33") <> "112233" Тогда
+ ВызватьИсключение "Не удалось установить значение Method1";
+ КонецЕсли;
+
+ Конец = ТекущаяУниверсальнаяДатаВМиллисекундах();
+ Сообщить(СтрШаблон("Длительность: %1", Конец - Начало));
+
+КонецПроцедуры
diff --git a/conf1c/Ext/HomePageWorkArea.xml b/conf1c/Ext/HomePageWorkArea.xml
new file mode 100644
index 0000000..dc7c9e4
--- /dev/null
+++ b/conf1c/Ext/HomePageWorkArea.xml
@@ -0,0 +1,14 @@
+
+
+ TwoColumnsEqualWidth
+
+ -
+
+ 10
+
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/conf1c/Languages/Русский.xml b/conf1c/Languages/Русский.xml
new file mode 100644
index 0000000..a62fde0
--- /dev/null
+++ b/conf1c/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file
diff --git a/src/addin1.rs b/src/addin1.rs
new file mode 100644
index 0000000..5a5ece1
--- /dev/null
+++ b/src/addin1.rs
@@ -0,0 +1,208 @@
+use crate::ffi::{Addin, Connection, ParamValue, ReturnValue, Tm};
+use utf16_lit::utf16_null;
+
+const PROPS: &[&[u16]] = &[
+ &utf16_null!("Test"),
+ &utf16_null!("PropI32"),
+ &utf16_null!("PropF64"),
+ &utf16_null!("PropBool"),
+ &utf16_null!("PropDate"),
+ &utf16_null!("PropStr"),
+ &utf16_null!("PropBlob"),
+];
+
+const METHODS: &[&[u16]] = &[&utf16_null!("Method1")];
+
+pub struct Addin1 {
+ prop_i32: i32,
+ prop_f64: f64,
+ prop_bool: bool,
+ prop_date: Tm,
+ prop_str: String,
+ prop_blob: Vec,
+}
+
+impl Addin1 {
+ pub fn new() -> Addin1 {
+ Addin1 {
+ prop_i32: 0,
+ prop_f64: 0.0,
+ prop_bool: false,
+ prop_date: Tm::default(),
+ prop_str: String::new(),
+ prop_blob: Vec::new(),
+ }
+ }
+}
+
+impl Drop for Addin1 {
+ fn drop(&mut self) {}
+}
+
+impl Addin for Addin1 {
+ fn init(&mut self, _interface: &'static Connection) -> bool {
+ true
+ }
+
+ fn get_info(&mut self) -> u16 {
+ 1000
+ }
+
+ fn done(&mut self) {}
+
+ fn register_extension_as(&mut self) -> &'static [u16] {
+ &utf16_null!("Class1")
+ }
+
+ fn get_n_props(&mut self) -> usize {
+ PROPS.len()
+ }
+
+ fn find_prop(&mut self, name: &[u16]) -> Option {
+ PROPS.iter().position(|&x| x == name)
+ }
+
+ fn get_prop_name(&mut self, num: usize, _alias: usize) -> Option<&'static [u16]> {
+ PROPS.get(num).map(|&x| x)
+ }
+
+ fn get_prop_val(&mut self, num: usize, val: ReturnValue) -> bool {
+ match num {
+ 0 => val.set_i32(111226),
+ 1 => val.set_i32(self.prop_i32),
+ 2 => val.set_f64(self.prop_f64),
+ 3 => val.set_bool(self.prop_bool),
+ 4 => val.set_date(self.prop_date),
+ 5 => {
+ let s: Vec = self.prop_str.encode_utf16().collect();
+ val.set_str(s.as_slice());
+ }
+ 6 => {
+ val.set_blob(self.prop_blob.as_slice());
+ }
+ _ => (),
+ };
+ true
+ }
+
+ fn set_prop_val(&mut self, num: usize, val: &ParamValue) -> bool {
+ match num {
+ 1 => match val {
+ ParamValue::I32(x) => {
+ self.prop_i32 = *x;
+ true
+ }
+ _ => false,
+ },
+ 2 => match val {
+ ParamValue::F64(x) => {
+ self.prop_f64 = *x;
+ true
+ }
+ _ => false,
+ },
+ 3 => match val {
+ ParamValue::Bool(x) => {
+ self.prop_bool = *x;
+ true
+ }
+ _ => false,
+ },
+ 4 => match val {
+ ParamValue::Date(x) => {
+ self.prop_date = *x;
+ true
+ }
+ _ => false,
+ },
+ 5 => match val {
+ ParamValue::Str(x) => {
+ self.prop_str = String::from_utf16(x).unwrap();
+ true
+ }
+ _ => false,
+ },
+ 6 => match val {
+ ParamValue::Blob(x) => {
+ self.prop_blob.clear();
+ self.prop_blob.extend_from_slice(x);
+ true
+ }
+ _ => false,
+ },
+ _ => false,
+ }
+ }
+
+ fn is_prop_readable(&mut self, _num: usize) -> bool {
+ true
+ }
+
+ fn is_prop_writable(&mut self, num: usize) -> bool {
+ match num {
+ 2 => true,
+ 3 => true,
+ _ => true,
+ }
+ }
+
+ fn get_n_methods(&mut self) -> usize {
+ METHODS.len()
+ }
+
+ fn find_method(&mut self, name: &[u16]) -> Option {
+ METHODS.iter().position(|&x| x == name)
+ }
+
+ fn get_method_name(&mut self, num: usize, _alias: usize) -> Option<&'static [u16]> {
+ METHODS.get(num).map(|&x| x)
+ }
+
+ fn get_n_params(&mut self, num: usize) -> usize {
+ match num {
+ 0 => 3,
+ _ => 0,
+ }
+ }
+
+ fn get_param_def_value(
+ &mut self,
+ _method_num: usize,
+ _param_num: usize,
+ _value: ReturnValue,
+ ) -> bool {
+ true
+ }
+
+ fn has_ret_val(&mut self, num: usize) -> bool {
+ match num {
+ 0 => true,
+ _ => false,
+ }
+ }
+
+ fn call_as_proc(&mut self, _num: usize, _params: &[ParamValue]) -> bool {
+ false
+ }
+
+ fn call_as_func(&mut self, num: usize, params: &[ParamValue], ret_value: ReturnValue) -> bool {
+ match num {
+ 0 => {
+ let mut buf = Vec::::new();
+ for p in params {
+ match p {
+ ParamValue::Str(x) => buf.extend_from_slice(x),
+ _ => return false,
+ }
+ }
+ ret_value.set_str(buf.as_slice());
+ true
+ }
+ _ => false,
+ }
+ }
+
+ fn set_locale(&mut self, _loc: &[u16]) {}
+
+ fn set_user_interface_language_code(&mut self, _lang: &[u16]) {}
+}
diff --git a/src/ffi.rs b/src/ffi.rs
new file mode 100644
index 0000000..3eff9b8
--- /dev/null
+++ b/src/ffi.rs
@@ -0,0 +1,753 @@
+use std::{
+ ffi::{c_int, c_long, c_ulong, c_void},
+ mem::size_of,
+ ptr,
+ slice::{from_raw_parts, from_raw_parts_mut},
+};
+
+#[repr(C)]
+#[derive(Debug)]
+pub enum AttachType {
+ CanAttachNotIsolated = 1,
+ CanAttachIsolated,
+ CanAttachAny,
+}
+
+#[repr(C)]
+#[allow(dead_code)]
+#[derive(Clone, Copy, Default)]
+pub struct Tm {
+ pub sec: c_int, // seconds after the minute - [0, 60] including leap second
+ pub min: c_int, // minutes after the hour - [0, 59]
+ pub hour: c_int, // hours since midnight - [0, 23]
+ pub mday: c_int, // day of the month - [1, 31]
+ pub mon: c_int, // months since January - [0, 11]
+ pub year: c_int, // years since 1900
+ pub wday: c_int, // days since Sunday - [0, 6]
+ pub yday: c_int, // days since January 1 - [0, 365]
+ pub isdst: c_int, // daylight savings time flag
+
+ #[cfg(target_family = "unix")]
+ pub gmtoff: std::ffi::c_long, // seconds east of UTC
+ #[cfg(target_family = "unix")]
+ pub zone: std::ffi::c_char, // timezone abbreviation
+}
+
+#[allow(dead_code)]
+pub struct ReturnValue<'a> {
+ mem: &'a MemoryManager,
+ variant: &'a mut TVariant,
+}
+
+#[allow(dead_code)]
+impl<'a> ReturnValue<'a> {
+ pub fn set_empty(self) {
+ self.variant.vt = VariantType::EMPTY;
+ }
+ pub fn set_i32(self, val: i32) {
+ self.variant.vt = VariantType::I4;
+ self.variant.value.i32 = val;
+ }
+
+ pub fn set_bool(self, val: bool) {
+ self.variant.vt = VariantType::BOOL;
+ self.variant.value.bool = val;
+ }
+
+ pub fn set_f64(self, val: f64) {
+ self.variant.vt = VariantType::R8;
+ self.variant.value.f64 = val;
+ }
+
+ pub fn set_date(self, val: Tm) {
+ self.variant.vt = VariantType::TM;
+ self.variant.value.tm = val;
+ }
+
+ pub fn set_str(self, val: &[u16]) {
+ let Some(data) = self.mem.alloc_memory::(val.len()) else {
+ return;
+ };
+
+ data.copy_from_slice(val);
+
+ self.variant.vt = VariantType::PWSTR;
+ self.variant.value.data_str.ptr = data.as_ptr() as *mut u16;
+ self.variant.value.data_str.len = data.len() as u32;
+ }
+
+ pub fn set_blob(self, val: &[u8]) {
+ let Some(data) = self.mem.alloc_memory::(val.len()) else {
+ return;
+ };
+
+ data.copy_from_slice(val);
+
+ self.variant.vt = VariantType::BLOB;
+ self.variant.value.data_blob.ptr = data.as_ptr() as *mut u8;
+ self.variant.value.data_blob.len = data.len() as u32;
+ }
+
+ pub fn alloc_str(self, len: usize) -> Option<&'a mut [u16]> {
+ let Some(data) = self.mem.alloc_memory::(len) else {
+ return None;
+ };
+
+ self.variant.vt = VariantType::PWSTR;
+ self.variant.value.data_str.ptr = data.as_ptr() as *mut u16;
+ self.variant.value.data_str.len = data.len() as u32;
+
+ Some(data)
+ }
+
+ pub fn alloc_blob(self, len: usize) -> Option<&'a mut [u8]> {
+ let Some(data) = self.mem.alloc_memory::(len) else {
+ return None;
+ };
+
+ self.variant.vt = VariantType::BLOB;
+ self.variant.value.data_blob.ptr = data.as_ptr() as *mut u8;
+ self.variant.value.data_blob.len = data.len() as u32;
+
+ Some(data)
+ }
+}
+
+pub enum ParamValue<'a> {
+ Empty,
+ Bool(bool),
+ I32(i32),
+ F64(f64),
+ Date(Tm),
+ Str(&'a [u16]),
+ Blob(&'a [u8]),
+}
+
+impl<'a> From<&'a TVariant> for ParamValue<'a> {
+ fn from(param: &'a TVariant) -> ParamValue {
+ unsafe {
+ match param.vt {
+ VariantType::EMPTY => Self::Empty,
+ VariantType::BOOL => Self::Bool(param.value.bool),
+ VariantType::I4 => Self::I32(param.value.i32),
+ VariantType::R8 => Self::F64(param.value.f64),
+ VariantType::TM => Self::Date(param.value.tm),
+ VariantType::PWSTR => Self::Str(from_raw_parts(
+ param.value.data_str.ptr,
+ param.value.data_str.len as usize,
+ )),
+ VariantType::BLOB => Self::Blob(from_raw_parts(
+ param.value.data_blob.ptr,
+ param.value.data_blob.len as usize,
+ )),
+ _ => Self::Empty,
+ }
+ }
+ }
+}
+
+#[repr(u16)]
+#[allow(dead_code)]
+enum VariantType {
+ EMPTY = 0,
+ NULL,
+ I2, //int16_t
+ I4, //int32_t
+ R4, //float
+ R8, //double
+ DATE, //DATE (double)
+ TM, //struct tm
+ PSTR, //struct str string
+ INTERFACE, //struct iface
+ ERROR, //int32_t errCode
+ BOOL, //bool
+ VARIANT, //struct _tVariant *
+ I1, //int8_t
+ UI1, //uint8_t
+ UI2, //uint16_t
+ UI4, //uint32_t
+ I8, //int64_t
+ UI8, //uint64_t
+ INT, //int Depends on architecture
+ UINT, //unsigned int Depends on architecture
+ HRESULT, //long hRes
+ PWSTR, //struct wstr
+ BLOB, //means in struct str binary data contain
+ CLSID, //UUID
+
+ UNDEFINED = 0xFFFF,
+}
+
+#[repr(C)]
+#[allow(dead_code)]
+#[derive(Clone, Copy)]
+struct DataStr {
+ pub ptr: *mut u16,
+ pub len: u32,
+}
+
+#[repr(C)]
+#[allow(dead_code)]
+#[derive(Clone, Copy)]
+struct DataBlob {
+ pub ptr: *mut u8,
+ pub len: u32,
+}
+
+#[repr(C)]
+union VariantValue {
+ pub bool: bool,
+ pub i32: i32,
+ pub f64: f64,
+ pub tm: Tm,
+ pub data_str: DataStr,
+ pub data_blob: DataBlob,
+}
+
+#[repr(C)]
+#[allow(dead_code)]
+struct TVariant {
+ value: VariantValue,
+ elements: u32, //Dimension for an one-dimensional array in pvarVal
+ vt: VariantType,
+}
+
+pub trait Addin {
+ fn init(&mut self, interface: &'static Connection) -> bool;
+ fn get_info(&mut self) -> u16;
+ fn done(&mut self);
+ fn register_extension_as(&mut self) -> &'static [u16];
+ fn get_n_props(&mut self) -> usize;
+ fn find_prop(&mut self, name: &[u16]) -> Option;
+ fn get_prop_name(&mut self, num: usize, alias: usize) -> Option<&'static [u16]>;
+ fn get_prop_val(&mut self, num: usize, val: ReturnValue) -> bool;
+ fn set_prop_val(&mut self, num: usize, val: &ParamValue) -> bool;
+ fn is_prop_readable(&mut self, num: usize) -> bool;
+ fn is_prop_writable(&mut self, num: usize) -> bool;
+ fn get_n_methods(&mut self) -> usize;
+ fn find_method(&mut self, name: &[u16]) -> Option;
+ fn get_method_name(&mut self, num: usize, alias: usize) -> Option<&'static [u16]>;
+ fn get_n_params(&mut self, num: usize) -> usize;
+ fn get_param_def_value(
+ &mut self,
+ method_num: usize,
+ param_num: usize,
+ value: ReturnValue,
+ ) -> bool;
+ fn has_ret_val(&mut self, method_num: usize) -> bool;
+ fn call_as_proc(&mut self, method_num: usize, params: &[ParamValue]) -> bool;
+ fn call_as_func(&mut self, method_num: usize, params: &[ParamValue], val: ReturnValue) -> bool;
+ fn set_locale(&mut self, loc: &[u16]);
+ fn set_user_interface_language_code(&mut self, lang: &[u16]);
+}
+
+#[repr(C)]
+//#[allow(dead_code)]
+struct InitDoneBaseVTable {
+ dtor: usize,
+ #[cfg(target_family = "unix")]
+ dtor2: usize,
+ init: unsafe extern "system" fn(&mut InitDoneBase, &'static Connection) -> bool,
+ set_mem_manager:
+ unsafe extern "system" fn(&mut InitDoneBase, &'static MemoryManager) -> bool,
+ get_info: unsafe extern "system" fn(&mut InitDoneBase) -> c_long,
+ done: unsafe extern "system" fn(&mut InitDoneBase),
+}
+
+unsafe extern "system" fn init(
+ component: &mut InitDoneBase,
+ interface: &'static Connection,
+) -> bool {
+ component.addin.init(interface)
+}
+
+unsafe extern "system" fn set_mem_manager(
+ component: &mut InitDoneBase,
+ mem: &'static MemoryManager,
+) -> bool {
+ component.memory = Some(mem);
+ true
+}
+
+unsafe extern "system" fn get_info(component: &mut InitDoneBase) -> c_long {
+ component.addin.get_info() as c_long
+}
+
+unsafe extern "system" fn done(component: &mut InitDoneBase) {
+ component.addin.done()
+}
+
+#[repr(C)]
+#[allow(dead_code)]
+struct LanguageExtenderBaseVTable {
+ dtor: usize,
+ #[cfg(target_family = "unix")]
+ dtor2: usize,
+ register_extension_as:
+ unsafe extern "system" fn(&mut LanguageExtenderBase, *mut *mut u16) -> bool,
+ get_n_props: unsafe extern "system" fn(&mut LanguageExtenderBase) -> c_long,
+ find_prop: unsafe extern "system" fn(&mut LanguageExtenderBase, *const u16) -> c_long,
+ get_prop_name:
+ unsafe extern "system" fn(&mut LanguageExtenderBase, c_long, c_long) -> *const u16,
+ get_prop_val: for<'a> unsafe extern "system" fn(
+ &mut LanguageExtenderBase,
+ c_long,
+ &'a mut TVariant,
+ ) -> bool,
+ set_prop_val:
+ unsafe extern "system" fn(&mut LanguageExtenderBase, c_long, &TVariant) -> bool,
+ is_prop_readable: unsafe extern "system" fn(&mut LanguageExtenderBase, c_long) -> bool,
+ is_prop_writable: unsafe extern "system" fn(&mut LanguageExtenderBase, c_long) -> bool,
+ get_n_methods: unsafe extern "system" fn(&mut LanguageExtenderBase) -> c_long,
+ find_method: unsafe extern "system" fn(&mut LanguageExtenderBase, *const u16) -> c_long,
+ get_method_name:
+ unsafe extern "system" fn(&mut LanguageExtenderBase, c_long, c_long) -> *const u16,
+ get_n_params: unsafe extern "system" fn(&mut LanguageExtenderBase, c_long) -> c_long,
+ get_param_def_value: unsafe extern "system" fn(
+ &mut LanguageExtenderBase,
+ c_long,
+ c_long,
+ &mut TVariant,
+ ) -> bool,
+ has_ret_val: unsafe extern "system" fn(&mut LanguageExtenderBase, c_long) -> bool,
+ call_as_proc: unsafe extern "system" fn(
+ &mut LanguageExtenderBase,
+ c_long,
+ *const TVariant,
+ c_long,
+ ) -> bool,
+ call_as_func: for<'a> unsafe extern "system" fn(
+ &mut LanguageExtenderBase,
+ c_long,
+ &mut TVariant,
+ *const TVariant,
+ c_long,
+ ) -> bool,
+}
+
+unsafe extern "system" fn register_extension_as(
+ component: &mut LanguageExtenderBase,
+ name: *mut *mut u16,
+) -> bool {
+ let Some(allocator) = component.memory else {
+ return false;
+ };
+
+ let extension_name = component.addin.register_extension_as();
+
+ let Some(data) = allocator.alloc_memory::(extension_name.len()) else {
+ return false;
+ };
+ data.copy_from_slice(extension_name);
+ unsafe { *name = data.as_mut_ptr() };
+
+ true
+}
+
+unsafe extern "system" fn get_n_props(component: &mut LanguageExtenderBase) -> c_long {
+ component.addin.get_n_props() as c_long
+}
+
+unsafe extern "system" fn find_prop(
+ component: &mut LanguageExtenderBase,
+ name: *const u16,
+) -> c_long {
+ let len = strlen(name);
+ let name = from_raw_parts(name, len);
+ match component.addin.find_prop(name) {
+ Some(i) => i as c_long,
+ None => -1,
+ }
+}
+
+unsafe extern "system" fn get_prop_name(
+ component: &mut LanguageExtenderBase,
+ num: c_long,
+ alias: c_long,
+) -> *const u16 {
+ let Some(allocator) = component.memory else {
+ return ptr::null();
+ };
+ let Some(prop_name) = component.addin.get_prop_name(num as usize, alias as usize) else {
+ return ptr::null();
+ };
+ let Some(name) = allocator.alloc_memory::(prop_name.len()) else {
+ return ptr::null();
+ };
+
+ name.copy_from_slice(prop_name);
+ name.as_ptr()
+}
+
+unsafe extern "system" fn get_prop_val<'a, T: Addin>(
+ component: &mut LanguageExtenderBase,
+ num: c_long,
+ val: &'a mut TVariant,
+) -> bool {
+ let Some(mem) = component.memory else {
+ return false;
+ };
+
+ let return_value = ReturnValue { mem, variant: val };
+ component.addin.get_prop_val(num as usize, return_value)
+}
+
+unsafe extern "system" fn set_prop_val(
+ component: &mut LanguageExtenderBase,
+ num: c_long,
+ val: &TVariant,
+) -> bool {
+ let param = ParamValue::from(val);
+ component.addin.set_prop_val(num as usize, ¶m)
+}
+
+unsafe extern "system" fn is_prop_readable(
+ component: &mut LanguageExtenderBase,
+ num: c_long,
+) -> bool {
+ component.addin.is_prop_readable(num as usize)
+}
+
+unsafe extern "system" fn is_prop_writable(
+ component: &mut LanguageExtenderBase,
+ num: c_long,
+) -> bool {
+ component.addin.is_prop_writable(num as usize)
+}
+
+unsafe extern "system" fn get_n_methods(
+ component: &mut LanguageExtenderBase,
+) -> c_long {
+ component.addin.get_n_methods() as c_long
+}
+
+unsafe extern "system" fn find_method(
+ component: &mut LanguageExtenderBase,
+ name: *const u16,
+) -> c_long {
+ let len = strlen(name);
+ let name = from_raw_parts(name, len);
+ match component.addin.find_method(name) {
+ Some(i) => i as c_long,
+ None => -1,
+ }
+}
+
+unsafe extern "system" fn get_method_name(
+ component: &mut LanguageExtenderBase,
+ num: c_long,
+ alias: c_long,
+) -> *const u16 {
+ let Some(allocator) = component.memory else {
+ return ptr::null();
+ };
+ let Some(method_name) = component.addin.get_method_name(num as usize, alias as usize) else {
+ return ptr::null();
+ };
+ let Some(name) = allocator.alloc_memory::(method_name.len()) else {
+ return ptr::null();
+ };
+
+ name.copy_from_slice(method_name);
+ name.as_ptr()
+}
+
+unsafe extern "system" fn get_n_params(
+ component: &mut LanguageExtenderBase,
+ num: c_long,
+) -> c_long {
+ component.addin.get_n_params(num as usize) as c_long
+}
+
+unsafe extern "system" fn get_param_def_value(
+ component: &mut LanguageExtenderBase,
+ method_num: c_long,
+ param_num: c_long,
+ val: &mut TVariant,
+) -> bool {
+ let Some(mem) = component.memory else {
+ return false;
+ };
+
+ let return_value = ReturnValue { mem, variant: val };
+
+ component
+ .addin
+ .get_param_def_value(method_num as usize, param_num as usize, return_value)
+}
+
+unsafe extern "system" fn has_ret_val(
+ component: &mut LanguageExtenderBase,
+ method_num: c_long,
+) -> bool {
+ component.addin.has_ret_val(method_num as usize)
+}
+
+unsafe extern "system" fn call_as_proc(
+ component: &mut LanguageExtenderBase,
+ method_num: c_long,
+ params: *const TVariant,
+ size_array: c_long,
+) -> bool {
+ let param_values = from_raw_parts(params, size_array as usize)
+ .iter()
+ .map(|x| ParamValue::from(x))
+ .collect::>();
+
+ component
+ .addin
+ .call_as_proc(method_num as usize, param_values.as_slice())
+}
+
+unsafe extern "system" fn call_as_func<'a, T: Addin>(
+ component: &mut LanguageExtenderBase,
+ method_num: c_long,
+ ret_value: &'a mut TVariant,
+ params: *const TVariant,
+ size_array: c_long,
+) -> bool {
+ let Some(mem) = component.memory else {
+ return false;
+ };
+
+ let return_value = ReturnValue {
+ mem,
+ variant: ret_value,
+ };
+
+ let param_values = from_raw_parts(params, size_array as usize)
+ .iter()
+ .map(|x| ParamValue::from(x))
+ .collect::>();
+
+ component
+ .addin
+ .call_as_func(method_num as usize, param_values.as_slice(), return_value)
+}
+
+#[repr(C)]
+#[allow(dead_code)]
+struct LocaleBaseVTable {
+ dtor: usize,
+ #[cfg(target_family = "unix")]
+ dtor2: usize,
+ set_locale: unsafe extern "system" fn(&mut LocaleBase, *const u16),
+}
+
+unsafe extern "system" fn set_locale(component: &mut LocaleBase, loc: *const u16) {
+ let len = strlen(loc);
+ let loc = from_raw_parts(loc, len);
+ component.addin.set_locale(loc)
+}
+
+#[repr(C)]
+#[allow(dead_code)]
+struct UserLanguageBaseVTable {
+ dtor: usize,
+ #[cfg(target_family = "unix")]
+ dtor2: usize,
+ set_user_interface_language_code:
+ unsafe extern "system" fn(&mut UserLanguageBase, *const u16),
+}
+
+unsafe extern "system" fn set_user_interface_language_code(
+ component: &mut UserLanguageBase,
+ lang: *const u16,
+) {
+ let len = strlen(lang);
+ let lang = from_raw_parts(lang, len);
+ component.addin.set_user_interface_language_code(lang)
+}
+
+#[repr(C)]
+#[allow(dead_code)]
+struct ComponentBase {
+ vptr1: Box>,
+ vptr2: Box>,
+ vptr3: Box>,
+ vptr4: Box>,
+ destroy: unsafe extern "system" fn(*mut *mut ComponentBase),
+ memory: Option<&'static MemoryManager>,
+ addin: T,
+}
+
+unsafe extern "system" fn destroy(component: *mut *mut ComponentBase) {
+ let component = unsafe { Box::from_raw(*component) };
+ drop(component);
+}
+
+#[repr(C)]
+#[allow(dead_code)]
+struct InitDoneBase {
+ _vptr1: Box>,
+ _vptr2: Box>,
+ _vptr3: Box>,
+ _vptr4: Box>,
+ destroy: unsafe extern "system" fn(*mut *mut ComponentBase),
+ memory: Option<&'static MemoryManager>,
+ addin: T,
+}
+
+// type InitDoneBase = ComponentBase;
+
+#[repr(C)]
+#[allow(dead_code)]
+struct LanguageExtenderBase {
+ _vptr2: Box>,
+ _vptr3: Box>,
+ _vptr4: Box>,
+ destroy: unsafe extern "system" fn(*mut *mut ComponentBase),
+ memory: Option<&'static MemoryManager>,
+ addin: T,
+}
+
+#[repr(C)]
+#[allow(dead_code)]
+struct LocaleBase {
+ _vptr3: Box>,
+ _vptr4: Box>,
+ destroy: unsafe extern "system" fn(*mut *mut ComponentBase),
+ memory: Option<&'static MemoryManager>,
+ addin: T,
+}
+
+#[repr(C)]
+#[allow(dead_code)]
+struct UserLanguageBase {
+ _vptr4: Box>,
+ destroy: unsafe extern "system" fn(*mut *mut ComponentBase),
+ memory: Option<&'static MemoryManager>,
+ addin: T,
+}
+
+pub fn create_component(addin: T) -> *mut c_void {
+ let vptr1 = Box::new(InitDoneBaseVTable {
+ dtor: 0,
+ #[cfg(target_family = "unix")]
+ dtor2: 0,
+ init,
+ set_mem_manager,
+ get_info,
+ done,
+ });
+
+ let vptr2 = Box::new(LanguageExtenderBaseVTable {
+ dtor: 0,
+ #[cfg(target_family = "unix")]
+ dtor2: 0,
+ register_extension_as,
+ get_n_props,
+ find_prop,
+ get_prop_name,
+ get_prop_val,
+ set_prop_val,
+ is_prop_readable,
+ is_prop_writable,
+ get_n_methods,
+ find_method,
+ get_method_name,
+ get_n_params,
+ get_param_def_value,
+ has_ret_val,
+ call_as_proc,
+ call_as_func,
+ });
+
+ let vptr3 = Box::new(LocaleBaseVTable {
+ dtor: 0,
+ #[cfg(target_family = "unix")]
+ dtor2: 0,
+ set_locale,
+ });
+
+ let vptr4 = Box::new(UserLanguageBaseVTable {
+ dtor: 0,
+ #[cfg(target_family = "unix")]
+ dtor2: 0,
+ set_user_interface_language_code,
+ });
+
+ let c = Box::new(ComponentBase {
+ vptr1,
+ vptr2,
+ vptr3,
+ vptr4,
+ destroy: destroy::,
+ memory: None,
+ addin,
+ });
+
+ let p = Box::leak(c);
+ p as *mut ComponentBase as *mut c_void
+}
+
+pub fn destroy_component(component: *mut *mut c_void) {
+ #[repr(C)]
+ #[allow(dead_code)]
+ struct ComponentWrapper {
+ vptr1: Box,
+ vptr2: Box,
+ vptr3: Box,
+ vptr4: Box,
+ destroy: unsafe extern "system" fn(*mut *mut c_void),
+ }
+
+ unsafe {
+ let wrapper = *component as *mut ComponentWrapper;
+ let wrapper = &mut *wrapper;
+ (wrapper.destroy)(component);
+ }
+}
+
+#[repr(C)]
+#[allow(dead_code)]
+struct MemoryManagerVTable {
+ dtor: usize,
+ #[cfg(target_family = "unix")]
+ dtor2: usize,
+ alloc_memory: unsafe extern "system" fn(&MemoryManager, *mut *mut c_void, c_ulong) -> bool,
+ free_memory: unsafe extern "system" fn(&MemoryManager, *mut *mut c_void),
+}
+
+#[repr(C)]
+#[allow(dead_code)]
+struct MemoryManager {
+ vptr1: &'static MemoryManagerVTable,
+}
+
+impl MemoryManager {
+ pub fn alloc_memory<'a, T>(&self, size: usize) -> Option<&'a mut [T]> {
+ let mut data = ptr::null_mut::();
+ unsafe {
+ if (self.vptr1.alloc_memory)(self, &mut data, (size * size_of::()) as c_ulong) {
+ let d = from_raw_parts_mut(data as *mut T, size);
+ Some(d)
+ } else {
+ None
+ }
+ }
+ }
+}
+
+#[repr(C)]
+#[allow(dead_code)]
+struct ConnectionVTable {
+ dtor: usize,
+ #[cfg(target_family = "unix")]
+ dtor2: usize,
+}
+
+#[repr(C)]
+#[allow(dead_code)]
+pub struct Connection {
+ vptr1: &'static ConnectionVTable,
+}
+
+fn strlen(s: *const u16) -> usize {
+ let mut i = 0;
+ while unsafe { *s.add(i) } != 0 {
+ i += 1;
+ }
+ i += 1;
+ i
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..8b8c4d3
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,53 @@
+mod addin1;
+mod ffi;
+
+use std::{
+ ffi::{c_long, c_void, c_int},
+ sync::atomic::{AtomicI32, Ordering},
+};
+
+use addin1::Addin1;
+use ffi::{destroy_component, AttachType};
+use utf16_lit::utf16_null;
+
+use crate::ffi::create_component;
+
+pub static mut PLATFORM_CAPABILITIES: AtomicI32 = AtomicI32::new(-1);
+
+#[allow(non_snake_case)]
+#[no_mangle]
+pub extern "C" fn GetClassObject(_name: *const u16, component: *mut *mut c_void) -> c_long {
+ let addin = Addin1::new();
+ unsafe {
+ *component = create_component(addin);
+ }
+ 1
+}
+
+#[allow(non_snake_case)]
+#[no_mangle]
+pub extern "C" fn DestroyObject(component: *mut *mut c_void) -> c_long {
+ destroy_component(component);
+ 0
+}
+
+#[allow(non_snake_case)]
+#[no_mangle]
+pub extern "C" fn GetClassNames() -> *const u16 {
+ utf16_null!("Class1").as_ptr()
+}
+
+#[allow(non_snake_case)]
+#[no_mangle]
+pub extern "C" fn SetPlatformCapabilities(capabilities: c_int) -> c_long {
+ unsafe {
+ PLATFORM_CAPABILITIES.store(capabilities, Ordering::Relaxed);
+ }
+ 3
+}
+
+#[allow(non_snake_case)]
+#[no_mangle]
+pub extern "C" fn GetAttachType() -> AttachType {
+ AttachType::CanAttachAny
+}