摘要:抽象類、接口會生成對應的Invoker,如果C#中沒有註冊返回對象實際對應類型,則會使用這些Invoker來提供一個假的C#實現,否則哪來的類來調用Java方法呢(霧)。另外Java還支持重寫某函數以後返回比父類更具體的子類類型,這一點C#是不支持的,所以你可能需要修改生成的膠水代碼才能編譯。

早早的.NET團隊就立下了.NET和Java互操作的flag, 如果你去翻一翻dotnet/runtime庫,絲毫看不出來倉庫內在搞支持。 xamarin/java.interop庫 一直有Mono和Java互操作的實現,那麼100%的實現.NET和Java互操作就是它,這兩篇文章就是和你一起揭祕.NET和Java互操作。 天發了 服務器程序的Xamarin-Java.Interop體驗(一) ,今天繼續第二部分  服務器程序的Xamarin-Java.Interop體驗(二) 原本以爲會比較容易跑起來demo,但其實還是我太單純了。

那麼今天來介紹一下單純的在C#中調用Java代碼段的一些解讀。這樣,意味着我們在本文中會直接調用Java的類,而不會在C#中進行繼承、重寫等。

此時需要考慮用到兩個工具:class-parse和generator。

class-parse通過讀取jar包字節碼,推導出每個類的public、protected方法、字段,並以XML的格式輸出。此工具基本上沒有太大問題,可以直接使用;當然了,你不會在C#裏用java的Stream API吧,所以可以考慮改一下源碼來手動去掉stream api。

generator通過讀取上述工具生成的XML和部分引用程序集來生成對應的.cs文件。這個工具似乎官方的進度還不夠快,有很多老舊的類名稱、方法都沒有修改(例如JNIEnv、RegisterAttribute、JniHandleOwnership等)需要魔改後才能正式用起來。https://github.com/yang-er/java-interop 這裏提供了我自己魔改的結果,不保證運行正確性、與最終發佈時的設計的一致性啊~

上述程序運行完了以後,你會獲得一個一串.cs文件,然後編譯之後就可以在你的C#程序裏運行了。注意由於截止目前還沒有支持coreclr,請使用TargetFramework = net472編譯,並在linux/macos上用mono運行。另外直接根據rt.jar編譯出來的文件需要進行一些修改(例如讓Java.Lang.Object繼承於Java.Interop.JavaObject,讓Java.Lang.Throwable繼承於Java.Interop.JavaException)

互操作基本方法

generator將對應類的字段、函數,生成對應的JNI調用代碼,C#運行時調用這個函數就會通過JNI訪問Java的對應功能。

  • 每個函數都會翻譯出來四個部分:

    • 一個cb_XXXX的Delegate,用於緩存互操作的時候Java的callback,在繼承和重寫中需要使用。

    • 一個GetXXXXHandler,用於獲取或創建上述callback的委託。

    • 一個n_XXXXX_函數,是提供上述回調類似於C++的方式訪問(函數簽名都是IntPtr、int等基礎值類型),在C#中獲取對應對象並進行調用。

    • 一個對應的函數,會將傳參列表轉換成 jvalue* 數組,然後通過JniPeerMember緩存的方法信息進行調用。

  • 普通的字段會被生成成爲具有getter和setter的屬性

  • 具有getXXX(),setXXX(value)的一對函數也會被翻譯成屬性

  • Listener、Observer之類的東西則會被翻譯成事件、EventArgs等

  • 抽象類、接口會生成對應的Invoker,如果C#中沒有註冊返回對象實際對應類型,則會使用這些Invoker來提供一個假的C#實現,否則哪來的類來調用Java方法呢(霧)

一些細節和討論

設計是否正確?

是否有必要將get和set對翻譯成屬性?我個人的觀點是:只翻譯成對應的函數,然後提供一個屬性來訪問對應函數。顯然這些get和set也可能被virtual override,而重寫屬性的話代碼就會長得比較醜了。

另外對有些類型的返回處理是否有必要?例如java.lang.String和System.String之間是否有必要每次調用都轉換?數組直接返回JavaArray 不也挺好?有必要將java.util.Collection,java.util.Set等翻譯成System.Collections.ICollection嗎?雖然生成的代碼更C#了,但是實際上似乎會比較影響GC和性能吧?我個人持懷疑態度。

IJavaPeerable

目前與Xamarin.Android一個很大的變化是,他們決定廢棄JNIEnv這個不倫不類的類,改爲使用JniEnvironment這個進行良好的整理的類。所以類的生成內容都有變化。原來的JniEnv中提供了直接對IntPtr操作的類,現在由JniObjectReference提供對應的方法來複制,整理的更加“乾淨”。

在Xamarin團隊決定將互操作支持帶到桌面上的時候,他們一開始使用了SafeHandle來代替原來的IntPtr,但是發現性能下降明顯,所以後期他們全部改成了JniObjectReference。目前的generator大部分還都返回IntPtr+JniHandleOwnership,你需要改成ref JniObjectReference+JniObjectReferenceOptions。

除此之外,與初代實現的不同一點是,

類型系統相容性

顯然Java中,Throwable是繼承於Object的,但是如果想在C#中強類型處理Java異常,Throwable就不能再繼承於Object了,除非之後CLR規範修改(霧)

另外目前的Generator生成出來的並沒有泛型,全部都是平鋪直敘的類。如果想支持C#那樣的泛型,需要後期他們繼續增加支持,目前你需要自己寫一些膠水代碼(繼承、重寫、cast)來“支持”。

另外Java還支持重寫某函數以後返回比父類更具體的子類類型,這一點C#是不支持的,所以你可能需要修改生成的膠水代碼才能編譯。

性能

這套框架走JNI,所以其實性能其實不會太差?但是需要注意的是,這套框架目前翻譯Java數組、CharSequence的時候,會有Java數組內容複製到C#數組,和C#數組內容複製到Java數組裏,這兩個過程,你需要非常小心,儘量在膠水中少使用數組,多使用ArrayList等。

完成進度

我怎麼總覺得按他們的速度,這個功能會跳票啊?(大霧)

相關文章