1
0
mirror of https://github.com/medigor/example-native-api-rs.git synced 2024-11-21 17:56:37 +02:00
This commit is contained in:
medigor 2022-12-07 18:27:07 +03:00
parent 0d60572ee6
commit b55e50ccee
15 changed files with 1591 additions and 1 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
target
Cargo.lock
.vscode

17
Cargo.toml Normal file
View File

@ -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"

21
LICENSE Normal file
View File

@ -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.

View File

@ -1 +1,55 @@
# example-native-add-rs
# 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/веб-клиент не реализовано и планов таких нет.

11
conf1c/ConfigDumpInfo.xml Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<ConfigDumpInfo xmlns="http://v8.1c.ru/8.3/xcf/dumpinfo" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" format="Hierarchical" version="2.15">
<ConfigVersions>
<Metadata name="Configuration.Конфигурация" id="72b21170-d6cc-48ee-8426-d69353f61fd5" configVersion="a64d3ffe00e3344f821527065fd9669400000000"/>
<Metadata name="Configuration.Конфигурация.HomePageWorkArea" id="29e7f5a4-dc26-408a-bd6e-083b5bf1384c.8" configVersion="0169b7c0855a0e459f04d61506e77aef00000000"/>
<Metadata name="DataProcessor.Обработка1" id="33179d0b-730e-44f5-b20c-1af11f7f1f60" configVersion="2f45b64f61dc1b4cb42bd668cf3942df00000000"/>
<Metadata name="DataProcessor.Обработка1.Form.Форма" id="24033a67-5ef1-4265-9a88-eb7f279ce1cb" configVersion="b173f3c73e1fdd489319ecfd372edab800000000"/>
<Metadata name="DataProcessor.Обработка1.Form.Форма.Form" id="24033a67-5ef1-4265-9a88-eb7f279ce1cb.0" configVersion="7894c5dc6490f14d92e085d4d4b8847300000000"/>
<Metadata name="Language.Русский" id="e3ac9659-5250-4530-9ca6-3f4f4ef6413a" configVersion="0a61ebedb1993c4a9ed758c68a381d7d00000000"/>
</ConfigVersions>
</ConfigDumpInfo>

221
conf1c/Configuration.xml Normal file
View File

@ -0,0 +1,221 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.15">
<Configuration uuid="72b21170-d6cc-48ee-8426-d69353f61fd5">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
<xr:ObjectId>29e7f5a4-dc26-408a-bd6e-083b5bf1384c</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
<xr:ObjectId>f76706d7-a451-49f0-a3b8-8c92dc9eb401</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
<xr:ObjectId>efad9454-c037-40e6-92f8-de6f995cb912</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
<xr:ObjectId>5a509aa9-2e5a-47a8-977a-8fbe609d782c</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
<xr:ObjectId>3982e56b-1417-490c-8ac4-7672c7bffa80</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
<xr:ObjectId>862a5e5b-5378-4b7c-b567-0ab6725fc840</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
<xr:ObjectId>ed5cd24c-da69-4a84-a011-421e03c8dd71</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
<Name>Конфигурация</Name>
<Synonym/>
<Comment/>
<NamePrefix/>
<ConfigurationExtensionCompatibilityMode>Version8_3_22</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes>
<ScriptVariant>Russian</ScriptVariant>
<DefaultRoles/>
<Vendor/>
<Version/>
<UpdateCatalogAddress/>
<IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries/>
<CommonSettingsStorage/>
<ReportsUserSettingsStorage/>
<ReportsVariantsStorage/>
<FormDataSettingsStorage/>
<DynamicListsUserSettingsStorage/>
<URLExternalDataStorage/>
<Content/>
<DefaultReportForm/>
<DefaultReportVariantForm/>
<DefaultReportSettingsForm/>
<DefaultReportAppearanceTemplate/>
<DefaultDynamicListSettingsForm/>
<DefaultSearchForm/>
<DefaultDataHistoryChangeHistoryForm/>
<DefaultDataHistoryVersionDataForm/>
<DefaultDataHistoryVersionDifferencesForm/>
<DefaultCollaborationSystemUsersChoiceForm/>
<RequiredMobileApplicationPermissions/>
<UsedMobileApplicationFunctionalities>
<app:functionality>
<app:functionality>Biometrics</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Location</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundLocation</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BluetoothPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>WiFiPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Contacts</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Calendars</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PushNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>LocalNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InAppPurchases</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PersonalComputerFileExchange</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Ads</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NumberDialing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AutoSendSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ReceiveSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SMSLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Camera</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Microphone</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>MusicLibrary</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PictureAndVideoLibraries</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InstallPackages</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>OSBackup</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ApplicationUsageStatistics</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BarcodeScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioRecording</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllFilesAccess</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Videoconferences</app:functionality>
<app:use>false</app:use>
</app:functionality>
</UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles/>
<MobileApplicationURLs/>
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface/>
<DefaultStyle/>
<DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation/>
<DetailedInformation/>
<Copyright/>
<VendorInformationAddress/>
<ConfigurationInformationAddress/>
<DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode>
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
<InterfaceCompatibilityMode>Taxi</InterfaceCompatibilityMode>
<CompatibilityMode>Version8_3_22</CompatibilityMode>
<DefaultConstantsForm/>
</Properties>
<ChildObjects>
<Language>Русский</Language>
<DataProcessor>Обработка1</DataProcessor>
</ChildObjects>
</Configuration>
</MetaDataObject>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.15">
<DataProcessor uuid="33179d0b-730e-44f5-b20c-1af11f7f1f60">
<InternalInfo>
<xr:GeneratedType name="DataProcessorObject.Обработка1" category="Object">
<xr:TypeId>aa584d2a-1a1f-4807-b2e6-588aef5193b8</xr:TypeId>
<xr:ValueId>620a7e0f-3713-4585-842f-262d1dfa18c0</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="DataProcessorManager.Обработка1" category="Manager">
<xr:TypeId>e6189229-5433-466b-a435-ac6f2fa36046</xr:TypeId>
<xr:ValueId>8606e276-c11b-4eaa-af7b-424d33285d14</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>Обработка1</Name>
<Synonym/>
<Comment/>
<UseStandardCommands>true</UseStandardCommands>
<DefaultForm>DataProcessor.Обработка1.Form.Форма</DefaultForm>
<AuxiliaryForm/>
<IncludeHelpInContents>false</IncludeHelpInContents>
<ExtendedPresentation/>
<Explanation/>
</Properties>
<ChildObjects>
<Form>Форма</Form>
</ChildObjects>
</DataProcessor>
</MetaDataObject>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.15">
<Form uuid="24033a67-5ef1-4265-9a88-eb7f279ce1cb">
<Properties>
<Name>Форма</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Форма</v8:content>
</v8:item>
</Synonym>
<Comment/>
<FormType>Managed</FormType>
<IncludeHelpInContents>false</IncludeHelpInContents>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
</UsePurposes>
<ExtendedPresentation/>
</Properties>
</Form>
</MetaDataObject>

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcssch="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.15">
<AutoSaveDataInSettings>Use</AutoSaveDataInSettings>
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1"/>
<ChildItems>
<InputField name="ИмяФайла" id="19">
<DataPath>ИмяФайла</DataPath>
<ContextMenu name="ИмяФайлаКонтекстноеМеню" id="20"/>
<ExtendedTooltip name="ИмяФайлаРасширеннаяПодсказка" id="21"/>
<Events>
<Event name="StartChoice">ИмяФайлаНачалоВыбора</Event>
</Events>
</InputField>
<Button name="Тест1" id="22">
<Type>UsualButton</Type>
<CommandName>Form.Command.Тест1</CommandName>
<ExtendedTooltip name="Тест1РасширеннаяПодсказка" id="23"/>
</Button>
<Button name="Тест2" id="3">
<Type>UsualButton</Type>
<CommandName>Form.Command.Тест2</CommandName>
<ExtendedTooltip name="Тест2РасширеннаяПодсказка" id="4"/>
</Button>
</ChildItems>
<Attributes>
<Attribute name="Объект" id="1">
<Type>
<v8:Type>cfg:DataProcessorObject.Обработка1</v8:Type>
</Type>
<MainAttribute>true</MainAttribute>
</Attribute>
<Attribute name="ИмяФайла" id="2">
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Имя файла</v8:content>
</v8:item>
</Title>
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>0</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</Type>
<Save>
<Field>ИмяФайла</Field>
</Save>
</Attribute>
</Attributes>
<Commands>
<Command name="Тест2" id="2">
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Тест2</v8:content>
</v8:item>
</Title>
<ToolTip>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Тест2</v8:content>
</v8:item>
</ToolTip>
<Action>Тест2</Action>
</Command>
<Command name="Тест1" id="1">
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Тест1</v8:content>
</v8:item>
</Title>
<ToolTip>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Тест1</v8:content>
</v8:item>
</ToolTip>
<Action>Тест1</Action>
</Command>
</Commands>
</Form>

View File

@ -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", Конец - Начало));
КонецПроцедуры

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<HomePageWorkArea xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.15">
<WorkingAreaTemplate>TwoColumnsEqualWidth</WorkingAreaTemplate>
<LeftColumn>
<Item>
<Form>DataProcessor.Обработка1.Form.Форма</Form>
<Height>10</Height>
<Visibility>
<xr:Common>true</xr:Common>
</Visibility>
</Item>
</LeftColumn>
<RightColumn/>
</HomePageWorkArea>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.15">
<Language uuid="e3ac9659-5250-4530-9ca6-3f4f4ef6413a">
<Properties>
<Name>Русский</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Русский</v8:content>
</v8:item>
</Synonym>
<Comment/>
<LanguageCode>ru</LanguageCode>
</Properties>
</Language>
</MetaDataObject>

208
src/addin1.rs Normal file
View File

@ -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<u8>,
}
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<usize> {
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<u16> = 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<usize> {
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::<u16>::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]) {}
}

753
src/ffi.rs Normal file
View File

@ -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::<u16>(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::<u8>(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::<u16>(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::<u8>(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<usize>;
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<usize>;
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<T: Addin> {
dtor: usize,
#[cfg(target_family = "unix")]
dtor2: usize,
init: unsafe extern "system" fn(&mut InitDoneBase<T>, &'static Connection) -> bool,
set_mem_manager:
unsafe extern "system" fn(&mut InitDoneBase<T>, &'static MemoryManager) -> bool,
get_info: unsafe extern "system" fn(&mut InitDoneBase<T>) -> c_long,
done: unsafe extern "system" fn(&mut InitDoneBase<T>),
}
unsafe extern "system" fn init<T: Addin>(
component: &mut InitDoneBase<T>,
interface: &'static Connection,
) -> bool {
component.addin.init(interface)
}
unsafe extern "system" fn set_mem_manager<T: Addin>(
component: &mut InitDoneBase<T>,
mem: &'static MemoryManager,
) -> bool {
component.memory = Some(mem);
true
}
unsafe extern "system" fn get_info<T: Addin>(component: &mut InitDoneBase<T>) -> c_long {
component.addin.get_info() as c_long
}
unsafe extern "system" fn done<T: Addin>(component: &mut InitDoneBase<T>) {
component.addin.done()
}
#[repr(C)]
#[allow(dead_code)]
struct LanguageExtenderBaseVTable<T: Addin> {
dtor: usize,
#[cfg(target_family = "unix")]
dtor2: usize,
register_extension_as:
unsafe extern "system" fn(&mut LanguageExtenderBase<T>, *mut *mut u16) -> bool,
get_n_props: unsafe extern "system" fn(&mut LanguageExtenderBase<T>) -> c_long,
find_prop: unsafe extern "system" fn(&mut LanguageExtenderBase<T>, *const u16) -> c_long,
get_prop_name:
unsafe extern "system" fn(&mut LanguageExtenderBase<T>, c_long, c_long) -> *const u16,
get_prop_val: for<'a> unsafe extern "system" fn(
&mut LanguageExtenderBase<T>,
c_long,
&'a mut TVariant,
) -> bool,
set_prop_val:
unsafe extern "system" fn(&mut LanguageExtenderBase<T>, c_long, &TVariant) -> bool,
is_prop_readable: unsafe extern "system" fn(&mut LanguageExtenderBase<T>, c_long) -> bool,
is_prop_writable: unsafe extern "system" fn(&mut LanguageExtenderBase<T>, c_long) -> bool,
get_n_methods: unsafe extern "system" fn(&mut LanguageExtenderBase<T>) -> c_long,
find_method: unsafe extern "system" fn(&mut LanguageExtenderBase<T>, *const u16) -> c_long,
get_method_name:
unsafe extern "system" fn(&mut LanguageExtenderBase<T>, c_long, c_long) -> *const u16,
get_n_params: unsafe extern "system" fn(&mut LanguageExtenderBase<T>, c_long) -> c_long,
get_param_def_value: unsafe extern "system" fn(
&mut LanguageExtenderBase<T>,
c_long,
c_long,
&mut TVariant,
) -> bool,
has_ret_val: unsafe extern "system" fn(&mut LanguageExtenderBase<T>, c_long) -> bool,
call_as_proc: unsafe extern "system" fn(
&mut LanguageExtenderBase<T>,
c_long,
*const TVariant,
c_long,
) -> bool,
call_as_func: for<'a> unsafe extern "system" fn(
&mut LanguageExtenderBase<T>,
c_long,
&mut TVariant,
*const TVariant,
c_long,
) -> bool,
}
unsafe extern "system" fn register_extension_as<T: Addin>(
component: &mut LanguageExtenderBase<T>,
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::<u16>(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<T: Addin>(component: &mut LanguageExtenderBase<T>) -> c_long {
component.addin.get_n_props() as c_long
}
unsafe extern "system" fn find_prop<T: Addin>(
component: &mut LanguageExtenderBase<T>,
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<T: Addin>(
component: &mut LanguageExtenderBase<T>,
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::<u16>(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<T>,
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<T: Addin>(
component: &mut LanguageExtenderBase<T>,
num: c_long,
val: &TVariant,
) -> bool {
let param = ParamValue::from(val);
component.addin.set_prop_val(num as usize, &param)
}
unsafe extern "system" fn is_prop_readable<T: Addin>(
component: &mut LanguageExtenderBase<T>,
num: c_long,
) -> bool {
component.addin.is_prop_readable(num as usize)
}
unsafe extern "system" fn is_prop_writable<T: Addin>(
component: &mut LanguageExtenderBase<T>,
num: c_long,
) -> bool {
component.addin.is_prop_writable(num as usize)
}
unsafe extern "system" fn get_n_methods<T: Addin>(
component: &mut LanguageExtenderBase<T>,
) -> c_long {
component.addin.get_n_methods() as c_long
}
unsafe extern "system" fn find_method<T: Addin>(
component: &mut LanguageExtenderBase<T>,
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<T: Addin>(
component: &mut LanguageExtenderBase<T>,
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::<u16>(method_name.len()) else {
return ptr::null();
};
name.copy_from_slice(method_name);
name.as_ptr()
}
unsafe extern "system" fn get_n_params<T: Addin>(
component: &mut LanguageExtenderBase<T>,
num: c_long,
) -> c_long {
component.addin.get_n_params(num as usize) as c_long
}
unsafe extern "system" fn get_param_def_value<T: Addin>(
component: &mut LanguageExtenderBase<T>,
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<T: Addin>(
component: &mut LanguageExtenderBase<T>,
method_num: c_long,
) -> bool {
component.addin.has_ret_val(method_num as usize)
}
unsafe extern "system" fn call_as_proc<T: Addin>(
component: &mut LanguageExtenderBase<T>,
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::<Vec<ParamValue>>();
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<T>,
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::<Vec<ParamValue>>();
component
.addin
.call_as_func(method_num as usize, param_values.as_slice(), return_value)
}
#[repr(C)]
#[allow(dead_code)]
struct LocaleBaseVTable<T: Addin> {
dtor: usize,
#[cfg(target_family = "unix")]
dtor2: usize,
set_locale: unsafe extern "system" fn(&mut LocaleBase<T>, *const u16),
}
unsafe extern "system" fn set_locale<T: Addin>(component: &mut LocaleBase<T>, 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<T: Addin> {
dtor: usize,
#[cfg(target_family = "unix")]
dtor2: usize,
set_user_interface_language_code:
unsafe extern "system" fn(&mut UserLanguageBase<T>, *const u16),
}
unsafe extern "system" fn set_user_interface_language_code<T: Addin>(
component: &mut UserLanguageBase<T>,
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<T: Addin> {
vptr1: Box<InitDoneBaseVTable<T>>,
vptr2: Box<LanguageExtenderBaseVTable<T>>,
vptr3: Box<LocaleBaseVTable<T>>,
vptr4: Box<UserLanguageBaseVTable<T>>,
destroy: unsafe extern "system" fn(*mut *mut ComponentBase<T>),
memory: Option<&'static MemoryManager>,
addin: T,
}
unsafe extern "system" fn destroy<T: Addin>(component: *mut *mut ComponentBase<T>) {
let component = unsafe { Box::from_raw(*component) };
drop(component);
}
#[repr(C)]
#[allow(dead_code)]
struct InitDoneBase<T: Addin> {
_vptr1: Box<InitDoneBaseVTable<T>>,
_vptr2: Box<LanguageExtenderBaseVTable<T>>,
_vptr3: Box<LocaleBaseVTable<T>>,
_vptr4: Box<UserLanguageBaseVTable<T>>,
destroy: unsafe extern "system" fn(*mut *mut ComponentBase<T>),
memory: Option<&'static MemoryManager>,
addin: T,
}
// type InitDoneBase<T> = ComponentBase<T>;
#[repr(C)]
#[allow(dead_code)]
struct LanguageExtenderBase<T: Addin> {
_vptr2: Box<LanguageExtenderBaseVTable<T>>,
_vptr3: Box<LocaleBaseVTable<T>>,
_vptr4: Box<UserLanguageBaseVTable<T>>,
destroy: unsafe extern "system" fn(*mut *mut ComponentBase<T>),
memory: Option<&'static MemoryManager>,
addin: T,
}
#[repr(C)]
#[allow(dead_code)]
struct LocaleBase<T: Addin> {
_vptr3: Box<LocaleBaseVTable<T>>,
_vptr4: Box<UserLanguageBaseVTable<T>>,
destroy: unsafe extern "system" fn(*mut *mut ComponentBase<T>),
memory: Option<&'static MemoryManager>,
addin: T,
}
#[repr(C)]
#[allow(dead_code)]
struct UserLanguageBase<T: Addin> {
_vptr4: Box<UserLanguageBaseVTable<T>>,
destroy: unsafe extern "system" fn(*mut *mut ComponentBase<T>),
memory: Option<&'static MemoryManager>,
addin: T,
}
pub fn create_component<T: Addin>(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::<T>,
memory: None,
addin,
});
let p = Box::leak(c);
p as *mut ComponentBase<T> as *mut c_void
}
pub fn destroy_component(component: *mut *mut c_void) {
#[repr(C)]
#[allow(dead_code)]
struct ComponentWrapper {
vptr1: Box<c_void>,
vptr2: Box<c_void>,
vptr3: Box<c_void>,
vptr4: Box<c_void>,
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::<c_void>();
unsafe {
if (self.vptr1.alloc_memory)(self, &mut data, (size * size_of::<T>()) 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
}

53
src/lib.rs Normal file
View File

@ -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
}