美文网首页输入法开发xamarin开发技术iOS Developer
如何将第三方库封装成Xamarin.iOS链接库

如何将第三方库封装成Xamarin.iOS链接库

作者: 临岁之寒 | 来源:发表于2018-01-04 21:16 被阅读182次

    如果你使用的是原生的开发技术,那么你肯定是不需要阅读本文的。但是如果在你的技术栈中,Xamarin.iOS有其一席之地的话,花点时间看一看或者收藏一下本文以备日后查阅,你一定不会吃亏。因为无论是Xamarin官方所提供的文档,抑或是国内所能查找的资料(事实上,几乎找不到),要么已经过时,要么遗漏了某些重要的细节,以至于即使你依样画葫芦,却发现怎么画也画不像。在本文中,我将以我最近封装的一个第三方库——OpenCC,作为示例,实践出真知。

    OpenCC

    OpenCC是一个非常出名的中文简体转繁体的开源项目,对于做输入法的我来说自然是非常需要的。但是我在github上转了一圈,并没有发现有人为OpenCC封装了可供Xamarin.iOS调用的绑定库,倒是发现了一个Xamarin.Android的。于是我决定自己封装一个,并开源出来,以飨世人,文末会给出开源地址。但是很快,我就发现,我他妈踩了一大坑啊!

    环境准备

    1. 具备开发Xamarin.iOS的必要环境,包括Mac、Xcode、Xamarin Studio 或Visual Studio for Mac,如果是在Windows平台,则是Visual Studio和一台Mac设备充当编译的主机;本文以Mac平台为例;
    2. 安装Xcode Command Line Tools,注意:选择的版本应与本机所安装的Xcode版本一致。(安装方法在链接中)
    3. 安装最新版的Objective Sharpie,这个是帮我们自动生成绑定代码的工具,可以节省编程人员大量的精力,特别是在需要绑定的接口数量特别多的时候,堪称神器。但它不是万能的。(安装方法在链接中)

    准备材料

    1. 下载OpenCC-iOS的代码。
      目前github上的OpenCC-iOS的项目有两个,一个Objective-C的,一个Swifty的。Xamarin官方目前只给出了绑定Objective-C写成的库的方法,但是出于好奇,我还是把两个项目都下载下来了,我这一行为居然成为最后破局的关键。
    2. 分析源码
      要将第三方库封装成Xamarin.iOS可以调用的库,并不是将源码下载之后无脑封装就可以了,我们还需要对源码进行分析,将其中我们真正需要的部分提取出来。
      打开OpenCC-iOS两个的项目,仔细观察,会发现核心的代码放在src文件夹下



      事实上,如果你还下载了OpenCC的Android版本,你会发现这一部分的代码是完全一样的,他们是用标准的C++实现的。

    3. 难题
      OpenCC不是纯代码的项目,其中包含了许多用于简转繁的词库文件和配置文件。而官方给出的例子是一个纯代码的项目,而且静态库项目也是不能携带资源的,他们似乎并没有打算告诉我应该如何处理这种情况。
      真是mmp!

    封装步骤

    1 创建一个Xcode静态库项目;

    1.1 File ->New ->Project

    选择Cocoa Touch Static Library


    然后是给项目命名和选择存储位置,这里我就不给出截图了,在本文中我创建的项目名为OpenCC。

    1.2 将OpenCC-master(Objective-C版本)项目中src、darts-clone和rapidjson-0.11几个文件夹和OpenCCService.h与OpenCCService.mm两个文件都复制到该项目空间下。

    其实OpenCC-master中还有一个tclap-1.2.1文件夹和gtest-1.7.0文件夹,不过这是用于调试的代码,没有必要放到静态库中去。毕竟就算要调试,我也不会在静态库项目中进行。不仅如此,接下来,我们还要移除代码对这两个项目的引用。

    1.3 在代码中添加我们刚才复制的文件。


    注意,将我们刚才复制的文件夹中的文件都添加到OpenCC文件夹中,虽然他们实际存储在不同的文件夹里,但是添加的时候要将他们都添加的OpenCC文件夹下,添加后的结果大概是这样子。

    这个时候,我们可以尝试编译一下文件,但会发现报错。



    这正是我刚才没有选择添加的tclap中的文件。这个时候我其实有两个选择,一个是将tclap项目中的文件也都添加到这个项目中,一个是删除所有与之相关的代码。我发现tclap项目下的文件也是不少,加上前面给出的解释,我选择了后者。对待gtest项目相关的代码也是一样。我做的事情大概有这么两件:

    • 注释掉对这两个项目的所有引用和分散在各处的用于调试的main方法;
    • 移除所有以Test和TestBase为后缀的文件,这些是测试用的文件,对于静态库而言也是无用的。

    最后,我再执行编译,Build success!显而易见,在进行下一步之前,我们必须能成功地进行编译。

    2 将静态库项目编译成Fat库文件

    与Fat库相对应的是Thin库。静态库项目可以编译成不用架构下的.a文件,比如i386(模拟器用的)、Arm64和Armv7。如果库文件只适用于一种架构,则是Thin库,如果是多种架构,则是Fat库。鉴于方便我们调试和使用,我们要把项目编译成Fat库文件。

    官方给出的Makefile的内容如下:

    XBUILD=/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild
    PROJECT_ROOT=./YOUR-PROJECT-NAME
    PROJECT=$(PROJECT_ROOT)/YOUR-PROJECT-NAME.xcodeproj
    TARGET=YOUR-PROJECT-NAME
    
    all: lib$(TARGET).a
    
    lib$(TARGET)-i386.a:
        $(XBUILD) -project $(PROJECT) -target $(TARGET) -sdk iphonesimulator -configuration Release clean build
        -mv $(PROJECT_ROOT)/build/Release-iphonesimulator/lib$(TARGET).a $@
    
    lib$(TARGET)-armv7.a:
        $(XBUILD) -project $(PROJECT) -target $(TARGET) -sdk iphoneos -arch armv7 -configuration Release clean build
        -mv $(PROJECT_ROOT)/build/Release-iphoneos/lib$(TARGET).a $@
    
    lib$(TARGET)-arm64.a:
        $(XBUILD) -project $(PROJECT) -target $(TARGET) -sdk iphoneos -arch arm64 -configuration Release clean build
        -mv $(PROJECT_ROOT)/build/Release-iphoneos/lib$(TARGET).a $@
    
    lib$(TARGET).a: lib$(TARGET)-i386.a lib$(TARGET)-armv7.a lib$(TARGET)-arm64.a
        xcrun -sdk iphoneos lipo -create -output $@ $^
    
    clean:
        -rm -f *.a *.dll
    

    把YOUR-PROJECT-NAME全部替换成OpenCC就是我要使用的Makefile文件了。这里需要注意的是,要确保make命令的开头是Tab缩进的。如果出现Makefile:9: *** missing separator. Stop.错误,则说明格式不正确。

    将Makefile文件放在项目同一级的目录下:


    在Xcode项目中的Build Phases设置OpenCCService.h为public:


    打开终端,cd 进入该目录,执行make命令:

    如果一切顺利的话,我们可以该目录下看到.a文件了。

    执行xcrun -sdk iphoneos lipo -info libOpenCC.a看这个Fat库文件是否包含各个架构的.a文件,其结果应该是Architectures in the fat file: libOpenCC.a are: i386 armv7 x86_64 arm64

    3 使用Objective Sharpie生成API定义文件

    在生成文件之前,我们利用sharpie xcode -sdks查看一下所安装的SDK版本:

    接下来,我使用如下命令生成API定义文件sharpie bind -output SuiHanOpenCC -namespace SuiHanOpenCC -sdk iphoneos11.2 /Users/huangboru/myfile/XcodeWorkspace/SuihanOpenCC/OpenCC/OpenCC/OpenCCService.h -scope /Users/huangboru/myfile/XcodeWorkspace/SuihanOpenCC/OpenCC/OpenCC -c -F .
    其中

    • SuiHanOpenCC是我自行指定的输出目录和命名空间的名称

    • /Users/huangboru/myfile/XcodeWorkspace/SuihanOpenCC/OpenCC/OpenCC/OpenCCService.h是目标文件的全路径

    • -scope /Users/huangboru/myfile/XcodeWorkspace/SuihanOpenCC/OpenCC/OpenCC指定了生成的接口范围,如果不增加这条命令的话,Objective Sharpie会将OpenCCService.h文件中include的头文件中声明的公共接口也一并生成对应的C# API,这会导致定义文件的体积和接口的数量膨胀,那些接口我们既用不上,同时也增加了我们纠正问题的成本。

    Objective Sharpie并不是万能,在自动生成的接口中很可能会带有 [Verify]特性。这说明基于已有信息,Objective Sharpie无法确定其所生成的接口是否合适,这个特性会强制我们检查每一个带有该特性的接口定义,否则后续的Xamarin项目是不能通过编译的。
    如果想要了解更多关于接口定义的信息,参考该文档Binding Types Reference Guide,如果你还是不知道正确的接口是什么样的,删除掉所有的[Verify]标记就OK了。

    在这个例子中,由于我加入了-scope参数,所以生成出来的文件内容非常简单,而这些也正是我所需要的。

    ApiDefinitions.cs的内容如下:

    using System;
    using Foundation;
    
    namespace SuiHanOpenCC
    {
        // @interface OpenCCService : NSObject
        [BaseType (typeof(NSObject))]
        interface OpenCCService
        {
            // -(instancetype)initWithConverterType:(OpenCCServiceConverterType)converterType;
            [Export ("initWithConverterType:")]
            IntPtr Constructor (OpenCCServiceConverterType converterType);
    
            // -(NSString *)convert:(NSString *)str;
            [Export ("convert:")]
            string Convert (string str);
        }
    }
    

    StructsAndEnums.cs的内容如下:

    using System;
    using ObjCRuntime;
    
    namespace SuiHanOpenCC
    {
        [Native]
        public enum OpenCCServiceConverterType : nint
        {
            S2t,
            T2s,
            S2tw,
            Tw2s,
            S2hk,
            Hk2s,
            S2twp,
            Tw2sp,
            T2hk,
            T2tw
        }
    }
    

    有了libOpenCC.a、ApiDefinitions.cs和StructsAndEnums.cs这三个文件,接下来我们就可以创建绑定库项目了。

    4. 创建Xamarin.iOS Bindings Library

    打开Xamarin Studio或者Visual Studio for Mac ,在本例中我使用的是Visual Studio for Mac,别问我两者有什么区别,我只想说改名也是一门艺术活。真是mmp!

    1. 创建一个新的Xamarin.iOS Bindings Library项目;


    这里我取名为SuiHanOpenCC。新建好之后,我们会看到项目的结构如下:


    1. 添加.a文件
      右击Native References,找到我们前面生成好的libOpenCC.a文件,将其添加到项目中;


    根据官方文档的说法,当我们完成.a文件的添加之后,IDE会自动为我们生成一个.linkwith.cs文件:


    但是!但是!但是!我并没有看到这个文件!!!我一度怀疑是我添加文件的姿势有问题,但是无论我试多少遍,这个文件就是不会出现。也许,官方已经调整了具体做法,但是文档并没有改过来,所以我只能猜测IDE到底现在会为我做什么?

    我猜这个文件已经生成了,只不过不可见而已,虽然我打开show All Files,还是看不见这个文件。于是我决定尝试编译一下。


    哇~~~Wonderful!

    几番探索之后,我确认了一点,添加完.a文件后,IDE什么都没有为我做。WTF!说好的自行车呢!

    最后,我发现,我需要在AssemblyInfo.cs文件中自行加入这句话。

    using ObjCRuntime;
    [assembly: LinkWith("libOpenCC.a", SmartLink = true, ForceLoad = true, IsCxx = true)]
    
    1. 复制API定义
    • 将前面的ApiDefinitions.cs中的内容复制到项目中的ApiDefinition.cs文件中;
    • 将前面的StructsAndEnums.cs中的内容复制到项目中的Structs.cs文件中;

    但是此时编译是通不过的,枚举类OpenCCServiceConverterType的定义有问题。


    于是,我将nint改成了uint。此时有报/Users/huangboru/myfile/xamarin_workspace/SuiHanOpenCC/SuiHanOpenCC/BTOUCH: Error BI1026: bgen:SuiHanOpenCC.OpenCCServiceConverterType: Enums attributed with [NativeAttribute] must have an underlying type oflongorulongin parameterconverterType' from SuiHanOpenCC.OpenCCService.Constructor (BI1026) (SuiHanOpenCC)`,看这意思是只要ulong和long,最后我将uint改成ulong,编译通过了。但是关于这一点,官方文档中并没有提及,当然也可能是我没有找到;

    1. 解决OpenCC中的资源问题
      关于OpenCC项目中存在的词库文件和配置文件其实很好解决,就是在引用了绑定库的项目的Resource中添加这些文件即可,但是如果每一次使用这个库都要手动添加这些文件,实在太麻烦了。
      于是我又创建了一个Xamarin.iOS Class Library项目。


    这个项目我取名为OpenCC,添加了对SuiHanOpenCC的引用和封装,并将需要使用到的资源放在这个项目中,如此一来,其它的项目只需要引用这个项目即可。

    但是在解决这个问题的过程中我发现了OpenCC项目中也有坑啊。
    在调试的过程中,项目一直报libc++abi.dylib: terminating with uncaught exception of type std::runtime_error: STPhrases.ocd not found or not accessible.,我以为STPhrases.ocd是一个可执行文件,以至于思路七拐八弯,始终不得其门而入。最后发现其根结是Swifty版本的OpenCC中对资源的命令方法与Objective-C版本的OpenCC不同。目前来看,两个版本的OpenCC的src代码内核是一样的,但内核中已经采用了.ocd格式,而Objective-C版本的OpenCC中并没有包含这种格式的文件。最后我从Swifty项目中的.ocd文件替代了原有的.txt文件才算真正地解决了问题;

    5.写在最后

    我已将我封装好的项目推送至github,如有需要,欢迎使用:Xamarin.iOS.OpenCC
    如果看完这篇文章,你仍然无法完成对第三方库的封装的话,我这里还有一言相赠:珍爱生命,远离Xamarin。

    相关文章

      网友评论

        本文标题:如何将第三方库封装成Xamarin.iOS链接库

        本文链接:https://www.haomeiwen.com/subject/frurnxtx.html