суббота, 25 августа 2012 г.

GLScene 1.2 Ревизия 6121 Обновления каденсера и механизм многопоточности JNI


Приветсвую! Много воды утекло с момента последнего сообщения, но тем не менее на то было основание. Моя попытка с ходу портировать каденсер провалилась ввиду незнания особенностей работы 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; структуру.

Таким образом удалось универсально решить проблему, правда недостаток в ней в том что при каждом вызове нужно проводить поиск в массиве что немного снижает эффективность вызова.

Напомню что это было сделано для совместимости внутри лазаруса. Позднее можно будет сделать оптимизацию кода для ускорения работы.