Разработка внешних компонент (ВК) для мобильной платформы 1С — задача повышенной сложности, так как она требует одновременного владения стеком технологий C++ (NDK), Java (Android SDK) и понимания внутренних механизмов взаимодействия 1С с операционной системой. Часто разработчики сталкиваются с тем, что компонента "вроде бы работает", но периодически вызывает аварийное завершение приложения (Crash) с ошибкой SIGSEGV. В этой статье мы подробно проанализируем причины таких падений на примере реального кейса и разберем способы их устранения.
Ошибка signal 11 (SIGSEGV) означает нарушение сегментации. Проще говоря, программа попыталась обратиться к участку памяти, который ей не принадлежит или уже был освобожден. В контексте Android и 1С это чаще всего происходит в трех случаях:
Разберем каждую из этих причин на основе кода из обсуждения.
Рассмотрим внимательно функцию конвертации wsz2jstr, которую использовал автор темы. На первый взгляд она кажется логичной, но именно здесь кроется "бомба замедленного действия":
static jstring wsz2jstr(std::wstring cstr) {
JNIEnv* env = getJniEnv();
Addin1C::platformString str = Addin1C::convertStringToPlatform(cstr);
int len = str.size();
jchar* raw = new jchar[len];
memcpy(raw, str.c_str(), len * sizeof(wchar_t)); // ОШИБКА ЗДЕСЬ!
jstring result = env->NewString(raw, len);
delete[] raw;
return result;
}
Разберем по шагам, что здесь происходит. В операционной системе Windows тип wchar_t обычно занимает 2 байта (UTF-16). Однако в Android (Linux) тип wchar_t занимает 4 байта (UTF-32). При этом тип jchar в JNI всегда строго равен 2 байтам.
Когда выполняется инструкция memcpy(raw, str.c_str(), len * sizeof(wchar_t)), происходит следующее: мы выделяем массив raw размером len * 2 байт, но пытаемся скопировать в него данные объемом len * 4 байт. Это приводит к классическому heap buffer overflow (переполнению кучи). Мы затираем память, находящуюся за пределами массива raw. В этой памяти могут лежать системные указатели 1С или структуры JNI. Приложение падает не в момент копирования, а позже, когда система пытается обратиться к поврежденным данным — например, в методе StopAPKMethod().
Как исправить: Необходимо использовать преобразование в UTF-16. Самый надежный способ для Android — использовать стандартные средства конвертации или передавать строки через UTF-8:
jstring wsz2jstr(JNIEnv* env, const std::wstring& wstr) {
// Конвертируем wstring (UTF-32) в string (UTF-8)
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
std::string utf8_str = converter.to_bytes(wstr);
return env->NewStringUTF(utf8_str.c_str());
}
В JNI существует два типа ссылок на объекты Java: локальные и глобальные. Локальные ссылки (LocalRef) автоматически освобождаются при выходе из функции Native-кода. Глобальные ссылки (GlobalRef) живут до тех пор, пока разработчик не удалит их вручную через DeleteGlobalRef.
Проанализируем метод Initialize:
jclass ccloc = helper->FindClass(...);
if (ccloc) {
cc = static_cast<jclass>(env->NewGlobalRef(ccloc));
env->DeleteLocalRef(ccloc);
// ...
jobject objloc = env->NewObject(cc, methID, ...);
obj = static_cast<jobject>(env->NewGlobalRef(objloc));
env->DeleteLocalRef(objloc);
}
Здесь автор правильно создает глобальные ссылки для cc (класс) и obj (экземпляр объекта). Однако в методе StopAPKMethod происходит удаление этих ссылок:
void StopAPKMethod(){
if (obj) {
JNIEnv* env = getJniEnv();
env->CallVoidMethod(obj, methID);
env->DeleteGlobalRef(obj);
env->DeleteGlobalRef(cc);
obj = nullptr;
cc = nullptr;
}
}
Важный нюанс: Если метод StopAPKMethod будет вызван дважды (например, один раз программно из 1С, а второй раз платформой при уничтожении объекта в методе Done), возникнет попытка удалить уже невалидный указатель, что приведет к моментальному падению. Необходимо всегда проверять указатели и обнулять их атомарно.
В коде Java-части мы видим работу с BroadcastReceiver:
public void start() {
mActivity.registerReceiver(mReceiver, new IntentFilter(mIntentAction));
}
public void stop() {
mActivity.unregisterReceiver(mReceiver);
}
Методы registerReceiver и unregisterReceiver в Android должны вызываться в том же потоке, где живет Activity (в главном UI-потоке). Внешняя компонента 1С вызывает Native-методы в своих рабочих потоках. Если 1С вызовет StopAPKMethod в момент, когда Android занят обновлением интерфейса или пересозданием Activity, возникнет конфликт.
Решение: Всегда оборачивайте работу с системными сервисами Android в runOnUiThread:
public void stop() {
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (mReceiver != null) {
mActivity.unregisterReceiver(mReceiver);
mReceiver = null;
}
}
});
}
Выясним причину, почему стандартная отладка часто не работает. Когда вы запускаете проект из Android Studio, она ожидает, что библиотека .so будет упакована внутри вашего APK. Но в случае с 1С ситуация иная: мобильная платформа сама загружает библиотеку из своих внутренних каталогов (куда она распаковала её из макета 1С).
Для эффективной отладки выполните следующие шаги:
#include <android/log.h>
#define LOG_TAG "MyComponent"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
// Использование:
LOGD("Инициализация успешно пройдена, объект: %p", obj);
Run -> Attach Debugger to Android Process. В списке процессов выберите ваше приложение 1С. Чтобы отладчик "подцепил" исходный код C++, убедитесь, что библиотека собрана с отладочными символами (в CMakeLists.txt должен быть установлен тип сборки Debug).DeleteGlobalRef. В Native-коде полезно добавить проверки:
if (env->IsSameObject(obj, NULL)) {
LOGD("Объект уже удален Garbage Collector-ом или вручную");
return;
}
Проанализировав ситуацию, мы можем составить чеклист для разработки надежной ВК под Android:
memcpy между wstring и jchar. Используйте промежуточную конвертацию в UTF-8.Init. Получайте его заново или убедитесь, что он валиден на протяжении всей работы.JNIEnv* нельзя передавать между потоками. В каждом новом потоке нужно вызывать JavaVM->AttachCurrentThread.isFinishing()).Соблюдение этих правил позволит избежать трудноуловимых багов, связанных с памятью, и сделает вашу мобильную разработку на базе 1С стабильной и предсказуемой.