基础14-兼容性和高级互操作_第1页
基础14-兼容性和高级互操作_第2页
基础14-兼容性和高级互操作_第3页
基础14-兼容性和高级互操作_第4页
基础14-兼容性和高级互操作_第5页
已阅读5页,还剩23页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

在这一章,学习F#与其他语言进行互操作所需的内容,不仅包括在.NET框架内,而且也包括从F#中使用非托管代码,以及从非托管代码中使用F#。在本书中,我尽一切努力保证使你只需要了解F#;然而,在这一章,虽然我一直保持代码C#C++.NETCommonIL的内容,C#F#在F#中可以创建两种类型的库:一种是只在F#中使用的库,另一种是可以在任何.NETF#NET.NETF#中来看时,这些类型总是.NETF#.fsiprivateinternalF#System.Collections.ObjectModelSystem.Collections.Generic.IEnumerable首先,我们讨论为什么应该避免使用元组。如果函数返回元组,用户就必须#函数hourAndMinute,它从结构DateTime中返回时、分。moduleStrangelights.DemoModuleopenSystem//returnsthehourandminutefromthegivedateasatuplelethourAndMinute(time:DateTime)=time.Hour,time.Minute//returnsthehourfromthegivendatelethour(time:DateTime)=time.Hour//returnstheminutesfromthegivendateletminute(time:DateTime)=time.MinuteC#VisualStudio,需要在F#C#C#控制台项目,如图14-1所示。14-1C#接下来,需要在C#项目中添加对F#项目的,然后,添加下面的C#类到刚创建的项//!!!C#Source!!!usingSystem;usingstaticclassPrintClassinternalstaticvoidHourMinute()//callthe"hourAndMinute"functionandcollect//tuplethat'sTuple<int,int>t=//printthetuple's ine("Hour{0}Minute{1}",t.Item1,}}Hour16Minute#(delegatemoduleStrangelights.DemoModuleopenSystem///afunctionthatprovidesfilteringletfilterStringListfra=ra|>Seq.filter//anotherfunctionthatprovidesletfilterStringListDelegate(pred:Predicate<string>)ra=letfx=pred.Invoke(x)newResizeArray<string>(ra|>Seq.filter虽然,filterStringList要比filterStringListDelegate短很多,但是,库用户会感谢你为把函数C#下面的例子演示了调用filterStringList。调用这个函数,你需要创建委托,然后,使用FuncConvert类把它转换成FastFunc,这是一种F#类型,用来表示函数值。对于库用户来说,还有一件相当讨厌的事情,需要依赖于FSharp.Core.dll,而用户可能并不想用它。//!!!C#SourceusingusingSystem.Collections.Generic;usingStrangelights;usingclassMapOneClasspublicstaticvoidMapOne()//definealistofList<string>names=newList<string>(newstring[]{"Stefany","Sebastien","Frederik"//defineapredicateConverter<string,bool>preddelegate(strings){returns.StartsWith("S");//converttoaFastFunc<string,bool>ff=FuncConvert.ToFastFunc<string,bool>(pred);//calltheF#demoIEnumerable<string>results=DemoModule.filterStringList(ff,names);//writetheresultstotheforeach(varnameinresults){ }}}用了委托,就可以使用C#的委托功能,并把委托直接嵌入函数调用中,减少了库用户的工作量,且不需要对FSharp.Core.dll的编译时依赖。//!!!C#SourceusingusingSystem.Collections.Generic;usingStrangelights;classMapTwoClasspublicstaticvoidMapTwo()//definealistofList<string>names=newList<string>(newstring[]{"Aurelie","Ibrahima","Lionel"//calltheF#demofunctionpassingin//anonymousList<string>resultsdelegate(strings){returns.StartsWith("A");},//writetheresultstotheforeach(varsinresults){ }}},C#C#C#中使用看上去不漂亮。在这一节讨论如何在C#中使用联合类型,以及作为库设计人ty另一个包含浮点数;还提供一个函数getRandomty(),初始化ty的新实例。,moduleStrangelights.DemoModuleopenSystem//typethatcanrepresentadiscreteorcontinuoustytypety=|Discreteof|Continuousof//initalizerandomnumbergeneratorletrand=newRandom()//createarandomtyletgetRandomty()=matchrand.Next(1)|0->ty.Discrete|_-(rand.NextDouble()*float_of_int虽然,我们提供了getRandomty()去创建ty类型的新版本,但是,类型自身C#usingusingstaticclassGet{publicstaticvoidGet{//initializebothaDiscreteandContinuoustyDemoModule.tyd=DemoModule.ty.Discrete(12);DemoModule.tyc=DemoModule.ty.Continuous(12.0);}}#数属于哪一个特定的ty值。可以有三种方法,下面的两段代码,我先讨论前两种方第法是可以对值的Tag属性的分支选择(switch。这个属性是整数,但是,因为联tag_前缀,帮助识别这个整数的含义。因此,如果想使用Tag属性去找出是哪种类型的ty,通常写一个switch语句,如下面的例子//!!!C#Source!!!usingSystem;usingstaticclassGet{publicstaticvoidGet{//getarandomDemoModule.tyq=DemoModule.getRandom//usethe.Tagpropertytoswitchoverthequatityswitch(q.Tag){caseDemoModule. ine("Discretevalue:{0}",q.Discrete1);caseDemoModule. ine("Continuousvalue:{0}",q.Continuous1);}}}Discretevalue:和IsContinuous(),测试ty是属于Discrete,还是Continuous。下面的例子就演示了//!!!C#Source!!!usingSystem;usingstaticclassGet{publicstaticvoidGet{//getarandomDemoModule.tyq=DemoModule.getRandom//useif...elsechaintodisyvalueif(q.IsDiscrete()){Console.Wriine("Discretevalue:{0}",}elseif{Console.Wriine("Continuousvalue:{0}",}}}Discretevalue:DiscreteContinuous1属性,就会NullReferenceException异常。DemoModule.Easytyq=DemoModule.getRandomEasyty();if(q.IsDiscrete()){Console.Wriine("Discretevalue:{0}",}例子修改了ty类型,产生新类型Easyty,添加两个成员,把类型转换成整数moduleStrangelights.ImprovedModuleopenSystem//typethatcanrepresentadiscreteorcontinuous//withmemberstoimproveinteroperabilitytypeEasyty=|Discreteof|Continuousof//converttytoafloatmemberx.ToFloat()=matchx|Discretex->float|Continuousx->//converttytoaintegermemberx.ToInt()=matchx|Discretex->|Continuousx->int//initalizerandomnumbergeneratorletrand=newRandom()//createarandomletgetRandomEasyty()=matchrand.Next(1)with|0->Easyty.Discrete|_-Easy(rand.NextDouble()*float//!!!C#Source!!!usingSystem;usingclassGet{publicstaticvoidGet{//getarandomImprovedModule.Easytyq=ImprovedModule.getRandomEasy//converttytoafloatandshowitConsole.Wriine("Valueasafloat:{0}",q.ToFloat());}}[]F#C#F#的列表,是完全可能的,但是,我建议不要用,因为,只要再做一点,就C#List.toArray函数;转换成System.Collections.Generic.List,用newResizeArray<_>()System.Collections.Generic.IEnumerableList.toSeqC#程序员System.ArraySystem.Collections.Generic.List,因为它们F代码中使用F#列表类型完全可行的。MSDN建议使用System.Collections.ObjectModel命名空间下的Collection或ReadOnlyCollection公开集合,这两个类都有一个接收IEnumerable的构造函数,也可以从F#列表中构造。F#module//getsapreconstructedlistletgetList()=[1;2;C#foreachusingusingusingusingclass{staticvoidMain(string[]{//getthelistofList<int>ints=//foreachoverthelistprintingitforeach(intiinints){Console.Wri}}}123.NET语言,应该把它们放在命名空间下,而不是模块中。这是C#.NET语言时,被处理成类,在模块中定义的任何类型都成#替模块,C#C#using(open)命名空间,而如果是在模块中的类型,在C#中使用时,就必须把模块名作为前缀。了一个名字叫TheModule模块,来管理函数值。namespaceopen//thisisacountertypeTheClass(i)letmutabletheField=imemberx.TheFieldwithget()=//incrementsthememberx.Increment()theField<-theField+//decrementsthememberx.Decrement()theField<-theField-//thisisamoduleforworkingwiththemoduleTheModule=//incrementsalistofletincList(theClasses:List<TheClass>)theClasses|>Seq.iter(func->//decrementsalistofletdecList(theClasses:List<TheClass>)theClasses|>Seq.iter(func->在C#中使用TheClass类,现在就很简单了,因为不必要加前缀,也可以很容易地TheModule//!!!C#SourceusingusingSystem.Collections.Generic;usingStrangelights;classProgramstaticvoidUseTheClass()//createalistofList<TheClass>theClasses=newList<TheClass>(){newTheClass(5),newTheClass(6),newTheClass(7)};//incrementthe//writeouteachvalueintheforeach(TheClasscintheClasses){}}staticvoidMain(string[]args){}}#curried(tuple看下面的例子,在F#中定义一个类,其中有一个curried风格定义的成员CurriedStyle,还有一个元组风格的TupleSspacetypeDemoClass(z:int)//methodinthecurriedmemberthis.CurriedStylexy=x+y+//methodinthetuplememberthis.TupleStyle(x,y)=x+y+C#CurriedStylepublicFastFunc<int,int>CurriedStyle(intTupleStylepublicintTupleStyle(intx,intC#//!!!C#SourceusingusingusingclassProgramstaticvoidUseDemoClass(){DemoClassc=newDemoClass(3);FastFunc<int,int>ff=c.CurriedStyle(4);intresult=ff.Invoke(5);Console.Wriine("CurriedStyleResult{0}",result);result=c.TupleStyle(4,5);Console.Wriine("TupleStyleResult{0}",}staticvoidMain(string[]args){}}在接口和类中指定抽象成员,要稍微复杂一些,因为有几个的选择。下面以例子说明namespaceStrangelightstypeIDemoInterface=//methodinthecurriedCurriedStyle:int->int->//methodinthetupledTupleStyle:(int*int)->//methodintheC#CSharpStyle:int*int->//methodintheC#stylewithnamedCSharpNamedStyle:x:int*y:int->注意,OneArgStyleMultiArgStyleF#定义中的这一点不同,对从C#中的签名却有很大的影响。下面是前者的签名:intOneArgStyle(Tuple<int,intMultiArgStyle(int,C#这并不改变C#用户使用的签名,当实现这个方法时,只改变在用VisualStudio工具实现.NET语言会把参数名看得很重要。这听起来好像有下面的例子演示C#代码实现面的例子中定义的接口IDemoInterface,很明显,C#用户更乐于接收包含由MultiArgStyle或NamedArgStyle指定的方法的接口。//!!!C#Sourceusingusingusing//showshowtoimplementan//thathasbeencreatedinclassDemoImplementation:IDemoInterface//curriedstylepublicFastFunc<int,int>CurriedStyle(intx)//createadelegatetoConverter<int,int>d=delegate(inty){returnx+y;};//convertdelegatetoareturn}//tuplestylepublicintTupleStyle(Tuple<int,int>t){returnt.Item1+t.Item2;}//C#stylepublicintCSharpStyle(intx,inty){returnx+y;}//C#styleimplementation,named//makenodifferencepublicintCSharpNamedStyle(intx,inty){returnx+y;}}[]COMModelCOM上,.NETCOM,但是,系统仍然保留了这个功能,可能就是为了我们的不时之需。Windows中许多编程接口都公开为COM对象,虽然,现在越来越多地有.NET框架等效的可托管对象,但仍有一些并没有等效的可托管对象;另外,还有一些开发商的软件是通过COM公开编程接口的。.NETCOMCOM组件通常是COM组件总是通过可托管的包装(wrapper)完成的,由它处理调用非托管的代码。生成包装使用工具TlbImp.exe,称为类型库导入器(TypeLibraryImporter)随.NETSDK一起。可以在 /en-us/library/tt0cf3sx(VS.80).aspx上找到有关工TlbImp.exeTlbImp.exeCOM组件时,首先应该去检查是否有开Assemblies关首选的互操作程序集的内容,参见下一节“使用COM组件的.dll的路径传给TlbImp.exe,可托管的包装就产生在当 下。如果想SpeechAPI创建可托管的包装,就使用下面令tlbimp"C:\ProgramFiles\CommonFiles\注意:TlbImp.exe.dll.NET.NETfsc.exe命令中用开关–r进行。这个命令还有一个有用的副产品,如果编程接口没有很好的文档,可之后我能说的的事情就是你可能会发现这些程序集的结构与COM模型令结构有点不同寻常,因此,不能与大多数的.NET程序集不共享相同名转换。你会发现,在Class,每一个类都提供一个单独的接口:这就是COM对象的唯一要求。下面的例子演示如何包装SpeechAPI,我们面的例openSpeechLibletmain()=//createannewinstanceofacom//(thesealmostalwaysendwithletvoice=new//callamethodSpeak,ignoringthevoice.Speak("oworld",SpeechVoiceSpeakFlags.SVSFDefault)|>ignoredomain()COMCOMCOM风格的编程接口。这是因为现在许多开发商发布应用程序时,提供了首选的互操作程序集(PrimaryInteropAssembliesMSDN .NET10;许多属性和方返回对象(object)类型,产生的结果对象需要强制转换成真正的类型;COM类包含非托管的资源,需要处理(dispose,然而,这些类没有实现标准的.NETIDisposableF#useF#的对象表达式很容易地就实现了IDisposable。F#和COM进行交互与C#的关键不同在于须总是要创建对象的实例而非接口。类。在C#中,如果使用关键字new尝试创建COM(redirect)F# Office交互可能是使用COM风格库的最常见理由。下面的是openopenletmain()//initalizeanexcelletapp=new//loadaexcelworkletworkBook=app.Workbooks.Open(@"Book1.xls",ReadOnly=//ensureworkbookisclosedusebookCloser={newIDisposablememberx.Dispose()=workBook.Close()//openthefirstletworksheet=workBook.Worksheets.[1]:?>//gettheA1ceelandallsurroundleta1Cell=worksheet.Range("A1")letallCells=//loadallcellsintoalistofletmatrix[forrowinallCells.Rows->letrow=row:?>Range[forcellinrow.Columns->letcell=cell:?>Rangecell.Value2]]//closethe//printtheprintfn"%A"matrixdomain()IDisposablebookCloser,保证即使在出错的情况下也能关闭工作簿;Open15我们只用了两个:.Open(@"Book1.xls",ReadOnly=true);第一个工作表的索引是1:使用平台调用invokeCC++调用约定(callingconventionsWin32编程接口,这是一个巨大的库,它公开了Windows所有的内置功能。System.Runtime.InteropServicesDllImport(attribute想导入函数的.dll,加上一些其他的可选特性;第二步,用关键字extern,加以C风格函F#类型,和函数的名字,最后是用括号括起来的参数类型和参数名。结果这个函数就能像外部的.NET方法一样进行调用。WindowsMessageBeepopen//declareafunctionfoundinanexternalexternboolMessageBeep(uint32//callthismethodignoringtheMessageBeep(0ul)|>使用平台调用,最棘手的问题就是要找出要调用函数的签名。在上有C#和VB.NET中常用编程接口的签名的,F#中需要的签名也相类似。这个站点是一个百科(wiki,因此可以自由添加F#签名。(*(&&(&&器开关--nowarn51,或者命令#nowarn51。open//declareafunctionfoundinanexternaldllexternboolFileEncryptionStatus(stringfilename,uint32*letmain()//declareamutableidenifiertobepassedtotheletmutablestatus=//callthefunction,usingtheaddressofoperatorwith//secondFileEncryptionStatus(@"C:\test.txt",&&status)|>ignoreprintfn"%d"status这个例子的运行结果如下(假设在C:盘 下有一个文件test.txt,加过密的。平台调用也可以运行在Mono平台上,语法与F#中的完全一样,而难点在于保证要调用的库在所有的目标平台上都可用,且遵循在所有不同的平台上库的不同名约定有关解释的细节请看 /Interop_with_Native_Libraries上的文章。DllImport14-1做了14-1DllImport定义了传送字符串数据的字符集,可以是CharSet.Auto、CharSet.Ansi .GetLastWin32Error()COM.NET的非托管编程接口的数量在持续减少,因此,在准备使用内联中间语言(Inline内联中间语言(InlineIL)能够直接在中间语言(intermediatelanguage,IL)中定义函数体,F#编译后的语言,它主要为语言添加了实现特定低级运算符和函数,比如,加boxnotF#FSharp.Core.dll已经公开F#中使用内联中间语言很简单,只要把中间语言指令放在括号和#号中间,(##)。中间语言指令放在字符串中间,使用能够被ilasm.exe编译的标准记号,它必须是正确的中间语言形式,(ILevaluation我们现在看一下使用内联中间语言的示例。假设由于某种原因,我们要使用在F#基本库fslib.dll中定义的加法和减法运算符,用我们自己定义的函数去替换它们。我们定义了两个函数add和sub,函数体使用中间语言定义://declareaddfunctionusingtheILaddletadd(x:int)(y:int)=(#"add"xy:int//declaresubfunctionusingtheILsubletsub(x:int)(y:int)=(#"sub"xy:int//testtheseletx=add1lety=sub4//printtheprintfn"x:%iy:%i"xx:2y:"add""ret"指//createafaultyaddletadd(x:int)(y:int)=(#"ret"xy:int//attempttousefaultletx=add1UnhandledException:System.InvalidProgramException:CommonLanguageRuntimedetectedaninvalidprogram.atError.add(Int32x,Int32,随.NETSDK发布的一个工具能够帮助检测这种错误,这个工具叫peverify.exe有关这个工具的信息,参见http://m ,COMF代码调用本机代码,但是,在某些情况下,也可能F#C++写的,有可C++F#,以方便。在这种情况下,我们就要从本机代码中调用F#了。简单的方法是借用.NET提F#COMCOMC++中调用F#函数。要通过COM公开函数需要用特殊方法进行开发首先必须定义接口为函数指定契约,接口的成员必须使用命名参数(参见本章前面的“C#中调用F#库”一节接口本身使用System.Runtime.InteropServices.Guid特性来标记;然后,必须提供一个类来实现这个接口, 特性进行标记,还应该总是把ClassInterfaceType.None枚举成员传递给ClassInterface特性的构造函数,说明没有接口应AddSub给非托管的客户端,需要在命名空间Strangelights下创建接口IMath,然后,创建类Math实现这个接口,还namespaceStrangelightsopenSystemopen//defineaninterface(sinceallCOMclasses//haveaseperate//markitwithafreshlygeneratedtypeIMathAdd:x:int*y:int->intSub:x:int*y:int->//implementtheinterface,theclass//-haveanempty//-bemarkedwithitsown//-bemarkedwiththeClassInterfacetypeMath()=interfaceIMathmemberthis.Add(x,y)=x+ymemberthis.Sub(x,y)=x-AddSubMath类的主体中实现,自然没有问题;但是,如法实现类成员,都行,只需要提供接口和类,这样,COM运行时就能够在代码中有一个入,下面是这个过程中公开的最复杂的部分程序集,使COM运行时能找到它。这是通过使用工具RegAsm.exe实现的。假设我们把前面的示例代码编译成的.NET.dll叫ComLibrary.dll,那么,需要调用RegAsm.exe两次,使用下面令:,regasmcomlibrary.dll regasmcomlibrary.dll第一次创建类型库文件.tlb,它是能够用于开发的C++项目中的;第二次是程序集,使COM运行时能够找到它;这两个步骤还需要在分发程序集的机器上运行。C++调用Add函数是这样的,开发环境以及如何设置C++编译器都会对代码的编译产生VisualStudioATL。comlibrary.tlhdebugreleaseCOMCoInitializePtr;完成的。注意,命名空间是来自库的名字,而不是.NET名空间;MathCreateInstanceMath类GUID传递给它。幸运的是,为此目的,有一个常量定义;AddHRESULT,这个//!!!C++Source#include//importthemetadataabout #import"..\ComLibrary\ComLibrary.tlb"named_guids//theapplicationsmainentryint_tmain(intargc,_TCHAR*{//initializetheCOM//apointertoourCOM //createanewinstanceoftheMathHRESULThRes //checkitwascreatedif(hRes=={//definealocaltoholdthelongres=//calltheAddhRes Ptr->Add(1,2,//checkAddwascalledif(hRes=={//printtheprintf("Theresultwas:%ld",}//releasethepointertothemathCOM}//uninitialisetheCOMCoUninitialize}Theresultwas:ComLibrary.dll与程序在同样的中,否则,COM运就不必要在第一个下都一份。COMF#C/C++应用程序集成到一起,开成自定义的承载通用语言运行时,通用语言运行时就是C++应用程序,且有一些可用的库(.lib)文件,可以在标准的C++应用程序中它。承载通用语言运行时的代码要比加载COM库的代码稍许复杂一点但不需要复杂的COM库了这个方法还可能以非常常好了;但是,这种方法并不适合高性能的在C++和F#之间调用,因为,我们几乎不能控制使用的签名(signaturesused)[不知所云],通用语言运行时的方法调用是通过反射而,如果我们要调用F#代码中非常重要的部分,就会发现调用的成本很快冰被摊销了。#VisualStudioC++#include<mscoree.h>C++然后,需要加载并初始化这个通用语言运行时,这是通过在结果对象上调用CorBindToRuntimeExStart通过调用ExecuteInDefaultApp 下面是完整的C++程序://!!!C++Source#include//theheadfilethatexposestheC++methodsand#include//theapplicationsmainentryint_tmain(intargc,_TCHAR*{//pointertotheCLRhostICLRRuntimeHost*pClrHost=//invokethemethodthatloadstheHRESULThrCorBind=NULL,//CLRversion-NULLloadthelatestavailableL"wks",//GCType("wks"=workstationor"svr"=Server)//StarttheHRESULThrStart=pClrHost-//Definetheassembly,type,functionto//aswellastheparameterandvariableforthereturnLPCWSTRpwzAssemblyPath=LPCWSTRpwzTypeName=L"Strangelights.TestModule";LPCWSTRpwzMethodName=L"print";LPCWSTRpwzMethodArgs=L"oworld!";DWORDretVal;//LoadanassemblyandexecuteamethodinHRESULThrExecute=pClrHost->ExecuteInDefaultApp(pwzAssemblyPath,pwzTypeName,pwzMethodName,pwzMethodArgs,//printtheprintf("retVal:%i",}除了这段代码以外,还需要mscoree.lib,这在WindowstformSDK中。我们需要知F#stringintC++程序一起运行的F#函数非常简单:module//functionwillbeletprints=printfn"%s"soworld!retVal:0有关自定义通用语言承载的信息,参见MSDN上AlessandroCatorcini和PiotrPuszkiewicz的文章 #对难以掌握,但它也会给F#编程增添了巨大的灵活性。[host,微软翻译成承载,网络上多数翻译成寄宿,托管。前面两个的成份一些,最后的主动成份一些,因此,翻译成托管更好。][[].NETv11.1.NETv11.1F#--cli-F#代码和的程序集两方面,因此,需要知道一点,如果有可能,建议使用.NETFramework2。.NETFrameworkversions1、1.1,2之间的区别的读者,可能已经预料到,使用类型参数化的代码,俗称范型,使用.NETFr

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论