日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

android sudio jni 调用so_UE4:UPL 与 JNI 调用的最佳实践

發布時間:2023/12/31 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android sudio jni 调用so_UE4:UPL 与 JNI 调用的最佳实践 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本篇文章搬運自我自己的博客,原文鏈接: imzlp.me/posts/27289/ 作者: 查利鵬

在使用UE4開發Android時,有時需要獲取平臺相關的信息、或者執行平臺相關的操作,在這種情況下,需要在代碼中添加Java的代碼以及在C++中調用它們。有些需求也需要在游戲中從Java側接收一些事件,需要處理Java調用C++的流程。

本篇文章主要涉及以下幾部分內容:

  • UE工程中添加Java代碼
  • Java函數的簽名規則
  • Java調用C++的函數
  • C++調用Java的函數

如何利用UE的UPL特性、Java的簽名規則,以及在UE中進行JNI調用實現方法,會在文章中做詳細的介紹。

UPL

UPL全稱Unreal Plugin Language,是一個XML-Based的結構化語言,用于介入UE的打包過程(如拷貝so/編輯,添加IOS的framework/操作plist等),本篇文章主要介紹UPL在Android中的使用,UPL在IOS上的使用,在我之前的文章UE4 開發筆記:Mac/iOS 篇#UPL 在 iOS 中的應用中有介紹。

往UE項目里添加Java代碼,需要通過UPL在打包時往插入代碼來實現。

UPL的語法使用XML,文件也需要保存為.xml格式:

<?xml version="" encoding="utf-8"?><!--Unreal Plugin Example--><rootxmlns:android=""></root>

在<root></root>中可以使用UPL提供的節點來編寫邏輯(但是因為它的語法都是XML的形式來實現編程邏輯的,所以寫起來循環等控制流程十分麻煩),以添加中權限請求為例(以下代碼均位于<root></root>中):

<androidManifestUpdates><!--權限請求--><addPermissionandroid:name=""/><addPermissionandroid:name=""/><!--Android的全面屏支持--><addElementstag="application"><meta-dataandroid:name=""android:value="portrait|landscape"/><meta-dataandroid:name=""android:value="true"/></addElements></androidManifestUpdates>

使用androidManifestUpdates節點,可以在其中更新,UPL為IOS和Android都提供了很多平臺相關的節點,在使用時需要注意,不能混用。

UPL還提供了往GameActivity類中添加Java方法的節點:gameActivityClassAdditions,通過這個節點,可以直接在UPL里編寫Java代碼,在構建Android包時,會自動把這些代碼插入到中的GameActivity類中:

<gameActivityClassAdditions><insert>public String AndroidThunkJava_GetPackageName(){Context context = getApplicationContext();return ();}public String AndroidThunkJava_GetInstalledApkPath(){Context context = getApplicationContext();PackageManager packageManager = ();ApplicationInfo appInfo;try{appInfo = ((),);return appInfo.sourceDir;}catch (PackageManager.NameNotFoundException e){return "invalid";}}</insert></gameActivityClassAdditions>

插入之后生成的文件:

這兩個函數就在中了,UPL有很多增加GameActivity內容的節點,這部分內容在UE的文檔中是不全的,具體還是要去看UBT的代碼:UnrealBuildTool/System/。

UPL支持對GameActivity的擴展,不僅僅只是添加函數,還可以給OnCreate/OnDestory等函數添加額外的代碼,方便根據需求介入到不同的時機。

/* Engine/Source/Programs/UnrealBuildTool/System/ * <!-- optional additions to the GameActivity imports in --> * <gameActivityImportAdditions> </gameActivityImportAdditions> * * <!-- optional additions to the GameActivity after imports in --> * <gameActivityPostImportAdditions> </gameActivityPostImportAdditions> * * <!-- optional additions to the GameActivity class implements in (end each line with a comma) --> * <gameActivityImplementsAdditions> </gameActivityImplementsAdditions> * * <!-- optional additions to the GameActivity class body in --> * <gameActivityClassAdditions> </gameActivityOnClassAdditions> * * <!-- optional additions to GameActivity onCreate metadata reading in --> * <gameActivityReadMetadata> </gameActivityReadMetadata> * * <!-- optional additions to GameActivity onCreate in --> * <gameActivityOnCreateAdditions> </gameActivityOnCreateAdditions> * * <!-- optional additions to GameActivity onDestroy in --> * <gameActivityOnDestroyAdditions> </gameActivityOnDestroyAdditions> * * <!-- optional additions to GameActivity onStart in --> * <gameActivityOnStartAdditions> </gameActivityOnStartAdditions> * * <!-- optional additions to GameActivity onStop in --> * <gameActivityOnStopAdditions> </gameActivityOnStopAdditions> * * <!-- optional additions to GameActivity onPause in --> * <gameActivityOnPauseAdditions> </gameActivityOnPauseAdditions> * * <!-- optional additions to GameActivity onResume in --> * <gameActivityOnResumeAdditions> </gameActivityOnResumeAdditions> * * <!-- optional additions to GameActivity onNewIntent in --> * <gameActivityOnNewIntentAdditions> </gameActivityOnNewIntentAdditions> * * <!-- optional additions to GameActivity onActivityResult in --> * <gameActivityOnActivityResultAdditions> </gameActivityOnActivityResultAdditions> */

那么,寫完了UPL的腳本之后,如何來使用它呢?

需要在需要添加該UPL的Module的中添加以下代碼:

// for Android if(Target.Platform==UnrealTargetPlatform.Android){PrivateDependencyModuleNames.Add("Launch");AdditionalPropertiesForReceipt.Add("AndroidPlugin",Path.Combine(ModuleDirectory,"UPL/Android/"));}// for IOS if(Target.Platform==UnrealTargetPlatform.IOS){AdditionalPropertiesForReceipt.Add("IOSPlugin",Path.Combine(ModuleDirectory,"UPL/IOS/"));}

通過AdditionalPropertiesForReceipt來指定我們的UPL腳本,注意AndroidPlugin和IOSPlugin不可修改,文件路徑可以根據UPL文件在項目中的位置指定。

使用這種方式就把UPL添加到了UE的構建系統中,當構建Android/IOS平臺時,就會自動執行我們在腳本中的邏輯了。

Java函數簽名

JNI是什么?JNI全稱Java Native Interface,即Java原生接口。主要用來從Java調用其他語言代碼、其他語言來調用Java的代碼。

在上一節中,我們通過UPL往GameActivity中添加了Java的代碼,在UE中如何通過C++去調用這些Java的函數,需要使用JNI調用來實現。

通過C++去調用Java,首先需要知道,所要調用的Java函數的簽名。簽名是描述一個函數的參數和返回值類型的信息。 以該函數為例:

publicStringAndroidThunkJava_GetPackageName(){return"";}

以這個函數為例,它不接受參數,返回一個Java的String值,那么它的簽名是什么呢?

簽名的計算是有一個規則的,暫時先按下不表,后面會詳細介紹。

JDK提供的javac具有一個參數可以給Java代碼生成C++的頭文件,用來方便JNI調用,其中就包含了簽名。

寫一個測試的Java代碼,用來生成JNI調用的.h:

publicclassGameActivity{publicstaticnativeStringSingnatureTester();}

生成命令:

javac -h .

會在當前目錄下生成.class和.h文件,.h中的內容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */#include<jni.h>/* Header for class GameActivity */#ifndef _Included_GameActivity #define _Included_GameActivity #ifdef __cplusplus extern"C"{#endif /* * Class: GameActivity * Method: SingnatureTester * Signature: ()Ljava/lang/String; */JNIEXPORTjstringJNICALLJava_GameActivity_SingnatureTester(JNIEnv*,jclass);#ifdef __cplusplus }#endif #endif

里面導出了GameActivity類成員SingnatureTesterJNI調用的符號信息,在注釋中包含了它的簽名()Ljava/lang/String;。

Java_ue4game_GameActivity_SingnatureTester是當前函數可以在C/C++中實現的函數名,當我們在C++中實現了這個名字的函數,在Java中調用到GameActivity的SingnatureTester時,就會調用到我們C++中的實現。

把函數聲明改為:

publicclassGameActivity{publicstaticnativeStringSingnatureTester(intival,doubledval,Stringstr);}

它的簽名則是:

/* * Class: GameActivity * Method: SingnatureTester * Signature: (IDLjava/lang/String;)Ljava/lang/String; */

經過上面的兩個例子,其實就可以看出來Java函數的簽名規則:簽名包含兩部分——參數、返回值。

其中,()中的是參數的類型簽名,按照參數順序排列,()后面的是返回值的類型簽名。

那么Java中的類型簽名規則是怎么樣的呢?可以依據下面的Java簽名對照表:JNI 調用簽名對照表。

Java中的基礎類型和簽名對照表:

根據上面的規則,void EmptyFunc(int)的簽名為(I)V。

非內置基礎類型的簽名規則為:

  • 以L開頭
  • 以;結尾
  • 中間用/隔開包和類名
  • 如Java中類類型:

    • String:Ljava/lang/String;
    • Object:Ljava/lang/Object;

    給上面的例子加上package時候再測試下:

    packageue4game;publicclassGameActivity{publicstaticnativeStringSingnatureTester(GameActivityactivity);}

    則得到的簽名為:

    /* * Class: ue4game_GameActivity * Method: SingnatureTester * Signature: (Lue4game/GameActivity;)Ljava/lang/String; */JNIEXPORTjstringJNICALLJava_ue4game_GameActivity_SingnatureTester(JNIEnv*,jclass,jobject);

    JNI:Java to C++

    UE給我們的游戲生成的GameActivity中也聲明了很多的native函數,這些函數是在C++實現的,在Java中執行到這些函數會自動調用到引擎的C++代碼中:

    publicnativeintnativeGetCPUFamily();publicnativebooleannativeSupportsNEON();publicnativevoidnativeSetAffinityInfo(booleanbEnableAffinity,intbigCoreMask,intlittleCoreMask);publicnativevoidnativeSetConfigRulesVariables(String[]KeyValuePairs);publicnativebooleannativeIsShippingBuild();publicnativevoidnativeSetAndroidStartupState(booleanbDebuggerAttached);publicnativevoidnativeSetGlobalActivity(booleanbUseExternalFilesDir,booleanbPublicLogFiles,StringinternalFilePath,StringexternalFilePath,booleanbOBBInAPK,StringAPKPath);publicnativevoidnativeSetObbFilePaths(StringOBBMainFilePath,StringOBBPatchFilePath);publicnativevoidnativeSetWindowInfo(booleanbIsPortrait,intDepthBufferPreference);publicnativevoidnativeSetObbInfo(StringProjectName,StringPackageName,intVersion,intPatchVersion,StringAppType);publicnativevoidnativeSetAndroidVersionInformation(StringAndroidVersion,StringPhoneMake,StringPhoneModel,StringPhoneBuildNumber,StringOSLanguage);publicnativevoidnativeSetSurfaceViewInfo(intwidth,intheight);publicnativevoidnativeSetSafezoneInfo(booleanbIsPortrait,floatleft,floattop,floatright,floatbottom);publicnativevoidnativeConsoleCommand(StringcommandString);publicnativevoidnativeVirtualKeyboardChanged(Stringcontents);publicnativevoidnativeVirtualKeyboardResult(booleanupdate,Stringcontents);publicnativevoidnativeVirtualKeyboardSendKey(intkeyCode);publicnativevoidnativeVirtualKeyboardSendTextSelection(Stringcontents,intselStart,intselEnd);publicnativevoidnativeVirtualKeyboardSendSelection(intselStart,intselEnd);publicnativevoidnativeInitHMDs();publicnativevoidnativeResumeMainInit();publicnativevoidnativeOnActivityResult(GameActivityactivity,intrequestCode,intresultCode,Intentdata);publicnativevoidnativeGoogleClientConnectCompleted(booleanbSuccess,StringaccessToken);publicnativevoidnativeVirtualKeyboardShown(intleft,inttop,intright,intbottom);publicnativevoidnativeVirtualKeyboardVisible(booleanbShown);publicnativevoidnativeOnConfigurationChanged(booleanbPortrait);publicnativevoidnativeOnInitialDownloadStarted();publicnativevoidnativeOnInitialDownloadCompleted();publicnativevoidnativeHandleSensorEvents(float[]tilt,float[]rotation_rate,float[]gravity,float[]acceleration);

    在上一節Java簽名中已經提到過,native的方法是Java調用其他語言實現,上面這些函數在UE中均有實現,用于在引擎中接收Android設備的不同邏輯,定義分布在下列文件中:

    Runtime\Android\AndroidLocalNotification\Private\AndroidLocalNotification.cpp Runtime\ApplicationCore\Private\Android\AndroidWindow.cpp Runtime\Core\Private\Android\AndroidPlatformFile.cpp Runtime\Core\Private\Android\AndroidPlatformMisc.cpp Runtime\Core\Private\Android\AndroidPlatformProcess.cpp Runtime\Launch\Private\Android\AndroidEventManager.cpp Runtime\Launch\Private\Android\AndroidJNI.cpp Runtime\Launch\Private\Android\

    我們也可以自己在GameActivity添加native的函數,如果有一些SDK中提供了native這樣的函數,也可以用以下方式來實現,我這里寫一個簡單的例子,使用UPL往GameActivity添加一個native函數,并在C++端實現。

    <gameActivityClassAdditions><insert>publicnativevoidnativeDoTester(StringMsg);</insert></gameActivityClassAdditions>

    在C++中實現一個這樣的函數即可:

    #if PLATFORM_ANDROID JNI_METHODvoidJava_com_epicgames_ue4_GameActivity_nativeDoTester(JNIEnvjenv*,jobjectthiz,jstringmsg);{}#endif

    com.epicgames.ue4是UE生成的的包名(package com.epicgames.ue4;)。

    可以看到,在C++中實現JNIMETHOD的函數名是以下規則:

    RTypeJava_PACKAGENAME_CLASSNAME_FUNCNAME(JNIEnv*,jobjectthiz,Oher...)

    注意:這個函數必須是個C函數,不能參與C++的name mangling,不然簽名就不對了。

    在UE中可以使用JNI_METHOD宏,它定義在中。

    // Runtime/Core/Public/Android/ #define JNI_METHOD __attribute__ ((visibility ("default"))) extern "C"

    也可以使用extern "C"。在C++中定義之后,如果Java端調用了該函數,就可以執行到我們在C++里寫的邏輯了。

    JNI:C++ to Java

    通過上一節的內容,可以知道了Java中函數的簽名信息,如何在UE中通過函數名和簽名信息來在C++中調用到游戲中的Java代碼呢。

    UE在C++端封裝了大量的JNI的輔助函數,可以很方便地進行JNI操作。這些函數大多定義在下面三個頭文件中:

    // Runtime/Launch/Public/Android #include"Android/"// Runtime/Core/Public/Android #include"Android/"// Runtime/Core/Public/Android #include"Android/"

    因為位于Launch模塊中,所以在需要在中為Android平臺添加該模塊。

    以第一節我們使用UPL往GameActivity類中添加的下面這個函數為例:

    publicStringAndroidThunkJava_GetPackageName(){Contextcontext=getApplicationContext();returncontext.getPackageName();}

    想要在UE中調用到它,首先要獲取它的jmethodID,需要通過函數所屬的類、函數名字,簽名三種信息來獲取:

    if(JNIEnv*Env=FAndroidApplication::GetJavaEnv()){jmethodIDGetPackageNameMethodID=FJavaWrapper::FindMethod(Env,FJavaWrapper::GameActivityClassID,"AndroidThunkJava_GetPackageName","()Ljava/lang/String;",false);}

    因為我們的代碼是插入到GameActivity類中的,而UE對GameActivity做了封裝,所以可以通過FJavaWrapper來獲取,FJavaWrapper定義位于Runtime/Launch/Public/Android。

    得到的這個methodID,有點類似于C++的成員函數指針,想要調用到它,需要通過某個對象來執行調用,UE也做了封裝:

    jstringJstringResult=(jstring)FJavaWrapper::CallObjectMethod(Env,FJavaWrapper::GameActivityThis,GetPackageNameMethodID);

    通過CallObjectMethod來在GameActivity的實例上調用GetPackageNameMethodID,得到的值是java中的對象,這個值還不能直接轉換為UE中的字符串使用,需要進行轉換的流程:

    namespaceFJavaHelperEx{FStringFStringFromParam(JNIEnv*Env,jstringJavaString){if(!Env||!JavaString||Env->IsSameObject(JavaString,NULL)){return{};}constautochars=Env->GetStringUTFChars(JavaString,0);FStringReturnString(UTF8_TO_TCHAR(chars));Env->ReleaseStringUTFChars(JavaString,chars);returnReturnString;}FStringFStringFromLocalRef(JNIEnv*Env,jstringJavaString){FStringReturnString=FStringFromParam(Env,JavaString);if(Env&&JavaString){Env->DeleteLocalRef(JavaString);}returnReturnString;}}

    通過上面定義的FJavaHelperEx::FStringFromLocalRef可以把jstring轉換為UE的FString:

    FStringFinalResult=FJavaHelperEx::FStringFromLocalRef(Env,JstringResult);

    到這里,整個JNI調用的流程就結束了,能夠通過C++去調用Java并獲取返回值了。

    結語

    參考資料:

    - Unreal Plugin Language

    - Jni符號對照

    - JNI Types and Data Structures

    總結

    以上是生活随笔為你收集整理的android sudio jni 调用so_UE4:UPL 与 JNI 调用的最佳实践的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。