I'm using the Microsoft.Office.Interop.Excel library (version 14.0) from an F# application and I can't seem to be able to reference some of the properties defined in Interop's interfaces/classes.
For example, if I have a Worksheet object I can't do the following:
let sht = // Get the Worksheet
sht.PageSetup.CenterHeader <- // Set the header
It can't find CenterHeader as a property of the PageSetup interface, even though it's there if I view the Interop dll in the object browser.
Just for reference, the Interop dll that I'm using is from the VS directory: C:\Program Files (x86)\Microsoft Visual Studio 14.0\Visual Studio Tools for Office\PIA\Office14\Microsoft.Office.Interop.Excel.dll
Update:
I actually spoke too soon. Unfortunately the suggested solution with the cast didn't work either. VS thinks it's OK but it fails at runtime with the following error:
Unable to cast COM object of type 'System.__ComObject' to interface type 'Microsoft.Office.Interop.Excel.IPageSetup'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{000208B4-0001-0000-C000-000000000046}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
As I said in the comments, F# doesn't support type equivalence for embedded interop types, so if a C# project has Embed Interop Types enabled then the F# compiler may be unable to determine which version of the interop type to use. Since the type embedded in the C# output assembly has been stripped down to only the members used, this can make it so that the F# compiler is unable to see members that are present in the version of the type from the primary interop assembly.
The workaround is to turn off Embed Interop Types on the C# project.
This works for me:
#if INTERACTIVE
#r "office"
#r "Microsoft.Office.Interop.Excel"
#endif
open Microsoft.Office.Interop.Excel
let setCenterHeader fileName worksheet header =
let file = System.IO.FileInfo(fileName)
let excel = ApplicationClass()
try
// Make sure to use full path since this will
// be interpreted as relative to Excel's process,
// not currently executing one
let workbook = excel.Workbooks.Open(file.FullName)
let sheet = workbook.Worksheets.[worksheet] :?> Worksheet
sheet.PageSetup.CenterHeader <- header
workbook.Save()
finally
excel.Application.Quit()
setCenterHeader "TestWorksheet.xlsx" "Sheet1" "My header 1"
setCenterHeader "TestWorksheet.xlsx" "Sheet2" "My header 2"
setCenterHeader "TestWorksheet.xlsx" "Sheet3" "My header 3"
You might want to make sure you have matching versions of PIA and Office installed/used.
Related
How is it possible that the F# compiler rejects the type string when it's expecting a System.String?
#I #"..\..\packages"
#r #"FSharp.Data\lib\net40\FSharp.Data.dll"
open FSharp.Data
let [<Literal>] csvFile = #"..\..\data\FootballResults.csv"
type Football = CsvProvider< csvFile >
let data = Football.GetSample().Rows |> Seq.toArray
After running this simple code, I get the error message:
Script.fsx(5,30): error FS0001: This expression was expected to have type 'System.String' but here has type 'string'
I thought string was simply an alias for System.String?
Edit
The csv file is quite big, just the first rows are important:
Date,Home Team,Away Team,Full Time Home Goals,Full Time Away Goals,Full Time Result,Half Time Home Goals,Half Time Away Goals,Half Time Result,Home Shots,Away Shots,Home Shots on Target,Away Shots on Target,Home Fouls,Away Fouls,Home Cards,Away Cards,Home Yellow Cards,Away Yellow Cards,Home Red Cards,Away Red Cards
08/18/2012,Arsenal,Sunderland,0,0,D,0,0,D,14,3,4,2,12,8,7,0,0,0,0,0
08/18/2012,Fulham,Norwich,5,0,H,2,0,H,11,4,9,2,12,11,6,3,0,0,0,0
08/18/2012,Newcastle,Tottenham,2,1,H,0,0,D,6,12,4,6,12,8,3,5,2,2,0,0
The full file can be downloaded here under the folder data
FSharp.Data version
FSharp.Data 2.3.2
I found the problem, it was in the paket script provided in the book Get Programming with F#.
The script was pointing to an old version of FSharp.Data (I thought paket was this amazing package manager that always get you the best version for your project while in fact, it does not, you always fight and struggle with wrong dependencies).
At the end I managed to fix the problem this way:
Solution 1
Simply start a new project using the nuget command Install-Package FSharp.Data -Version 4.2.7 and then the reference to the package #r "nuget: FSharp.Data"
Solution 2
Update the paket.lock provided by the book with the correct version FSharp.Data (4.2.7). The code posted in my question run after that.
Advice for F# learner
Whoever is trying to learn F# with this book, it's a good book but be very careful with their package reference. They seem outdated and can get you into unpleasant situation.
I'm trying to explain a weird F# compiler behavior between Release and Debug configurations regarding transitive dependencies. I will use Newtonsoft.Json package as the base dependency here, because that's the furthest I managed to get in pinpointing the issue, and it makes the example a bit less abstract.
Let's create a library project called SerializerProject, referencing Newtonsoft.Json via paket. In this project there is only one module:
module Serializer =
open System.IO
open System.Text
open Newtonsoft.Json
type OptionConverter() =
inherit JsonConverter()
(* only the signature is important, the implementation
and other methods are not interesting *)
override x.WriteJson(writer: JsonWriter, value: obj, serializer: JsonSerializer) =
(* ... *)
let fromJson<'a> (bytes: byte []): 'a =
let s = Encoding.UTF8.GetString(bytes)
use sr = new StringReader(s)
use jr = new JsonTextReader(sr)
let serializer = JsonSerializer()
serializer.Converters.Add(OptionConverter())
serializer.Deserialize<'a>(jr)
Now let's create a second project in the same solution and reference SerializerProject via project reference. I'd like to use fromJson in my new project, that's why I referenced SerializerProject in the first place.
module MyModule =
open Serializer
(* This is here just so we reference the fromJson function *)
let deserializer bytes = fromJson bytes
This is the minimal example to reproduce the behavior.
Now when I build the solution in Debug configuration, everything compiles and works fine. But when I switch to Release, the compilation fails in the second project, in MyModule in the deserializer definition. The exact error message is this:
The type referenced through 'Newtonsoft.Json.JsonWriter' is defined in an assembly that is not referenced. You must add a reference to assembly 'Newtonsoft.Json'
I'm using Visual Studio 2015 Update 3, F# tools (fsc, fsi) show version 14.0.23413.0.
It kind of makes sense, because it's reading metadata of the SerializerProject and finds that public OptionConverter type leaks the type JsonWriter on its public WriteJson method (as well as other types and other methods, but this one is encountered first), but what makes me wonder is why this works in Debug mode and is only a problem in the Release mode.
What kind of extra operations does the compiler do that affect this?
Why is this not a problem in Debug build when the type defined in Newtonsoft.Json really leaks transitively into the second project?
As suggested in the comments I tried referencing Newtonsoft.Json and decompiling the second assembly with ILSpy to see whether inlining turned on by compiler optimizations is to blame here, but even in Release configuration the second assembly looks like this:
call !!0 [SerializerProject]Serializer::fromJson<!a>(uint8[])
The fromJson function has not been inlined to expose the JsonWriter type directly, so there seem to be something more subtle going on.
This isn't a blocking issue, I just made the converter types private as I don't want to use them from the outside anyway, but I'd like to dig deeper in F# compiler inner workings.
I'm loading FSharp.Data in the interactive console. The library is loaded without any problem:
> #r "FSharp.Data.dll";;
--> Referenced 'C:\Users\pw\AppData\Local\Temp\FSharp.Data.dll' (file may be locked by F# Interactive process)
> open FSharp.Data;;
However, when I'm trying to initialize CsvProvider (defined in FSharp.Data) I get the error message saying the type is not defined:
> type Stocks = CsvProvider<"C:\Users\pw\Downloads\msft.csv">;;
type Stocks = CsvProvider<"C:\Users\pw\Downloads\msft.csv">;;
--------------^^^^^^^^^^^
stdin(62,15): error FS0039: The type 'CsvProvider' is not defined
I thought the problem may be with file and assemblies paths but now I'm using absolute paths and the error remains. On the other hand, I am able to use the CsvProvider when I'm creating a standard, not interactive, project. Any help to make it work in interactive session highly appreciated.
The warning about file being locked looks worrisome. Can you copy FSharp.Data somewhere and reference it using absolute path:
\#r #"C:\Poligon\packages\FSharp.Data.2.1.0\lib\net40\FSharp.Data.dll";;
Downgrade your FSharp.Core to 4.7 and FSharp.Data to 3.3.3. It should work after that.
I am trying to make use of the ImmutableDictionary in F# using Mono. I'm using the Xamarin IDE.
I have set my target framework to Mono/.Net4.5 and imported the System.Collections.Immutable using the built in Nuget package manager.
The following line
open System.Collections.Immutable
is generating the following two errors
'/Users/UserName/Projects/Xamarin/OrderInfer/OrderInference/MyTest.fs(34,34): Error FS1109: A reference to the type 'System.Collections.Generic.IEnumerable'1' in assembly 'System.Runtime' was found, but the type could not be found in that assembly (FS1109) (MyTest)'
/Users/UserName/Projects/Xamarin/OrderInfer/OrderInference: Error FS1108: The type 'Lazy'2' is required here and is unavailable. You must add a reference to assembly 'System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. (FS1108) (OrderInference)
The 2nd error suggests I need to reference System.ComponentModel.Composition. Am I able to use it in Mono? If so, is there another assembly I need to reference?
EDIT:
Solution removed and reposted below as an answer
This problem can be solved by adding a reference to: 'System.ComponentModel.Composition'. In Xamarin's IDE, this is done by using the Edit References Dialog which can be found by right-clicking on the reference in your project. Go to the All tab and search for System.ComponentModel and just add the System.ComponentModel.Composition assembly.
I now have the following two assemblies installed:
System.Collections.Immutable.dll
System.ComponentModel.Composition.dll
My code now reads:
open System
open System.ComponentModel.Composition
open System.Collections.Immutable
type wordPairs = { pairs:ImmutableDictionary<string, string>; count:int}
let myPairs = {pairs = ImmutableDictionary.Create<string, string>(); count = 0}
Note: As gradbot pointed out (and Immo Landwerth later nitpicked about ;>> ), ImmutableDictionary is a abstract sealed class. And as such, it has no public constructors. So you need to use the .Create method.
ImmutableDictionary is abstract so new won't work. It does however provide a number of create methods.
ImmutableDictionary.Create<string, string>()
I have a question related to the code provided in an answer to this question.
The problem I have is that the three referenced assmeblies (System.dll, FSharp.Core.dll, FSharp.Powerpack.dll) that are passed to CompilerParameters are not found at runtime. The error I get is:
unknown-file(0,0) : error 0: error FS0218: Unable to read assembly
'c:\user s\utente\documents\visual studio
2010\Projects\TrashSolution\TrashSolution\bin\D ebug\FSharp.Core.dll'
How do I tell the compiler to search for these assemblies in the GAC, instead of the project's bin directory? If I open a namespace in the code provided as a string, how do I know which assemblies to add? Where can I get this information?
In the code from the answer you linked, there's a line towards the bottom:
let asm = Reflection.Assembly.LoadFrom(fileinfo.Value.FullName)
If you call Reflection.Load instead and pass it the fully-qualified assembly name, it'll try to load the assembly from the GAC (and a few other places, if the assembly isn't in the GAC).
let asm =
Assembly.Load "SampleAssembly, Version=1.0.2004.0, Culture=neutral, PublicKeyToken=8744b20f8da049e3"
If you don't know the fully-qualified assembly name you have to create an AssemblyName with the simple name of the assembly, then call the Reflection.Load overload which takes an AssemblyName instead of a string.
let asmName = AssemblyName "Your.Assembly.Name"
let asm = Assembly.Load asmName
As far as knowing which assemblies to load -- I don't think there's a simple way to determine that programmatically. The only two solutions I can think of right now:
If you have some knowledge about the code you're being given (as a string), you could parse it with the FSharpCodeProvider and look at which namespaces/modules are opened and which types are used. If you're looking to see if some particular namespace or type is used (i.e., that you would need to include an assembly reference for when compiling the code), you could create a Map (in your .fsx which is doing the compilation) of namespaces and/or type names to assembly names and use it to reference the appropriate assemblies.
You could "brute-force" search the GAC, by using the semi-documented Fusion API to enumerate all of the assemblies installed in the GAC, then using Reflection to examine each assembly and determine if it's one you require. This is likely to be extremely slow, so I'd avoid it at all costs. If you do decide to go this route, you must also use the Assembly.ReflectionOnlyLoad method to load the assemblies! This allows the assemblies to be unloaded after you finish examining them -- if you use normal Reflection the assemblies can't be unloaded and your program will likely crash with an OutOfMemoryException or similar.
EDIT: Turns out that loading the assembly by its simple name succeeds in fsi and not in normal F# code because fsi automatically installs a handler for the AppDomain.AssemblyResolve event. This event is triggered by the CLR when you try to load an assembly and it can't be resolved; the event provides a way for you to "manually" resolve the assembly and/or generate an assembly dynamically and return it.
If you look at the FileNotFoundException raised when you try to run the code in an F# project, you'll see something like this in the Fusion Log property of the exception:
=== Pre-bind state information ===
LOG: User = Jack-Laptop\Jack
LOG: DisplayName = System
(Partial)
WRN: Partial binding information was supplied for an assembly:
WRN: Assembly Name: System | Domain ID: 1
WRN: A partial bind occurs when only part of the assembly display name is provided.
WRN: This might result in the binder loading an incorrect assembly.
WRN: It is recommended to provide a fully specified textual identity for the assembly,
WRN: that consists of the simple name, version, culture, and public key token.
WRN: See whitepaper http://go.microsoft.com/fwlink/?LinkId=109270 for more information and common solutions to this issue.
LOG: Appbase = file:///C:/Users/Jack/Documents/Visual Studio 2010/Projects/StackOverflow1/StackOverflow1/bin/Debug/
LOG: Initial PrivatePath = NULL
Calling assembly : StackOverflow1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.
===
LOG: This bind starts in default load context.
LOG: No application configuration file found.
LOG: Using host configuration file:
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///C:/Users/Jack/Documents/Visual Studio 2010/Projects/StackOverflow1/StackOverflow1/bin/Debug/System.DLL.
LOG: Attempting download of new URL file:///C:/Users/Jack/Documents/Visual Studio 2010/Projects/StackOverflow1/StackOverflow1/bin/Debug/System/System.DLL.
LOG: Attempting download of new URL file:///C:/Users/Jack/Documents/Visual Studio 2010/Projects/StackOverflow1/StackOverflow1/bin/Debug/System.EXE.
LOG: Attempting download of new URL file:///C:/Users/Jack/Documents/Visual Studio 2010/Projects/StackOverflow1/StackOverflow1/bin/Debug/System/System.EXE.
Looking towards the bottom of that log, you'll see where the CLR searched for the assembly before it gave up.
Here's a simple handler to give you an idea of how to use the AppDomain.AssemblyResolve handler to manually resolve the assembly. (NOTE: The handler needs to be added before the code that attempts to load the assembly!)
System.AppDomain.CurrentDomain.add_AssemblyResolve (
System.ResolveEventHandler (fun _ args ->
let resolvedAssembly =
System.AppDomain.CurrentDomain.GetAssemblies ()
|> Array.tryFind (fun loadedAssembly ->
// If this assembly has the same name as the one we're looking for,
// assume it's correct and load it. NOTE : It may not be the _exact_
// assembly we're looking for -- then you'll need to adjust the critera below.
args.Name = loadedAssembly.FullName
|| args.Name = loadedAssembly.GetName().Name)
// Return null if the assembly couldn't be resolved.
defaultArg resolvedAssembly null))
If you add that code to a new F# console project, followed by the code which uses AssemblyName with Assembly.Load, you should be able to load the System assembly because it's referenced by default in an F# project and it'll be loaded when you run the project. If you try to resolve System.Drawing, it'll fail because our custom event handler can't find the assembly. Obviously, if you need some more complicated assembly-resolving logic, you should build that into the event handler in whatever way makes sense for your application.
Finally, here's a link to the MSDN whitepaper mentioned in the exception message: Best Practices for Assembly Loading. It's worth a read if you get stuck and can't figure out how to resolve the assemblies you need.