Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to link to a C# (i.e., managed) DLL from Haskell?

I am trying to build a Windows DLL from my Haskell code. The functions in this DLL are supposed to be called from a managed code in C#. And, atleast one of the function (defined in the c# code) is to be called from a function in this DLL.

At the risk of over explaining, here's a small diagram to depict what I want:

+----------------------+           +------------------------+
|   Managed C# code    |           |  Haskell code (in DLL) |
|                      |     (1)   |                        |
|  fn_calling_hs()  -----------------> fn_called_from_cs()  |   
|                      |           |                        |
|                      |           |                        |          
| fn_called_from_hs() <--------------- fn_calling_cs()      |
|                      |     (2)   |                        |
+----------------------+           +------------------------+

I managed to make the (1) work perfectly, i.e., a Haskell function in the DLL is called by C# code, with correct marshalling of structures and arrays, and the results from the function execution in Haskell is also correct. So far, so good.

The problem is with (2), i.e., a function from Haskell (in the DLL) calling a managed function defined in C#. The problem is in the build itself - I have not yet gone past that to actually check the results of (2).

As the fn_called_from_hs() in the c# managed code is defined in C#, I only have the function symbol "imported" in the Haskell code (in DLL):

foreign import ccall fn_called_from_hs :: IO CString

Now, when I build my Haskell project with stack, it builds the Haskell DLL without problems, but the build continues to also link "main.exe" - and this fails (obviously), because there is no function fn_called_from_hs() defined anywhere in the Haskell code (it is defined in c#).

Is there any way that I can stop stack from continuing to build main.exe after building HsDLL.dll? I am ok with HsDLL.dll having unresolved symbol (fn_called_from_hs()) because this symbol will be found by the runtime linker during the loading of this DLL by the managed C# code.

So far, I have tried these steps, but none of them helped:

  1. Removed the "executables" and "test" from package.yaml
  2. Added the GHC option: -no-hs-main in the package.yaml. The package.yaml portion that contains building of HsDLL looks like this:

    library:   
      source-dirs: 
      - src
      - src/csrc   
      include-dirs: src/csrc   
      ghc-options:
      - -shared
      - -fno-shared-implib 
      - -no-hs-main
    
    1. Completely removed the Main module (i.e., removed Main.hs that was automatically created by stack from the "app" folder)
    2. I added the -dynamic flag in the ghc-options in the hopes that GHC will assume that the unresolved symbols will be defined elsewhere, but this gave other problems: GHC now complains that it needs "dyn" libraries of base, etc.

So, finally, I always end up with this:

PS C:\workspace\Haskell\hscs\src\csrc> stack build
hscs-0.1.0.0: configure (lib)
Configuring hscs-0.1.0.0...
hscs-0.1.0.0: build (lib)
Preprocessing library for hscs-0.1.0.0..
Building library for hscs-0.1.0.0..
Linking main.exe ...
.stack-work\dist\5c8418a7\build\HsLib.o:fake:(.text+0x541): undefined reference to `fn_called_from_hs'
collect2.exe: error: ld returned 1 exit status
`gcc.exe' failed in phase `Linker'. (Exit code: 1)

--  While building custom Setup.hs for package hscs-0.1.0.0 using:
      C:\tools\HaskellStack\setup-exe-cache\x86_64-windows\Cabal-simple_Z6RU0evB_2.0.1.0_ghc-8.2.2.exe --builddir=.stack-work\dist\5c8418a7 build lib:hscs --ghc-options " -ddump-hi -ddump-to-file -fdiagnostics-color=always"
    Process exited with code: ExitFailure 1

So, my questions are: (1) I have absolutely no idea how to stop linking "main.exe"! I know that the function fn_called_from_hs() is not defined within the HsDLL, but, as I said, I am ok because it is defined in the managed c# code. I just want main.exe not to be built.

OR

(2) Should I go ahead with adding -dynamic flag to GHC (keeping all the other flags as above)? In this case, how do I get stack to install the "dyn" libraries that GHC is complaining about?

Can somebody help me? Thanks in advance for your patience in reading this (rather) long question!

like image 936
arvindd Avatar asked Jul 02 '18 05:07

arvindd


People also ask

How do I link HTML and C?

Ways: 1. Insert your exe file that is made with c++ to your html code in <a href="cppfile.exe"> And you will have an tag and after clicking it will download your cppfile.exe 2. The second way is to ofstream html tags from c++ file.

Can we connect C to HTML?

You can include or embed C code in an HTML document, but it will not do anything; it will taken just as text data. There is no support in browsers for using C as client-side scripting language. Theoretically, you could write an interpreter for C (or a subset of C) in JavaScript.

How do you link a file?

Press Ctrl+K. You can also right-click the text or picture and click Link on the shortcut menu. Under Link to, click Existing File or Web Page. In the Look in box, click the down arrow, and find and select the file that you want to link to.


1 Answers

And so finally, I managed to solve this myself! After a week of struggle, that is. And any helpful comments to add it this answer is welcome.

I did this as follows:

In C# class DLL:

I had to find a way to "export" my function fn_called_from_hs() to unsafe native code. I found this is not really straight-forward, and there are really quite some amount of articles on the internet to explain how this is done. Everything amounts to actually disassembling the .NET DLL via the tool ildasm, and in the intermediate IL file generated, adding an ".export" prefix to the function that we want to export, and then again assembling the IL file back to the DLL form using ilasm.

I found all these steps are automated by the NUGetPackage Unmanaged Exports, so the first step is to install this package as a part of your .NET project, and then adding the DLLExport attribute to your function to be exported. Make sure you have RGiesecke.DllExport in your list of imports:

using RGiesecke.DllExport;

[DllExport("fn_called_from_hs", CallingConvention=CallingConvention.Cdecl)]
public static string FnCalledFromHs()
{
      // Your function code here
}

As you can see, I have named the actual function as FnCalledFromHs() (in accordance with the naming convention in C#), but exported the same function as fn_called_from_hs (in accordance with the naming convention in Haskell). This way, when you look at the Haskell code, you will not see anything that looks out of place.

One of the most important steps for this to actually work is to make sure that the project in which you are exporting the function is made to target x64 or x86 - On default the projects target "Any CPU" - RGiesecke.DllExport does not work if the project targets "Any CPU".

Now build the project to get the csharp.dll which contains your exported fn_called_from_hs.

Before linking Haskell code

Mingw GCC (which ghc on Windows internally uses) can actually directly link with DLLs, provided they were created with gcc before. However, since we have created our C# DLL using the .NET compiler csc, we need to specifically create an import library that our Haskell can see.

We use two tools to our aid: gendef and dlltool, both of which are in the "mingw\bin" folder within your ghc installation (so, of course, you need to have this in your PATH env variable to access these tools).

Here's how I went about it:

  • Created a .def file which in-turn can be used for creating an import library:

    gendef csharp.dll
    
  • Created an import library with dlltool:

    dlltool -k -d csharp.def -l csharp.lib
    
  • Copied the above import lib to the same directory in which the DLL was present.

The last step (below) now will use this import library for actually linking with the csharp DLL.

Linking Haskell code with the above import library

This was a little trickier, and has possibly made me hit a bug in stack / GHC (not sure), but have already filed here.

I went about this as follows:

  • Added extra-lib-dirs in my stack.yaml, and added the directory in which the above import-lib was created:

    extra-lib-dirs: ["<drive>:\\path\\to\\importlib"]
    

(Note that this could have also been added to your package.yaml under "libraries", but I chose to have it in my stack.yaml).

  • Added extra-libraries to my stack.yaml, under libraries.

    extra-libraries: csharp
    
  • And, added also the options -l and -L to my ghc-options for linking my library. This is what I did to circumvent the (possible) bug that stack somehow is not passing the extra-lib-dirs and extra-libraries to ghc and ld during linking. So, my final "library" section in package.yaml looks like this (compare it to how it was before in my question above):

     library:
       source-dirs: 
       - src
       - src/csrc
       include-dirs: src/csrc
       ghc-options:
       - -shared
       - -fno-shared-implib
       - -lcslib
       - -L<drive>:\\path\\to\\importlib
       extra-libraries: csharp
    

Conclusion

With all this done, my Haskell code now simply builds well with the normal stack build command, without any "unreferenced symbols" error. On executing my Haskell code, I also checked that the c# function fn_called_from_hs was actually called, and the results got returned correctly.

Of course, there is more to this from the c# side: correct marshalling of parameters, etc., and I had to also work on those to get my result correct. The only place I can cover all of these nitty-gritties is in a blog :-)

Please feel free to cross-verify my solution, and also comment on any better way of doing this. This was the best way I could figure out after my struggles!

like image 193
arvindd Avatar answered Oct 26 '22 15:10

arvindd