Connect library created with Julia's PackageCompiler demo to a c# program

136 Views Asked by At

Programs written in the Julia language can be compiled with the help of the package PackageCompiler.jl to a library that facilitates linking and use by external (non-Julian) programs. In the help pages of PackageCompiler.jl is a very simple demonstration about how to export two increment methods for variables of type integer and long.

After compiling this demonstration library a libinc.dll library file is created containing the increment methods, together with 53 dll files and one executable. First I tried copying all 55 files to the same folder as the .net executable. (The bin/debug/.net7.0-windows folder.) I got the error:

Unable to load dependent library myfolder\bin\Debug\net7.0-windows../bin/libopenlibm.dll

So I copied all library files except libinc.dll to a bin folder below the folder with the .net executable. Now I get the error:

Unable to load DLL 'libinc.dll' or one of its dependencies: The specified module could not be found. (0x8007007E)

I can't see what dll file is missing. So for the program to work I must have the 54 dll files in two places: A separate bin folder and the program folder.

How can I have a .net program that uses only one copy of the julia library? And preferably not a bin folder below the program folder?

Here's the console program:

// See https://aka.ms/new-console-template for more information
using System.ComponentModel;
using System.Runtime.InteropServices;


Wrapper.init_julia(0, new string[1] { "" });
int start = 1;
int ret = Wrapper.increment32(start);
Console.WriteLine($"When you add 1 to {start} you get: {ret}.");
Console.ReadKey();
Wrapper.shutdown_julia(0);

public static class Wrapper
{
    [DllImport("libinc.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    public static extern void init_julia(int argc, string[] argv);

    [DllImport("libinc.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void shutdown_julia(int retcode);

    [DllImport("libinc.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int increment32(int value);

    [DllImport("libinc.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern long increment64(long value);
}

Here's The project file:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0-windows</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <PlatformTarget>x64</PlatformTarget>
  </PropertyGroup>

</Project>
1

There are 1 best solutions below

0
Robert On BEST ANSWER

I found this issue on Github that had a solution for my problem. The solution removes the lines that have "../bin/" in the generated libjulia.dll file. It's not the most elegant solution but it works for now. Below you see how I've changed the build.jl file of the example project.

using PackageCompiler

target_dir = get(ENV, "OUTDIR", "$(@__DIR__)/../../MyLibCompiled")
target_dir = replace(target_dir, "\\" => "/")       # Change Windows paths to use "/"

println("Creating library in $target_dir")
PackageCompiler.create_library(".", target_dir;
    lib_name="libinc",
    precompile_execution_file=["$(@__DIR__)/generate_precompile.jl"],
    precompile_statements_file=["$(@__DIR__)/additional_precompile.jl"],
    incremental=false,
    filter_stdlibs=true,
    header_files=["$(@__DIR__)/mylib.h"]
)

libjulia_path = joinpath(target_dir,"bin/libjulia.dll")
libjulia_path = replace(libjulia_path, "\\" => "/")       # Change Windows paths to use "/"

if !isfile(libjulia_path)
    error("Unable to open libjulia.dll at $(libjulia_path)")
end

open(libjulia_path, read=true, write=true) do io
    # Search for ../bin/ string:
    needle = "../bin/"
    readuntil(io, needle)
    skip(io, -length(needle))
    libpath_offset = position(io)

    libpath = split(String(readuntil(io, UInt8(0))), ":")
    @info("Found embedded libpath", libpath, libpath_offset)

    # Get rid of `../bin/` prefix:
    libpath = map(libpath) do l
        if startswith(l, "../bin/")
            return l[8:end]
        end
        if startswith(l, "@../bin/")
            return "@" * l[9:end]
        end
    end

    @info("Filtered libpath", libpath)

    # Write filtered libpath out to the file, terminate with NULL.
    seek(io, libpath_offset)
    libspath = join(libpath, ":")
    Base.write(io, libspath)
    Base.write(io, UInt8(0))
end