Fix the COM class with the attribute ComImport problem in F#.
Warning
Snapshot Version
This is a snapshot version. It is probably not stable, like compiler error or no effect to your build.
[<ComImport>]
[<ComClassImportFix>] // Add this attribute.
[<Guid("A2A9545D-A0C2-42B4-9708-A0B2BADD77C8")>]
type StartMenuPin() = class endDownload from the release .
Using dotnet CLI to install downloaded packages.
dotnet add package PCT.FSharp.ComClassImportFix --source ".\YouDownloadDir"In C#, you can write this to declare a COM object. And it works well.
[ComImport]
[Guid("A2A9545D-A0C2-42B4-9708-A0B2BADD77C8")]
public class StartMenuPin { }If you rewrite this in F# below, you will get an runtime exception TypeLoadException.
[<ComImport>]
[<Guid("A2A9545D-A0C2-42B4-9708-A0B2BADD77C8")>]
type StartMenuPin() = class endIf we decompile it, we will find the different handling method between the C# and F# compiler.
// Decompile from C# implement
[SupportedOSPlatform("windows")]
[Guid("A2A9545D-A0C2-42B4-9708-A0B2BADD77C8")]
[ComImport]
internal class StartMenuPin
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
public extern StartMenuPin();
}
// Decompile from F# implement
[ComImport]
[Guid("A2A9545D-A0C2-42B4-9708-A0B2BADD77C8")]
[CompilationMapping(SourceConstructFlags.ObjectType)]
[Serializable]
public class StartMenuPin
{
public StartMenuPin()
: this()
{
}
}If we add [ComImport] attribute, the C# compiler can identify this attribute and generate a extern constructor declaration with [MethodImpl] attribute, to tell CLR that this is a COM invoke and handle it in correct way. But F# compiler cannot handle this feature in a special way and added a default constructor for this class. In other words, F# compiler incorrectly treated this COM class declaration as a regular class. This work will cause an exception TypeLoadException like this output below.
Unhandled exception. System.TypeLoadException: Method with non-zero RVA in an Import.
at Experiments.FSharp.TaskberComObjectLogic.unpinFromTaskbar(String linkFilePath)
at <StartupCode$Experiments-FSharp>.$TaskbarComObjectExperiment.Pipe #1 stage #1 at line 164@164.Invoke(String p) in E:\dotNETWork\Experiments\Experiments.FSharp\TaskbarComObjectExperiment.fs:line 164
at Microsoft.FSharp.Primitives.Basics.List.map[T,TResult](FSharpFunc`2 mapping, FSharpList`1 x) in D:\a\_work\1\s\src\fsharp\src\FSharp.Core\local.fs:line 247
at Microsoft.FSharp.Collections.ListModule.Map[T,TResult](FSharpFunc`2 mapping, FSharpList`1 list) in D:\a\_work\1\s\src\fsharp\src\FSharp.Core\list.fs:line 97
at Experiments.FSharp.TaskbarComObjectExperiment.runPlayground(FSharpList`1 args) in E:\dotNETWork\Experiments\Experiments.FSharp\TaskbarComObjectExperiment.fs:line 164
at Experiments.FSharp.Program.main(String[] _arg1) in E:\dotNETWork\Experiments\Experiments.FSharp\Program.fs:line 12
We use Mono.Cecil to fix the assembly after build in build process. Tools remove the default constructor of the COM class declaration and add the correct one to fix this problem like the decompiled code below.
[ComImport]
[ComClassImportFix] // Only add this!
[Guid("A2A9545D-A0C2-42B4-9708-A0B2BADD77C8")]
[CompilationMapping(SourceConstructFlags.ObjectType)]
[Serializable]
public class StartMenuPin
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
public extern StartMenuPin(); // Correct constructor declaration
}This tool is easy to use. You only need to add an attribute [ComClassImportFix] and wait the compiler to build it.
-
This is a snapshot version. It is probably not stable, like compiler error or no effect to your build.
-
This tool only identify the instance class with
[ComClassImportFix]and[ComImport], not static class, abstrct class, interface and so on. If you add[ComClassImportFix]to normal class, this class will be skipped.However, there is indeed a known issue about the COM interface declaration. This is due to F#'s type inference not automatically recognising the relationship between the class and its interface.
// Interface declaration [<ComImport>] [<Interface>] [<Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE")>] [<InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)>] type IShellItem = interface end [<ComImport>] [<Interface>] [<Guid("4CD19ADA-25A5-4A32-B3B7-347BEE5BE36B")>] [<InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>] type IStartMenuPinnedList = abstract member RemoveFromList : [<MarshalAs(UnmanagedType.Interface)>] pIShellItem : obj -> int let pinItem = StartMenuPin() match pinItem with // FS0193: Type constraint mismatch. The type 'IStartMenuPinnedList' is not compatible with type 'StartMenuPin'. | :? IStartMenuPinnedList as pinnedList -> (* Do somthing *) | _ -> (* Handle other value types. *)
There is no tools can fix this problem. To solve this error, you should use
boxfunction to cast the value type toobjlet pinItem = StartMenuPin() match box pinItem with // Correct! | :? IStartMenuPinnedList as pinnedList -> (* Do somthing *) | _ -> (* Handle other value types. *)
Although this project support .NET Standard 2.0, we suggest you using it on .NET version 5~10. And it only supports on Windows platform.
This project is developing. It probably contains some bugs. Welcome to submit an issue!