红鱼儿

How to create a Android SO library dinamically loaded in your FMX projects? by Emailx45

 盒子上朋友Emailx45又发布了贴子,在FMX项目中如何建立并使用Android动态库?又是一篇实用的文章,转贴过来,同时向他表示感谢!

先看图:

  

 

Later very searches on internet and almost nothing found, I have solved the question.

First, let's remeber about that a library is, pratically, the same that an executable application. Of course, the speciallists can define better than me.

The RAD Studio can create library in MSWindows, Linux and macOS platform systems. Just use the wizard do create it in your IDE:
File -> Other -> New :

Dinamic Library (MSWindows, macOS)
Android Service (Android)

Here, we can note that there is not a Linux option, but Android is a not a Linux at end?

But this is not the point here.

The point is: What is the difference between a library to MSWindows and Posix systems?

Good point!!!

The point more significant is how the code is called and intepreted by CPU target.

Certainly this matter is beyond my knowledge, and therefore we will leave it to the engineers on duty.

Returning to my humble world of knowledge, as already mentioned above, RAD Studio can generate such code that will be supported by the target CPU supported by RAD Studio, so there is no need to go into much depth at this point.

Let's pay attention to the following points that the programmer must obey:
create library file according to desired target CPU
test the code to check for possible design flaws
check the access permissions on the target disk (at least read and write in case of MSWindows, and in Posix, execute permission if necessary)
deploy the library to the target operating system
in the client software, always ask the end user for their consent to access the disk, and later access the library file for dynamic loading.
after executing the tasks proposed by the library, always end the call, that is, freeing the memory consumed by the library.
Thus, avoiding memory accumulation and possibly a memory leak when terminating the software.

As RAD Stuido is an advanced programming interface, many of the programmer's tasks are reduced to simple clicks and choices on the IDE's screen.

This is fine, however, it makes the developer very lazy and careless. Especially if he is a beginner or has a lot of applications in his domain.

We must not forget that the programmer is you, not the IDE you use.

We must have it as a working tool, not as an excuse not to reason.

In a popular Brazilian saying, we say:
The rushed eat raw. (those in a hurry don't wait for the rice to be cooked as it should)

Now, on to the code!

Here I will demonstrate a way that will try to demystify the use of the RAD Studio IDE, and, by following, prove that many codes found on the internet, and widely reproduced in many blogs, should not be or at least should be studied and used in other ways. shapes.

I will demonstrate that you can create a RAD Studio project file (*.DPR) manually (without using the RAD Studio IDE). Naturally, some settings I'll do via IDE for the simple fact that RAD Studio creates many auxiliary files on disk, and creating them manually is really a karma that I don't want at this point.

First I will create the DPR file called "mydprmanuallibrary.dpr", which will be my library (OS) for Android 64bits.

For that, I will use Notepad++ (generic file editor) to prove that is not necessary (at all) use the IDE from RAD Studio do create it.

So, the contents of this file will be as follows:

program mydprmanuallibrary;

{$R *.res}

// ---- this code will be added by RAD Studio IDE, ok?  because the auxiliar files used by RAD!
//  ------- then, just ignore it for while....
//  uses
//    uUnitWithFunctions in 'uUnitWithFunctions.pas';
//  -----

begin
end.

Pay attention in line with "program mydprmanuallibrary;" ... this is the real difference between a "Library MSWindows" and a "Library to Posix".

--- MSWindows: library mydprmanuallibrary;
--- Posix: program mydprmanuallibrary;

Now, I will go create my unit with my function in my library

unit uUnitWithFunctions;

interface

function IncrementThisValue(AValue: integer): integer; cdecl;

exports IncrementThisValue;

implementation

function IncrementThisValue(AValue: integer): integer;
begin
  Inc(AValue);
  result := AValue;
end;

end.

Now, our library SO Android is almost ready for use. Let's use the IDE to create all auxiliar files and configurations necessary for end it.
Auxiliar files to IDE development:
--- mydprmanuallibrary.identcache
--- mydprmanuallibrary.dproj.local
--- mydprmanuallibrary.dproj

In your IDE RAD Studio, just open the DPR file to complete the task
you need just a little adjust in:
--- Project -> Options -> Application -> Version Info: 1 or any other value > 0
--- Project -> Options -> Application -> Entitlement List: check the "Secure File Sharing"
--- Project -> Options -> Application -> Use Permissions: check the "read / write external storage" (if you go use it... any way, check it)
--- save your project to create the auxiliar files and store it in disk!

Now, all it's ready for use!

Now, you can create your FMX project for your client-app as expected in common use.

Let's create a sample for tests.

On RAD, do that:

--- File -> New - Firemonkey blank project
--- add the requirement of "permissions" from user
--- add code to load the library, catch the function address, and release the library if not necessary any more.

Here my sample for your test:

unit uView.FormMain;

interface

uses
  System.SysUtils,
  System.Types,
  System.UITypes,
  System.Classes,
  System.Variants,
  FMX.Types,
  FMX.Controls,
  FMX.Forms,
  FMX.Graphics,
  FMX.Dialogs,
  FMX.Controls.Presentation,
  FMX.StdCtrls,
  System.Permissions,
  FMX.Memo.Types,
  FMX.ScrollBox,
  FMX.Memo;

type
  TForm1 = class(TForm)
    BtnRequestPermissions: TButton;
    BtnLoadSOandRun: TButton;
    Memo1: TMemo;
    procedure BtnRequestPermissionsClick(Sender: TObject);
    procedure BtnLoadSOandRunClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    procedure MyPermissionsHandler(Sender: TObject; const APermissions: TClassicStringDynArray; const AGrantResults: TClassicPermissionStatusDynArray);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses
  System.IOUtils,
  Androidapi.JNI.JavaTypes,
  Androidapi.Helpers,
  Androidapi.JNI.Os;

{$R *.fmx}

const
  MyFileSO: string   = 'libmydprmanuallibrary.so';
  MyFuncName: string = 'IncrementThisValue';

var
  MyFileSOPath: string;
  MyFuncOnLib : function(AValue: integer): integer; cdecl;

procedure TForm1.MyPermissionsHandler(Sender: TObject; const APermissions: TClassicStringDynArray; const AGrantResults: TClassicPermissionStatusDynArray);
begin
  if PermissionsService.IsEveryPermissionGranted([          //
    JStringToString(TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE), { }
    JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE)]) then
  begin
    BtnLoadSOandRun.Enabled := true;
    Memo1.Lines.Add('permissions accepted by user');
  end else begin
    BtnLoadSOandRun.Enabled := false;
    Memo1.Lines.Add('permissions refused by user');
  end;
end;

procedure TForm1.BtnRequestPermissionsClick(Sender: TObject);
begin
  PermissionsService.RequestPermissions([          { }
    JStringToString(TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE),   { }
    JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE)], { }
    MyPermissionsHandler);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  MyFileSOPath := IncludeTrailingPathDelimiter(TPath.GetDocumentsPath) + MyFileSO;
end;

procedure TForm1.BtnLoadSOandRunClick(Sender: TObject);
var
  MySOHandle: THandle;
begin
  MySOHandle   := 0;
  @MyFuncOnLib := nil;
  //
  //
  try
    Memo1.Lines.Add('trying SafeLoadLibrary(MyFileSOPath)... starting...');
    //
    Memo1.Lines.Add('Lib path: ' + MyFileSOPath);
    Memo1.Lines.Add('Lib path: ' + PWideChar(MyFileSOPath));
    //
    (*
      SafeLoadLibrary simply as a replacement for LoadLibrary.
      It has a second parameter witch is used to call SetErrorMode.
      That parameter is optional and defaults to SEM_NOOPENFILEERRORBOX which is NOT always what you want.
      if the DLL you are loading depends on other DLLs which cannot be loaded, you will get a system error dialog.
      if not dialog error, not info about the "error" ... is it not?

      in Posix compilation:
      -- LoadLibrary(...), in fact, will calls: dlopen() function
      -- GetProcAddress(...), in fact, will calls: dlsym(), dladdr(), dlopne() and dlclose() functions
      -- FreeLibrary(...), in fact, will calls: dlclose() function
    *)
    // MySOHandle := SafeLoadLibrary(MyFileSOPath);
    MySOHandle := LoadLibrary(PWideChar(MyFileSOPath)); // using Posix function:  dlopen(....)
    //
    if (MySOHandle > 0) then
    begin
      Memo1.Lines.Add('MySOHandle = ' + MySOHandle.ToString);
      //
      @MyFuncOnLib := GetProcAddress(MySOHandle, PWideChar(MyFuncName)); // using Posix function:  dlsym(....), dladdr(...)...
      //
      if Assigned(MyFuncOnLib) then
        Memo1.Lines.Add('255 + 1 = ' + MyFuncOnLib(255).ToString)
      else
        Memo1.Lines.Add('MyFuncOnLib = nil');
    end
    else
      Memo1.Lines.Add('MySOHandle = 0');
  finally
    FreeLibrary(MySOHandle); // using Posix function:  dlclose(....)
    //
    Memo1.Lines.Add('FreeLibrary(MySOHandle)... done');
  end;
end;

end.

It's all folks!

good fun with this! 

这是运行结果:

 别忘记发布so文件,如下图:

原文地址:http://bbs.2ccc.com/topic.asp?topicid=628267 

posted on 2022-04-17 09:12  红鱼儿  阅读(476)  评论(0编辑  收藏  举报