没有合适的资源?快使用搜索试试~ 我知道了~
《理论计算机科学电子笔记》第41卷第1期(2001年)网址:http://www.elsevier.nl/locate/entcs/volume41.html29页Lambada,Haskell作为更好的Java埃里克·梅耶尔emeijer@meijcrosoft.com西格比约恩·费恩sigbjorn@galconn.com摘要Lambada框架为Haskell(目前Hugs和GHC都使用非Haskell98扩展)和Java之间的Cubuid互操作提供了便利。使用Lambada,我们可以从Haskell调用Java方法,并让Java方法调用Haskell函数。该框架基于Java本机接口(JNI)。Lambada发行版包括一个从Java.class文件生成IDL的工具(使用reflection),它被输入到我们现有的HDirect中以生成Haskell可调用的存根。1介绍毫无疑问,与其他语言交互的能力对于任何编程语言的长期生存至关重要,特别是对于Haskell或ML等利基语言。Java是一个有趣的互操作伙伴,原因如下:• Java提供了大量稳定的、通常文档化良好且设计良好的库和API。• 通过Java本机接口(JNI)[8,4]与Java的交互可以有效地允许我们编写底层JVM的脚本。这使得与Java的双向互操作性非常灵活,并且与供应商无关。• 由于Java尽管有这些优点,是乏味且容易出错的。 使用原始JNI就像在JVM上进行汇编编程。 我们的使命是通过JNI使Java和Haskell之间的互操作像直接在Java中编程一样方便。ElsevierScienceB提供2001个端口。 V. 操作访问和CCB Y-NC-ND许可证。2Lambada的架构大量借鉴了我们以前在面向Haskell和COM [6,2,3]方面的工作,特别是自动化的绑定[7]。我们建议读者参考这些论文,以讨论Lambada使用的对象编码背后的基本原理。2Java原生接口简单地说,JNI提供了许多调用函数来初始化和实例化Java虚拟机(JVM),并定义了到JVM的COM接口(JavaVM接口),以及到在特定JVM内运行的各个线程的COM接口(JNIEnv接口)。JNIEnv接口包含大约230个方法,用于与关联线程中的对象进行交互2.1从Java调用C,反之亦然为了解释C和Java通过JNI交互的方式,我们使用了一个稍微有点反常的Hello World!其中,Java方法调用C过程,该C过程回调Java以打印Hello World!.为了在Java中使用外部函数,我们将方法声明为本机方法。类Hello从HelloImplDLL1导入一个本地方法cayHello,并定义一个普通的静态Java方法sayHello,它将在标准输出上打印问候语:class Hello {public voidonDestinyNode() {System.out.println(“Hello World!“);}静态{System. out. println(“println”);}}在Java中,我们不能只调用任意DLL的条目; Java规定了本地方法应该是什么样子。对于这个例子,HelloImpl.dll应该有签名为2的条目Java_Hello_cayHello:JNIEXPORT void JNICALLJava_Hello_cayHello([in]JNIEnv* env,[in]jobject this)1动态链接库(DLL)是一个可执行文件,在Win32平台上充当共享函数库。2在本文中,我们为不同的JNI过程指定了C签名,IDL属性,如[in]和[out]。3一般来说,每个本地方法实现都有一个额外的JNIEnv参数和一个jobject对象引用(或者静态方法的jclass类对象)。JNIEnv参数是一个接口指针,原生方法可以通过它与Java通信,例如访问对象的字段,或者回调到JVM。jobject(或jclass)引用对应于Java中的this指针(或class对象)。过程Java_Hello_cayHello首先获取Hello类的class对象,然后获取静态void sayHello()方法的methodID,最后调用它:jclass cls=(*env)-> GetObjectClass(env,this);jmethodID mid =(*env)->GetStaticMethodID(env,cls,“sayHello”“()V”);(*env)->callStaticVoidMethod(env,cls,mid);在上面的例子中,主动权来自Java方面。也可以从C端开始。JNI_JavaVM函数接受一个初始化结构,并加载和重命名一个JVM实例。它返回指向JVM的JavaVM接口指针和指向在该JVM实例中运行的主线程的JNIEnvJint JNI_Jint JavaVM([out]JavaVM** v,[out]JNIEnv** e,[in]JavaVMInitArgs* a);为了以这种方式调用sayHello,我们首先在下面加载并初始化JVM。调用JNI_JavaVM来获取JNIEnv指针,然后调用sayHello方法,除了我们通过FindClass函数获取类对象并通过销毁JVM来完成JNIEnv* env;JavaVM*JVM;JavaVMInitArgs args;new_InitArgsjavaVM(jvm,env,args);jclass cls=(*env)-> FindClass(env,“Hello”); jmethodIDmid =(*env)->GetStaticMethodID(env,cls,“sayHello”“()V”);(*env)->callStaticVoidMethod(env,cls,mid);(*jvm)-> DestroyJavaVM(jvm);尽管我们省略了许多细节(包括处理错误),但很明显,在这种抽象级别上与Java交互是相当令人麻木的。特别是,我们必须在每个调用中绕过JNIEnv指针的事实是我们想要摆脱的。此外,本发明还提供了一种方法,4方法调用中的结果类型编码(例如callStaticVoidMethod)以及静态方法和实例方法之间的区别是我们想要向用户隐藏的。3四个简单步骤我们将介绍一些基本的JNI概念,这些概念支持Haskell的JNI绑定,同时回顾Hugs/GHC外部函数接口的基础知识关于FFI的更全面的解释,我们参考我们的ICFP 99论文[2]。3.1函数指针为了从Haskell调用外部函数,我们将外部定义的函数导入Haskell,通过静态链接或动态链接。例如,我们可以静态导入MicrosoftWindowskernel32DLL的LoadLibraryA条目HMOO2String s(String s,String s);提供以下外国进口申报:type CString = AddrtypeHMOString=Int国外进口标准调用“内核32”“LoadLibraryA”primLoadLibrary::CString->IOHMOString这个foreign声明定义了一个Haskell函数primLoadLibrary,它接受一个null终止字符串的地址,该字符串包含一个可执行模块的文件名,动态地将模块加载到内存中,并返回一个句柄到请求的模块。正 如 其 名 称 所 示 , 函 数 primLoadLibrary 相 当 原 始 。 使 用 函 数marshallString和freeCString,我们可以轻松地编写一个更友好的版本,它将普通的Haskell字符串作为其参数:marshallString:: String->CString freeCString::CString ->IO()loadLibrary::FilePath -> IOHMO命令loadLibrary=\p -> do{s- marshallString p; h-primLoadLibrarys;freeCstrings;return h;5}kernel32 DLL还导出一个函数GetProcAddress,该函数在给定一个模句柄和一个指向包含所需函数名的空终止字符串的指针的情况下返回该函数的地址FARPROC GetProcAddressA([in] HMONode,[in,string]char*);为了使用Haskell的GetProcAddr函数,我们声明了以下外部导入:typeFARPROC =地址foreignimport“kernel32”“GetProcAddressA”primGetProcAddress::HMONIO-> CString -> IO FARPROC再次我们可以容易构建体一Haskell友好版本的primGetProcAddress,以普通Haskell字符串作为参数:getProcAddress::HMONIO-> String -> IOFARPROCgetProcAddress =\h ->\n-> do{int n;pf- primGetProcAddress h pn;freeCString pn;return pf;}显式释放CStrings已经变得很乏味了,但不要绝望;在3.2节中,我们将指示Haskell垃圾收集器自动完成此操作。当使用JNI与Java接口时,我们必须处理不同的JVM实现和同一JVM的不同版本因此,我们loadLibrary和getProcAddress函数以及动态外部函数导入允许我们将特定JVM的选择延迟到最后一刻。外部导入声明的动态变化定义了一个函数,该函数可用于将外部函数指针解编组到Haskell函数中。我 们 可 以 使 用 一 个 函 数 指 针 , 通 过 声 明 动 态 外 部 导 入mkPrimNoteJavaVM来表示JNI_JavaVMDLL入口。这个函数会将低级函数指针强制到相应的Haskell函数中:类型JavaVM =地址类型JNIEnv =地址6外部导入动态mkPrimstockJavaVMFARPROC-> IO(JavaVM-> JNIEnv-> Addr-> IOInt)为了编写一个灵活的JavaVM版本,我们需要几个帮助函数。函数newJavaVMInitArgs创建了一个默认的JVM初始化结构;它附带了一个相应的自由例程freeJavaVMInitArgs:newJavaVMInitArgs:: IO AddrfreeJavaVMInitArgs:: Addr ->IO()函数newResultAddr为返回值分配空间;它还附带了相应的自由例程freeResultAddr:newResultAddr:: IO AddrfreeResultAddr:: Addr -> IO()最后,一个函数deref来缩短指针间接:deref::Addr-> IOAddr使用 这些 帮手 功能,我们 可以 完成 的 乏味 规范Java虚拟机:类型JavaVM =地址类型JNIEnv =地址JavaVM:: FilePath-> IO(JavaVM,JNIEnv)JavaVM =\p-> do{ h-loadLibraryp;a- getprocAddress h“JNI_JavaVM”; pArgs-newJavaVMInitArgs;pJavaVM- newResultAddr;pJNIEnv- newResultAddr;mkPrimData JavaVM a pJavaVM pJNIEnv pArgs;freeJavaVMInitArgs pArgs;jvm- deref pJavaVM;freeResultAddr pJavaVM;jnienv- deref pJNIEnv;freeResultAddr pJNIEnv;return(jvm,jnienv);}还有许多其他的情况下,我们想要调用动态函数指针。 例如,JavaVM和JNIEnv指针都是指向函数指针表(vtable)的双重间接寻址。我们之前使用的函数deref和函数getProcAtt i返回i-函数7表t中的指针使得调用vtable条目变得非常容易。我们将在3.3节中给出一个例子。3.2使用外部对象Haskell和Java等语言的一个优点是垃圾收集器会默默地释放未使用的资源。因此,当我们导入外部函数时,我们也导入了显式资源管理的麻烦,这似乎相反,我们想做的是在Haskell垃圾收集器中建立一个钩子,这样我们就可以告诉它如何在外部资源成为垃圾时释放它们像往常一样,一个额外的间接级别完成了这项工作。外来物体[5]是一个指向外部世界的数据ForeignObject当构造一个外部对象时,我们传递给它一个finalizer操作,一旦对象变得不可访问,Haskell垃圾收集器就会调用该操作。这给了物体一个最后打嗝的机会new_ForeignObject:: Addr->(Addr-> IO())-> IOForeignObject例如,我们可以定义一个智能的Stringmarshaller,一旦字符串变成垃圾,它就会自动释放字符串(为了避免混淆,marshallString的type CString = ForeignObjectmarshallString:: String -> IO CStringmarshallString=\s-> do{ps-Prim.marshallStrings;newForeignObject Prim.freeString ps;}请注意,导入的函数可以直接将ForeignObject作为参数。首先提取底层的Addr是相当危险的,因为垃圾收集器可能会在实际调用之前释放对象3.3导出闭包如果我们只能引进外来的功能,生活会相当无聊。当我们可以将Haskell函数伪装成普通的函数指针并将其传递给外部世界时,事情开始变得有趣起来。静态外部导出声明告诉Haskell编译器在C可调用函数接口后面导出一个8已分离的Haskell函数。例如9(假设我们在Win32平台上)我们可以创建一个DLL,其中有一个条目Reverse,它通过解组来反转C字符串,反转产生的Haskell字符串,然后将反转的字符串编组回C字符串,如下所示:type CString = Addrforeignexportstdcall“Reverse”primReverse:: CString -> IO CStringprimReverse =(unmarshallString ##return.reverse ##marshallString)双哈希运算符##只是反向一元组合:f## g =\a-> do{ b-f a;g b}。 稍后,我们还将使用单个哈希运算符#,它只是反向函数应用a #f=fa。Haskell解释器,例如Hugs,自然不支持静态导出函数。幸运的是,我们还可以动态导出Haskell函数,就像它们是C函数指针一样。动态导出的Haskell函数非常强大,并且使使用回调进行编程变得非常容易,如下面的示例所示。JNIEnv 方 法 RegisterNatives 动 态 地 向 运 行 的 JVM 注 册 新 的 本 机RegisterNatives条目引用一个类对象,其中将注册本地方法,以及一个包含本地方法的名称,类型和实现的JNINativeMethod结构数组typedefstruct{ [string]char*name;[string]char*signature;[ptr]FARPROC fnPtr;} JININativeMethod;jint RegisterNatives([in]JNIEnv*env,[in]jclass clazz,[in,size_is(nMethods)] JNINativeMethod* meths,[in]jintn方法);为了能够通过JNIEnv指针在Haskell中调用RegisterNatives,我们必须首先解引用JNIEnv指针,获取函数表中的第215个条目,并将其强制转换为Haskell可调用函数。然后我们通过3在第4节中,我们将展示如何使用动态导出和静态导出解释器组件来模拟静态导出。10结果函数需要计算结果所需的参数:getProcAt::Int-> Addr -> IOFARPROC国外进口动态mkRegisterNativesFARPROC-> IO(JNIEnv->Jclass-> Addr->Int-> IOInt)RegisterNatives:: JNIEnv -> Jclass-> Addr-> Int-> IO()registerNatives=\env -> \clazz -> \meths ->\n->do{ f-(deref## getProcAt 215##mkRegisternatives)env;fenv clazz meths n;}假设我们想通过registerNatives函数注册一个签名为void SayHello(JNIEnv*,jobject)的本机函数。我们可以通过使用函数mkSayHello从类型为JNIEnv -> Jobject -> IO()的Haskell函数轻松 构 造 所 需 的 函 数 指 针 。 后 一 个 函 数 使 用 foreign exportdynamicdeclaration定义:对外出口动态mkSayHello::(JNIEnv -> Jobject -> IO())->FARPROC因 此 , mkSayHello 是 一 个 Haskell 函 数 , 它 接 受 任 何 类 型 为 JNIEnv ->Jobject-> IO()的Haskell函数,并在运行时创建一个C函数指针,当调用该指针时,将调用导出的Haskell函数:sayHello_::FARPROCsayHello_ = mksayHello sayHellosayHello =\env->\this-> do{putStr“Hello From Haskell!“;}假设一个函数marshallJNINativeMethods从Haskell的名称/签名/函数指针三元组列表中构建一个JNINativeMethod结构数组marshallJNINativeMethods::[(String,String,FARPROC)] -> IOAddr不难定义一个函数registerHello,它将Haskell函数sayHello注册为Java方法void sayHello()的本地实现:registerHello =\env->\clazz->do{p-marshallJNINativeMethods [(“SayHello”,"()V”,sayHello_)]; env #registerNatives p 1 clazz;11}3.4使用稳定指针当我们将Haskell值(例如动态导出的函数)传递给外部世界时,我们必须防止Haskell垃圾收集器在收集期间在Haskell堆中移动它们。否则,一个保持该值的外部函数将突然指向一个完全不同的值。 此外,由于我们无法控制导出对象的副本数量,因此垃圾收集器无法再自动释放它们。因此,Haskell值隐藏在称为稳定指针的额外间接层之后[5,9]。在本文的其余部分,我们现在我们已经介绍了与外部世界交互和JNI的基础知识,我们可以开始研究本文的主题,即通过Java本地接口(JNI)在Java和Haskell4从Haskell调用Java和从Java调用Haskell从Haskell调用Java和从Java调用Haskell原则上与从C调用Java和从Java调用C只要我们有静态和动态的外部导入,从Haskell调用Java就不会有任何问题为了让Java调用Haskell,唯一的规定是Java可以加载一个包含所需本机方法的Haskell实现的DLL(直接实现本机方法),或者加 载 一 些 其 他 DLL , 这 些 DLL 可 以 使 用 我 们 在 3.3 节 中 看 到 的JRegisterNativesJNI条目动态注册本机方法DietHEP组件[11]利用了我们在以前的论文[3]中为Haskell定义的外部函数接口,并允许我们将任何Haskell模块视为普通DLL。DietHEP的客户端在通过kernel32.dll使用普通DLL或使用DietHEP原语之间没有区别(除了在运行时使用GetProcAddressEx时指定调用约定的额外灵活性通过DietHEP的额外间接层,我们可以从底层的Haskell实现中抽象出来(当然,前提是它支持DietHEP接口)。LoadLibrary函数获取Haskell模块(或GHC编译的二进制文件)的名称,加载它并返回该模块的句柄。函数GetProcAddress接受该句柄和函数名,并返回所请求的Haskell函数的[dllname(“DietHEP.dll”)]12模块DietHEP {typedef enum{stdcall, ccall}CALLCONV;StringgetString(String,String);FARPROCGetProcAddress([in] HMOOString m,[in,string]char* n);FARPROCGetProcAddressEx([in]CALLCONVc, [in] HMONORM,[in,string]char* n);};为了通过DietHEP注册本地Haskell方法,我们以预期的方式将DietHEP组件包装到Java类DietHEP中类DietHEP {staticnative int LoadLibrary(String n);intn(intm,int n);...}我们还假设我们有一个类JNI,它(在其他类中)ers)将JNI条目registernatives作为本地方法Registernatives返回Java:class NativeMethod {String name; String signature; int fnPtr;...}类JNI {static native void RegisterNatives(Class c,NativeMethod[]ms);...}使 用 这 两 个 类 , 第 2.1 节 的 Hello 示 例 可 以 编 写 为 使 用 Haskell 函 数Hello.sayHello,首先加载包含sayHello函数定义的Haskell模块Hello,然后使用JavaHello类注册sayHello的动态导出函数指针:class Hello {publicstatic native void SayHello(); static {inth = DietHEP.LoadLibrary(“Hello”);int sayHello = DietHEP.GetProcAddress(h,“sayHello”);JNI.RegisterNatives(Hello.class[] public voidnew NativeMethod(“SayHello”,"()V”,sayHello)13});}}5用户友好的图书馆在这个阶段,我们已经展示了Haskell和Java之间集成的基本框架我们将通过几个步骤使绑定更加用户友好。首先,我们将抽象出常见的模式。其次,我们在所有JNI相关代码中隐藏JNIEnv参数的显式线程接下来,我们将使用重载来隐藏JNI方法中结果类型的编码。5.1组合JNI调用在Java对象obj和JNIEnv上调用实例方法void Foo()指针env是一个三步过程:(i) 使用JNI方法GetObjectClass获取obj的类对象:cls<- env #getObjectClass obj(ii) 使用JNI方法GetMethodID通过方法的名称和类型描述查找方法的methodID:mid-env #getMethodIDcls“Foo”“()V”(iii) 使用JNI方法CallMethod调用方法,其中是方法的结果类型,传递对象,方法ID和实际参数:env #callVoidMethod obj mid nullAddr要调用静态或实例方法,或者获取或设置静态或实例字段,我们必须经历相同的步骤;获取类对象,根据名称和签名查找方法或字段ID,并最终执行所需的操作。然而,为了便于展示,我们将忽略调用静态方法,直到5.8节空间限制使我们无法解释(静态)场访问,但它们简化与JNI交互的第一步是将基本的3步JNI调用序列组合成一个函数家族callMethod。这些函数接受一个Jobject指针,方法或字段的名称和签名,一个指向参数数组的ForeignObject指针,并返回一个值(像往常一样,我们用Prim限定了原始调用Method):call方法::Jobject-> String-> String-> ForeignObject14-> JNIEnv -> IOcallMethod =\this-> \name ->\sig->\a-> \env -> do{ cls<-env #getObjectClass this;mid- env # getMethodID cls name mid;env #Prim.call Method this mid a;}我们的下一步将是删除JNIEnv参数。5.2隐藏JNIEnv在前面的例子中,我们已经看到,大多数与Java的交互都是通过JNIEnv接口指针进行的,并且JNIEnv接口的所有方法都将必须对env进行线程化是相当乏味的,所以我们愿意在此时移动一些山,以节省以后的大量工作JNIEnv指针仅在其关联的线程中有效,因此我们不能像这样将其放在全局变量中。然而,JavaVM指针在不同的线程中仍然有效,我们可以安全地将其放在全局变量中。要做到这一点,我们需要获得JavaVM指针,其中当前线程正在运行,我们假设(当前和未来)JNI实现提供了某种方式来实现这一点。我们将遵循Liang([8],第8.1.4节)的建议之一,该建议依赖于当前JVM版本不支持在单个JVM中创建多个JVM实例的事实。过程([8],第254页)。JNI库函数getCreatedJavaVM返回所有当前运行的JVM的列表,正如我们上面所讨论的,我们可以安全地假设getCreatedJavaVM总是返回一个包含单个运行的JavaVM的单例列表。因此,getCreatedJavaVM是一个纯函数,因此我们可以使用unsafePerformIO来创建一个全局javaVM:: JavaVMjavaVM = unsafePerformIO$ do{ [javaVM]-getCreatedJavaVM;return javaVM;}JavaVM接口条目attachCurrentThread返回当前线程的JNIEnv指针。使用全局变量javaVM,我们现在可以在任何上下文中轻松安全地获取当前有效的JNIEnv指针:[4]这本书给出了其他几个建议,但这一个是最容易实现的。另一种可能性是在JNIOnLoad事件处理程序中捕获当前JVM15attachCurrentThread:: JavaVM-> IO JNIEnvgetJNIEnv:: IO JNIEnvgetJNIEnv = javaVM #attachCurrentThread现在我们可以在任何上下文中获得正确的JNIEnv,我们不必再提供JNIEnv参数来调用我们之前方法::String-> String-> ForeignObject-> Jobject-> IOcall Method = \name ->\sig-> \args ->\this-> do{ env-getJNIEnv;env #Prim.call Methodthis namesig args;}5.3重载参数类型在我们开始使用重载来简化JNI之前,我们必须对编组做一个简短的 题 外 话 在 3.1 节 中 , 我 们 假 设 我 们 有 一 个 函 数marshallString::String -> IO CString,它接受一个Haskell字符串并返回一个指向空终止字符数组的指针的原始字符串马歇尔功能marshallString本身是已定义在方面两更原始的功能函数marshallByRefChar::Char -> [Addr -> IO()]返回在指定地址写入字符的函数列表(在本例中为单例)。函数writeAt接受一个Addr-> IO()函数列表和一个起始地址,并调用列表中的每个函数将值写入后续地址:writeAt:: [Addr-> IO()]-> Addr-> IO()为了编组一个字符串,我们分配足够的内存来保存字符串,为字符串中的每个字符生成一个编组函数列表,然后将每个函数写入分配的内存:marshallString:: String -> IOAddr marshallString=\s-> do{letcs=s++[0];p-mallocelngthcs;writeAt(concat(map marshallByRefChar)cs)p; return p;}字符串的free函数只是调用系统函数free::Addr -> IO(),它释放一个内存块,16以前通过调用malloc分配的:freeString:: Addr -> IO() freeString=\s-> do{frees;}一般来说,我们有一个函数家族marshall,marshallByRef,对于每个,我们可以从Haskell编组到Java:marshall::-> IO AddrmarshallByRef::-> [Addr -> IO()] free:: Addr -> IO()此时此刻,钟声应该开始敲响,警报应该响起:所 以 我 们 定 义 了 一 个 新 的 类 型 类 GoesToJava , 它 有 三 个 方 法marshall,marshallByRef和free。为了能够重载free,我们在类中的free函数中添加了一个额外的幻影参数classGoesToJava a where{ marshall::a -> IOAddr;marshallByRef:: a -> [Addr -> IO()]; free:: a-> Addr-> IO();}并使所有可以从Haskell编组到Java的类型(Char,Int,Double,Float,Jobject,String,...)GoesToJava类的一个实例:instance GoesToJava where{ marshall=marshall;marshallByRef =marshallByRef; free=\a->free;}Double和double的marshallByRef实例返回一个两个元素的列表,第二个条目是\a-> do{return()},以确保marshall函数将分配足够的内存来容纳double或long。我们继续为元组重载GoesToJava,以编码具有零个或多个参数的Java方法。如果我们在Haskell中使用curried函数来表示多参数Java方法,这是不可能的instance GoesToJava() where{ marshall =\()-> do{ malloc0; }; marshallByRef =\()->[];free=\()-> \p ->do{ free p;};}17例如(GoesToJavaa, GoesToJavab)=> GoesToJava(a,b)其中{marshall=\(a,b)-> do{设ab=marshallByRef a++marshallByRef b;p-malloc$lengthab;return p;return p;};marshallByRef =\(a,b)->marshallByRef a ++ marshallByRefb;free=\(a,b)-> \p ->do{(deref## free)p;(deref ## free)(incrAddrp); free p;}通过使用GoesToJava类,我们Method:: GoesToJava a=>String-> String-> a-> Jobject-> IOcallMethod =\name ->\sig->\a->\this-> do{p-marshalla;int n<= new int n();int n =new int n();env #Prim.call Methodthis namesig r;}5.4重载结果类型上面的JNI调用的签名显然也需要通过类型类ComesFromJava进行重载,其中每个((),Jobject,Bool,Byte,Char,Short,Int,Float,Double)都有一个实例,可以通过JNI方法调用返回:classComesFromJava b where{ callMethod::GoesToJava a =>String-> String-> a-> Jobject-> IO b;}instance ComesFromJava where{ callMethod = call Method;18D}在这一点上,与最原始的形式相比,我们已经大大降低了JNI调用的复杂性,但仍然有足够的可能性进行改进。我们要解决的下一个问题是类型描述符字符串的显式传递,目前这是相当不安全的。5.5生成类型描述符当前版本的函数callMethod的问题是,类型描述符字符串的值(表示字段或方法的Java类型)与callMethod函数的参数和结果的相应Haskell类型之间没有联系。例如,Haskell类型检查器接受foo::()->Jobject->IOIntfoo =\()->\obj-> do{obj #callMethod“foo”“(I)V”();}不幸的是,它会导致运行时错误,因为foo方法被调用时就好像它具有Java类型voidfoo(int),而不是正确的类型int foo()(对应于类型描述符字符串应该是“()I”)。我们将通过一系列函数descriptor::<第5章.正如我们在前面的章节中所做的那样,我们定义了一个Haskell类型类,在本例中称为Descriptor,它见证了,我们可以获得描述符:class Descriptor a where {descriptor:: a-> String;}实例描述符其中{descriptor= descriptor;}基本JNI类型Bool的类型描述符是“Z”:指针Bool::Bool -> String编译器Bool = \z ->“Z”其 他 基 本 类 型 映 射 如 下 : Byte›→“B” , Char›→“C” ,Short›→“S”,Int›→“I”,Int›→“L”,Float›→“F”,Double›→方法参数的类型描述符是通过连接各个参数的类型描述符形成的实例描述符(),其中{descriptor=“";}实例(描述符a,描述符b)[5]这个技巧也被用在Haskell模块Dynamic中,并归功于传奇黑客Lennart Augustsson。19=>描述符(a,b),其中{描述符=\(a,b)->描述符a++描述符b;}方法的类型描述符是通过将参数的类型描述符包含在括号内,然后是结果类型的类型描述符来形成的,除了V用于void返回类型:方法描述符::(Descriptora,Descriptor b)=>
下载后可阅读完整内容,剩余1页未读,立即下载
cpongm
- 粉丝: 4
- 资源: 2万+
上传资源 快速赚钱
- 我的内容管理 收起
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
会员权益专享
最新资源
- zigbee-cluster-library-specification
- JSBSim Reference Manual
- c++校园超市商品信息管理系统课程设计说明书(含源代码) (2).pdf
- 建筑供配电系统相关课件.pptx
- 企业管理规章制度及管理模式.doc
- vb打开摄像头.doc
- 云计算-可信计算中认证协议改进方案.pdf
- [详细完整版]单片机编程4.ppt
- c语言常用算法.pdf
- c++经典程序代码大全.pdf
- 单片机数字时钟资料.doc
- 11项目管理前沿1.0.pptx
- 基于ssm的“魅力”繁峙宣传网站的设计与实现论文.doc
- 智慧交通综合解决方案.pptx
- 建筑防潮设计-PowerPointPresentati.pptx
- SPC统计过程控制程序.pptx
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功