Приветсвую! Много воды
утекло с момента последнего сообщения,
но тем не менее на то было основание.
Моя попытка с ходу портировать каденсер
провалилась ввиду незнания особенностей
работы JNI и Lazarus Widgetseta в целом.
Итак прежде чем рассказать
суть решения я ввиду некоторый экскурс
по виджетсету jni и связи их с явой.
Первое что мне хочется
сказать и поддчеркнуть это то что при
програмировании под андроид вы
программируете в лазаре «НЕ НАТИВНО».
Это значит что результатом компиляции
будет не exe исполняймый файл а получится
.so библиотека. А так как это будет именно
библиотека то для работы с javaой
разработчики лазаруса применили механизм
экспорта методов
exports
Java_com_pascal_lclproject_LCLActivity_LCLOnTouch name
'Java_com_pascal_lcltest_LCLActivity_LCLOnTouch',
для того что бы из под
ява влиять на работу лазаруса и в яве
публикуя методы и переменные для того
что бы лазарус мог ими управлять
посредством JNI.
Причем ключевым моментом
здесь является то что при запуске
приложения .so библиотека загружается
сразу и в ней вызывается секция
«initialization» которая по моменту времени
является преждевеременной, да, в
класическом нативном понимании при
старте приложения можно в нее пихать
все что хочется но в Андроиде это не
допустимо. Недопустимо потому что
создание форм и других компонентов
происходит по времени значительно позже
и привязвается оно к событие Activity
OnStart.
Еще одной особенностью
является то что все указатели в нативном
коде являются локальными и могут жить
некоторый пероид времени. Собственно
что и произошло при первых попытках
принять указатель контекста, и при
удалении он оказался не верным, решить
проблему удалось закэшировав указатель,
тобишь сделав его глобальным при помощи
функции NewGlobalRef.
Итак с ключевыми
особенностями покончено приступим к
разбору особенностей виджетсета.
При загрузке библиотеки
происходит вызов метода JNI_OnLoad в котором
этому методу передается виртуальная
машина java, указатель которой по большей
части статичен в любой момент времени
и в любом потоке.
Для работы с ява методоми
и полями требуется структура EnvRef в
которой будет доступны различные функции
для обращения к полям явы, ее можно
получить из той же виртуальной машины
полученной при загрузке OnLoad.
И для обращения к какой
нить функции в яве нужно проделать
небольшую работу вот небольшой пример
javaMethod_LCLeglGetError :=
fjavaEnvRef^^.GetMethodID(fjavaEnvRef, javaActivityClass,
'LCLeglGetError', '()I');
result :=
EGLint(javaEnvRef^^.CallIntMethod(javaEnvRef, javaActivityObject,
javaMethod_LCLeglGetError));
В первой строке мы
получаем ID к полю которую хотим вызвать,
во второй строке происходит вызов.
В случае если вызов
подразумевается производить в различных
участках кода и модулей, разумнее всего
хранить ID в какой нить переменной.
Однако такой подходе
с большим количеством переменных
приводит в тупик когда дело доходит до
многопоточности.
Вся фишка в том что для
своего потока EnvRef уникален, и все вызовы
CallIntMethod с ID переменными GetMethodID должны
производится своим EnvRef. А так как
указатели к ID для другого потока
изменились то нужно применить механизм
с переменными которые будут уникальны
для любого потока.
Для получения структуры
EnvRef для конкретного потока необходимо
этот поток зарегестрировать в виртуальной
машине методом
javaVMRef^^.AttachCurrentThread(javaVMRef,@fjavaEnvRef,nil) ;
ну и после всей проделанной
работы отцепить.
javaVMRef^^.DetachCurrentThread(javaVMRef) ;
Ранее я говорил что
указатель на виртуальную машину не
меняется так что ее даже не нужно делать
глобальной.
Теперь основная проблема,
как сделать так что бы в определенном
потоке javaEnvRef был свой и так же своими
были указатели на ID переменные в яве.
Для решения этой проблемы
я пришел к следующему выводу, все
переменные получившие ID запихнуть в
массив, а для четкого понимания какую
ID извлекать из какого элемента массива
я все ранее использованные переменные
запихнул в типизированный массив.
type
javavariablesid = (
javaField_lcltext,
javaField_lcltitle,
Теперь для того что бы
извлеч ID достаточно было указать элемент
типизированного массива в числовом
виде что и было сделано.
Однако получение ID это
пол беды нужно еще получить javaEnvRef для
конкретного потока. Что бы особых проблем
небыло я создал массив записей в котором
было 3 переменных
type
TEnvRefStructureLayerThread = record
EnvID: pointer;
ThreadID: integer;
variablesid: array of pointer;
end;
fArrayEnvRefLayerThread: array of
TEnvRefStructureLayerThread;
первое это указатель
на структуру EnvRef
второе это Номер потока
полученного присвоении EnvRef.
Третье это массив
записей с ID ява переменными.
Итак у нас получилась
следующая картина
Позднее после некоторых
экспериментов я просто заменил основную
переменную javaEnvRef на функцию которая
сначала получала GetCurrentThreadId текущего
потока потом искала в массиве ThreadID:
integer; схожего потока и выводила EnvID:
pointer; структуру.
Таким образом удалось
универсально решить проблему, правда
недостаток в ней в том что при каждом
вызове нужно проводить поиск в массиве
что немного снижает эффективность
вызова.
Напомню что это было
сделано для совместимости внутри
лазаруса. Позднее можно будет сделать
оптимизацию кода для ускорения работы.