Failing to use CXX to link Rust-written library in C++ project - c++

I am testing CXX with a very simple project to link a Rust library into a C++ executable.
I write a foo() -> () Rust function and try to access it from C++ but the linker does not find it.
Here's what I have:
// lib.rs
#[cxx::bridge]
mod ffi {
extern "Rust" {
pub fn foo() -> ();
}
}
pub fn foo() -> () {
println!("foo")
}
# Cargo.toml
[package]
name = "cpprust"
version = "0.1.0"
edition = "2021"
[lib]
name = "cpprust"
path = "src/lib.rs"
crate-type = ["staticlib", "rlib", "dylib"] # EDIT: this is incorrect, see note at the end of question
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cxx = "1.0"
// main.cpp
void foo(); // I tried including lib.rs.h but it was not generated!
int main() {
foo();
}
Running cargo build generates target\debug\libcpprust.so.
I then try to make the project with (EDIT: g++ command is incorrect, see note at the end of question):
g++ -L../target/debug/ -lcpprust -o cpprust main.cpp
/tmp/ccOA8kJy.o: In function `main':
main.cpp:(.text+0x5): undefined reference to `foo()'
collect2: error: ld returned 1 exit status
make: *** [Makefile:2: cpprust] Error 1
What is wrong here?
EDIT: prog-fh's great answer correctly points out that I need to include build.rs with C++ compilation, even without having C++ to compile and access within the crate. However, even after implementing their answer, I was still getting the same error message. It turns out that I had two other problems: 1) the order of my arguments to g++ were incorrect, and I needed pthread -l dl as well. It should have been:
g++ -o cpprust main.cpp -I ../target/cxxbridge -L../target/debug -lcpprust -pthread -l dl
2) My Cargo.toml file was also generating "rlib", "dylib" library types, but that somehow also causes the error above; it works when only staticlib is generated.

Considering this documentation, the build.rs script should generate the lib.rs.h which was missing in your attempt.
Note that the example in the documentation considers that the main program comes from Rust, and that the C++ code is an extension.
In your question, it is the opposite situation: your main program comes from C++ but is extended by some Rust code.
This answer is made of two parts:
a minimal example very similar to yours (no C++ code to be called from Rust),
a more complete example with a bidirectional interaction between C++ and Rust (but the main program still is on the C++ side).
edit to answer subsequent questions in the comments
As said in the comment of the second build.rs below, the name chosen in .compile("cpp_from_rust") will be used to name a library containing the compiled C++ code (libcpp_from_rust.a for example).
This library will then be used by Rust to extend the Rust code: the libcpprust.a main target produced by Rust contains libcpp_from_rust.a.
If no C++ file is provided before .compile() (as in the first, minimal example below), this C++ library only contains the symbols enabling extern "Rust" access from C++.
$ nm ./target/debug/build/cpprust-28371278e6cda5e2/out/libcpp_from_rust.a
lib.rs.o:
U _GLOBAL_OFFSET_TABLE_
0000000000000000 T _Z13rust_from_cppv
U cxxbridge1$rust_from_cpp
On the other hand, you already found in the documentation that multiple invocations of .file() are allowed in order to provide the C++ library with much more code from various source files.
Another question was about the kind of library we want Rust to produce.
This documentation enumerates the various binary targets Rust can produce, especially various kinds of libraries.
Since in your original question you wanted the main executable to be on the C++ side, this means that Rust should produce a library which can be considered as a system library, not a Rust specific one, because Rust won't be involved anymore when generating the executable.
In the aforementioned documentation, we can see that only staticlib and cdylib are suitable for this usage.
In my examples, I chose staticlib for the sake of simplicity, but cdylib can be used too.
However, it is a bit more complicated because, as the main library (libcpprust.so) is generated as a dynamic one, Rust does not insert the C++ library (libcpp_from_rust.a) into it; thus, we have to link against this C++ library, which is not very convenient.
g++ -std=c++17 -o cpp_program src/main.cpp \
-I .. -I target/cxxbridge \
-L target/debug -l cpprust \
-L target/debug/build/cpprust-28371278e6cda5e2/out -l cpp_from_rust \
-pthread -l dl
And of course, because we are now dealing with a shared library, we have to find it at runtime.
$ LD_LIBRARY_PATH=target/debug ./cpp_program
I don't know whether some other kinds of libraries (crate-type) could work (by chance) with this C++ main program or not, but the documentation states that only staticlib and cdylib are intended for this usage.
Finally, note that if you use crate-type = ["staticlib", "rlib", "dylib"] in Cargo.toml (as stated in your comment), you will produce three libraries:
target/debug/libcpprust.a from staticlib,
target/debug/libcpprust.rlib from rlib,
target/debug/libcpprust.so from dylib.
Unfortunately, when linking with the command g++ ... -l cpprust ..., the linker will prefer the .so to the .a; you will be in the same situation as cdylib above.
The layout of the directory for the minimal example
cpprust
├── Cargo.toml
├── build.rs
└── src
├── lib.rs
└── main.cpp
Cargo.toml
[package]
name = "cpprust"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
[dependencies]
cxx = "1.0"
[build-dependencies]
cxx-build = "1.0"
build.rs
fn main() {
// This will consider the ffi part in lib.rs in order to
// generate lib.rs.h and lib.rs.cc
// minimal example: no C++ code to be called from Rust
cxx_build::bridge("src/lib.rs")
.compile("cpp_from_rust");
}
src/lib.rs
#[cxx::bridge]
mod ffi {
extern "Rust" {
fn rust_from_cpp() -> ();
}
}
pub fn rust_from_cpp() -> () {
println!("called rust_from_cpp()");
}
src/main.cpp
/*
Building this program happens outside of the cargo process.
We simply need to link against the Rust library and the
system libraries it depends upon
g++ -std=c++17 -o cpp_program src/main.cpp \
-I .. -I target/cxxbridge \
-L target/debug -l cpprust \
-pthread -l dl
*/
// consider the ffi part of Rust code
#include "cpprust/src/lib.rs.h"
#include <iostream>
int
main()
{
std::cout << "starting from C++\n";
rust_from_cpp();
std::cout << "finishing with C++\n";
return 0;
}
The cargo build command will generate the libcpprust.a static library in target/debug.
Building the main program simply relies on usual commands, provided that we find the relevant headers and libraries (see the comments in the code).
Note that the C++ source code for the main program is in the src directory here, but it could have been put anywhere else.
The layout of the directory for the bidirectional example
cpprust
├── Cargo.toml
├── build.rs
└── src
├── cpp_from_rust.cpp
├── cpp_from_rust.hpp
├── lib.rs
└── main.cpp
We just added a pair of .hpp/.cpp files.
build.rs
fn main() {
// This will consider the ffi part in lib.rs in order to
// generate lib.rs.h and lib.rs.cc
// The generated library (libcpp_from_rust.a) contains the code
// from cpp_from_rust.cpp and will be inserted into the generated
// Rust library (libcpprust.a).
cxx_build::bridge("src/lib.rs")
.file("src/cpp_from_rust.cpp")
.flag_if_supported("-std=c++17")
.compile("cpp_from_rust");
}
Note that this time the build process actually handles some C++ code (see below) to be called from Rust.
src/lib.rs
#[cxx::bridge]
mod ffi {
extern "Rust" {
fn rust_from_cpp() -> ();
}
unsafe extern "C++" {
include!("cpprust/src/cpp_from_rust.hpp");
fn cpp_from_rust() -> ();
}
}
pub fn rust_from_cpp() -> () {
println!("entering rust_from_cpp()");
ffi::cpp_from_rust();
println!("leaving rust_from_cpp()");
}
src/cpp_from_rust.hpp
#ifndef CPP_FROM_RUST_HPP
#define CPP_FROM_RUST_HPP
// declare a usual C++ function (no Rust involved here)
void
cpp_from_rust();
#endif // CPP_FROM_RUST_HPP
src/cpp_from_rust.cpp
#include "cpp_from_rust.hpp"
#include <iostream>
// define a usual C++ function (no Rust involved here)
void
cpp_from_rust()
{
std::cout << "called " << __func__ << "()\n";
}
Cargo.toml, src/main.cpp and the build process (cargo build, g++ ...) still are the same as in the previous example.

Related

How to correctly include and link a proprietary shared object library in C++

I have a linux c++ sdk that i need to integrate as a dependency into a .NET 6.0 bigger project.
This sdk is only conditionally included for the linux version of the software, i do already have the windows version running with no problems.
This skd exposes only classes in very nested namespaces, and as far as i'm aware, [DllImport] does not support class methods.
I do already know how to create a wrapper and expose functions with extern "C" and passing the sdk instances as pointers to a .NET Core application running with no problem under linux systems
My problem is that i do have no idea on how to succesfully compile/include/link the existing .so files into a .dll or .so that i can then [DllImport]
The sdk has been sent to me with the following structure (take name-of-lib as the placeholder name. I'm not sure if i can expose the real company name so i won't just in case):
run/
|- x86/
| +- {same files as x64 but i don't target x86 arcs so it's useless}
+- x64/
|- install.sh {see #1}
|- libNameModuleA.so.1.2.3
|- libNameModuleB.so.3.2.1
|- libNameApi.so.7.8.9
+- {etc. etc.}
lib/
|- lib-name-api/
| |- doc/
| | +- somehtmlautogenerateddocs.html
| |- include/
| | +- NameApi.h
| +- source/ {optionally for the top level apis}
| +- {varius header .h files}
|- name-of-lib-module-A/
| +- {same as previus, repeat for every .so file}
+- {etc. etc.}
{#1} script that craetes links as libName.so => libName.so.1 => libName.so.1.2 => etc
All of those library header files reference each other like they were in the same folder, using an #include "subLibraryModule.h" even if this file would actually be in something like ../../subLibrary/include/subLibraryModule.h.
Also, in the skd there are only headers.
now, what would be the (either, i don't really know waht would be the best tool) cmake, make, g++, gcc command, configuration or procedure to be able to include the top-level header that references all of the other ones in the compilation without a file not found error from the compiler?
Say i need to compile a single file wrapper.cpp that includes only the top level headers and contains only extern "C" functions
I've tried doing this with vscode and intellisense actually resolves the headers correctly, but when i try to compile it everything bursts into a fire of file-not-founds
Am I missing something? Is there further information or files i missed?
Thanks in advance.
So i figured it out.
makefile
The only problem here is that i did not know how to use a glob pattern to include stuff automatically (like -Ilib/*/include) but whatever.
CC := g++
SRC := wrapper.cpp
OBJ := wrapper.o
OUT := libWrapper.so
INCs := -Ilib/lib-name-api/include -Ilib/name-lib-module-A/include {etc. etc.}
LIB := -L/usr/local/lib/installedLibNameSos/ -lNameModuleA -lNameModuleB {etc. etc.}
all: $(OUT)
$(OUT): $(OBJ)
$(CC) $(OBJ) -shared -o $(OUT) $(LIB)
$(OBJ): $(SRC)
$(CC) -c $(SRC) -o $(OBJ) $(INCs) $(LIB)
wrapper.cpp
#include <stdio.h>
#include "NameApi.h"
// varius using namespace and other import stuff
extern "C" int test (){
return 42;
}
extern "C" string somethingReturningAString (){
// calls and stuff to the native library
return "Hi mom"
}
obviusly the .so needs to be copied where the executable is
csharpClass.cs
using System;
using System.Runtime.InteropServices;
// extension not needed.
// the library will be loaded along with the required .so's
// that have been linked from /usr/local/lib/installedLibNameSos/
[DllImport("libWrapper")]
extern static int test ();
[DllImport("libWrapper")]
extern static string somethingReturningAString ();
Console.WriteLine(test());
// 42
Console.WriteLine(somethingReturningAString());
// Hi mom
resources:
Link .so file to .cpp file via g++ compiling
Call C++ library in C#
https://en.wikipedia.org/wiki/Platform_Invocation_Services (p/Invoke examples)
https://learn.microsoft.com/en-us/dotnet/standard/native-interop/cross-platform
(note: the process works perfectly. But i've been using a library written and documented by a spastic monkey. So yeah i've literally learned the basics of g++, make and got crippling depression for nothing. lol)

Shared libraries and c++20 modules

There is very little documentation online on the proper use of C++20 modules in shared libraries. Many folks are clearly interested, but I haven't been able to find a clear solution.
In MSVC, you need to use dllexport when compiling the library, and dllimport when consuming the symbols. This can be done using macros in "legacy C++", but this does not work with C++20 modules, since the code is only compiled once, regardless of preprocessor directives.
This post suggests that you only need to use dllexport now, and that dllimport will be taken care of automatically by the compiler. However, this comes from a comment which has now been deleted, and I couldn't find any reliable source on the topic.
How is one expected to create a shared library using C++20 modules?
Background
A translation unit which declares a module interface or a module partition will be treated as a module unit and will, when compiled, generate both an object file and a binary module interface (BMI).
The BMI is a binary representation of an abstract syntax tree, that is a data structure representing the syntax and data types of the program. We have the traditional C++ compilation pipeline:
program -> precompiler -> lexer -> parser -> assembler -> linker
With GCC, we should add the compiler flag -c which tells the compiler to compile and assemble but not link.
But shared libraries are built by the linker by reading several compiled object files together and creating a shared object. So that happens after the BMI's have been built. And the BMI's may be built without linking them together as that is two different stages.
Module Visibility
In C# when building a DLL we have visibility attributes on class level, ie. public, private, internal. In C++ we can obtain the same functionality with module partitions.
A module partition, declared with module <module> : <partition>; will be entirely visible inside the compilation unit that declares export module <module>;, but not outside that module. This reminds me of internal mode from C#. But if we however export the partition with export module <module> : <partition>; then its declarations will be publicly visible. Read more on cppreference.
Example
I have solved that problem with GCC (g++-11), see here.
In essence, you don't need DLL import/export since there are (likely) no headers involved. I have tried inserting these visibility attributes but with complaints from my compiler, so I guess we might not need them after all. Other than that, it's standard procedure. I copy/paste my example here as well:
Main
import <iostream>;
import mathlib;
int main()
{
int a = 5;
int b = 6;
std::cout << "a = " << a << ", b = " << b << '\n';
std::cout << "a+b = " << mathlib::add(a, b) << '\n';
std::cout << "a-b = " << mathlib::sub(a, b) << '\n';
std::cout << "a*b = " << mathlib::mul(a, b) << '\n';
std::cout << "a/b = " << mathlib::div(a, b) << '\n';
return 0;
}
Library
export module mathlib;
export namespace mathlib
{
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
}
Makefile
GCC=g++-11 -std=c++20 -fmodules-ts
APP=app
build: std_headers mathlib main
std_headers:
$(GCC) -xc++-system-header iostream
mathlib: mathlib.cpp
$(GCC) -c $< -o $#.o
$(GCC) -shared $#.o -o libmathlib.so
main: main.cpp
$(GCC) $< -o $(APP) -Xlinker ./libmathlib.so
clean:
#rm -rf gcm.cache/
#rm -f *.o
#rm -f $(APP)
#rm -f *.so
Running
g++-11 -std=c++20 -fmodules-ts -xc++-system-header iostream
g++-11 -std=c++20 -fmodules-ts -c mathlib.cpp -o mathlib.o
g++-11 -std=c++20 -fmodules-ts -shared mathlib.o -o libmathlib.so
g++-11 -std=c++20 -fmodules-ts main.cpp -o app -Xlinker ./libmathlib.so
./app
a = 5, b = 6
a+b = 11
a-b = -1
a*b = 30
a/b = 0
Now this is clearly platform-specific, but the approach should work on other platforms. I have tested a similar thing with Clang as well (same repo as linked).
C++20 modules have no special relationship with shared libraries. They are primarily a replacement of header files.
This means that you would develop a shared library with C++20 modules in a similar fashion as you would with header files before C++20, at least with my current understanding. You design some API that is exported (unfortunately still using vendor-specific attributes like __declspec(dllexport) or __attribute__((visibility("default")))) and implement it. You build your shared library file (.dll/.so) and an import library for distribution, same way as before. However instead of distributing header files, you would distribute module interface units instead. Module interface units are files containing an export module ABC; declaration at the top.
And executables consuming that shared library would then import that module using import ABC;, instead of #include-ing a header file.
Edit: As was pointed out in the comments, it is seemingly still necessary on Windows to provide a macro switch inside the module interfaces that toggles between dllexport and dllimport attributes, similar to as it is done with headers. However, I have currently not experimented with this and can only defer to what #jeremyong has experimented with in What is the expected relation of C++ modules and dynamic linkage?.

Mixed programming - Including C++ header to Fortran

I am trying to use a function from library written in C++ in my program written in Fortran. The C++ library is summarized in one header file so that if you want to use it in another C++ program you only do #include functions.h I would like to find out how to do something similar in Fortran.
From my research I've created this minimal viable example:
clib/functions.h:
#ifndef ADD_H
#define ADD_H
extern "C"
{
int __stdcall add(int x, int y);
}
#endif
clib/functions.cpp:
extern "C"
{
int __stdcall add(int x, int y)
{
return x + y;
}
}
cinclude.c
#include "clib/functions.h"
cinterface.f95:
module cinterface
use,intrinsic::ISO_C_BINDING
integer(C_INT)::a,b
interface
integer(C_INT) function add(a,b) bind(C,name="add")
use,intrinsic::ISO_C_BINDING
implicit none
!GCC$ ATTRIBUTES STDCALL :: add
!DEC$ ATTRIBUTES STDCALL :: add
integer(C_INT), value ::a,b
end function add
end interface
end module cinterface
main.f90
program main
use cinterface
implicit none
integer :: c
c = add(1,2)
write(*,*) c
end program
makefile:
FC = gfortran
CC = g++
LD = gfortran
FFLAGS = -c -O2
CFLAGS = -c -O2
OBJ=main.o
DEP = \
cinterface.o cinclude.o
.SUFFIXES: .f90 .f95 .c .o
# default rule to make .o files from .f files
.f90.o : ; $(FC) $(FFLAGS) $*.f90 -o $*.o
.f95.o : ; $(FC) $(FFLAGS) $*.f95 -o $*.o
.c.o : ; $(CC) $(CFLAGS) $*.c -o $*.o
%.o: %.mod
#
main.ex: ${DEP} ${OBJ}
$(LD) ${DEP} ${OBJ} -o prog.exe
#
When I try to make this project using Cygwin I get a following error:
main.o:main.f90:(.text+0x13): undefined reference to `add'
main.o:main.f90:(.text+0x13): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `add'
collect2: error: ld returned 1 exit status
make: *** [makefile:19: main.ex] Error 1
How can I make add function in Fortran work?
You are most of the way there. There are two things you need to address to make this work: linkage and argument passing conventions.
Linkage
As francescalus noted, the Fortran compiler doesn't understand how to parse a C/C++ header file. So your functions.h and cinclude.c files aren't going to be of any use in this example.
Don't throw away your functions.h yet, though. In it you declare the add function as:
extern "C"
{
int __stdcall add(int x, int y);
}
The extern "C" is the important part. This tells g++ that the symbols in the following block of code aren't subject to all of the C++ name mangling. You'll need the same surrounding the add definition in functions.cpp.
extern "C"
{
int add(int x, int y)
{
return x + y;
}
}
Once you've done that all you will need to link are functions.o, cinterface.o/mod, and main.o.
Argument passing conventions
The way add is declared the arguments x and y are passed to the function by value. That is the default behavior for C/C++ function arguments. Fortran, on the other hand, defaults to passing arguments to functions/subroutines by reference. In C++ this would look like int add(int* x, int* y). There are two ways to address this problem.
The first option is to redefine your add function with integer pointers for the arguments and dereference them inside the function.
extern "C"
{
int add(int* x, int* y)
{
return *x + *y;
}
}
The second option (IMHO the preferred option) is to declare the Fortran interface to pass the arguments by value. They aren't being modified in the add function...why pass them by reference? If you choose this option, then your cinterface.f95 will need to contain the following declaration of add:
integer(C_INT) function add(a,b) bind(C,name="add")
use,intrinsic::ISO_C_BINDING
implicit none
integer(C_INT),value::a,b
end function add
Note the additional value decoration on the variables a and b. No matter which option you go with, without it on my machine I get 8393540 printed out as the result of the add function call. After addressing the argument passing conventions I get 3 printed out as expected.
A build system can simplify this significantly (albeit at the cost of introducing a complex build system). Assuming the directory layout in your question (although without the the cinclude.c since I don't see what purpose it serves)
$ tree
.
├── cinterface.f90
├── clib
│   ├── CMakeLists.txt
│   ├── functions.cpp
│   └── functions.h
├── CMakeLists.txt
└── main.f90
The contents of the The cmake files are
$ cat CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
project(cpp-add LANGUAGES C CXX Fortran)
add_subdirectory(clib)
add_executable(glue cinterface.f90 main.f90)
target_link_libraries(glue PUBLIC cpp-clib)
and
$ cat clib/CMakeLists.txt
add_library(cpp-clib functions.cpp)
The project can then be configured and built in the usual way:
$ cmake -H. -Bbuild && cmake --build build
Execution:
$ build/glue
3
I was having the same issue, can you show us the g++ compile line?
My issue was caused because my make file didn't properly include the appropriate .o file within the compilation for the .exe
i.e I had something like
Test: Test.cpp dependancy.o
g++ Test.cpp -o test.exe
and I was getting the same error returned that you did.
I solved it by ensuring the .o was actually used on the compile line.
Test: Test.cpp dependancy.o
g++ dependancy.o Test.cpp -o test.exe
I suggest this because the error "Undefined Symbol" typically means the compiler doesn't actually know where the code is for your call to the add function.

Use dub to output C++ linkable static library

I want to statically link my D library (which uses dub) with my C++ app.
I've followed the instructions on the wiki to successfully manually link the example.
But, I have my own library that uses dub, and I can't figure out how to make it output something I link to using cl.
Let me show you what I mean (example code from the wiki, but with dub added):
Project directory:
E:\Projects\foo
│ main.c
│
└───libadd
│ dub.json
│ libadd.lib
│
└───source
main.d
main.c:
#include <stdio.h>
// Defined in dlib.d
int add(int, int);
int main(int argc, char ** argv) {
int result = add(40, 2);
printf("The result is: %i\n", result);
return 0;
}
libadd/dub.json:
{
"name": "libadd",
"targetType": "staticLibrary",
"mainSourceFile": "libadd.d",
"buildOptions": [
"verbose"
]
}
libadd/source/libadd.d:
module libadd;
extern (C) int add(int a, int b) {
return a + b;
}
// Only needed on Linux.
extern (C) void _d_dso_registry() {}
Compiling and linking using instructions from the wiki works fine:
e:\Projects\foo> dmd -c -v -m32mscoff -betterC libadd/source/libadd.d
binary C:\opt\D\dmd2\windows\bin\dmd.exe
version v2.071.1
config C:\opt\D\dmd2\windows\bin\sc.ini
parse libadd
importall libadd
import object (C:\opt\D\dmd2\windows\bin\..\..\src\druntime\import\object.d)
semantic libadd
semantic2 libadd
semantic3 libadd
code libadd
function libadd.add
function libadd._d_dso_registry
e:\Projects\foo> cl /nologo /Fefoo.exe main.c libadd.obj
main.c
e:\Projects\foo> foo.exe
The result is: 42
But how do I do this with dub? I noticed that while manual compiling with dmd produces an .obj, dub produces an .lib. According to professor Google, .lib is a static library on Windows, but I can't link to it. I already set targetType to staticLibrary in dub.json.
I also noticed that the dmd flags -m32mscoff and -betterC have no corresponding buildOptions setting in dub.json. I'm not sure how to compensate, though.
e:\Projects\foo> cd libadd
e:\Projects\foo\libadd> dub
Performing "debug" build using dmd for x86.
libadd ~master: building configuration "library"...
binary C:\opt\D\dmd2\windows\bin\dmd.exe
version v2.071.1
config C:\opt\D\dmd2\windows\bin\sc.ini
parse libadd
importall libadd
import object (C:\opt\D\dmd2\windows\bin\..\..\src\druntime\import\object.d)
semantic libadd
semantic2 libadd
semantic3 libadd
code libadd
function libadd.add
function libadd._d_dso_registry
library .dub\build\library-debug-windows-x86-dmd_2071-2DA862E35C1BEDC80780CBC1AB5F7478\libadd.lib
Target is a library. Skipping execution.
e:\Projects\foo\libadd> cd ..
e:\Projects\foo> cl /nologo /Fefoo.exe main.c libadd/libadd.lib
main.c
libadd/libadd.lib : fatal error LNK1136: invalid or corrupt file
How do I statically link my D library that uses dub, with a C++ app?
After some trouble I figured it out.
It turns out, -m32mscoff is important, and it's required for 32-bit. Compiling and linking for 64-bit works fine as-is.
Add into dub.json:
"dflags-windows-x86-dmd": [
"-m32mscoff"
]
Even though dub passes -m32 to dmd, it's -m32mscoff that's needed. You can now link with cl as normal.

Can a Crystal library be statically linked to from C?

I've read through the "C bindings" in the tutorial but I'm a novice at C stuff.
Could someone please let me know if a Crystal program can be built as a static library to link to, and if so could you please provide a simple example?
Yes, but it is not recommended to do so. Crystal depends on a GC which makes it less desirable to produce shared (or static) libraries. Thus there are also no syntax level constructs to aid in the creation of such nor a simple compiler invocation to do so. The C bindings section in the documentation is about making libraries written in C available to Crystal programs.
Here's a simple example anyhow:
logger.cr
fun init = crystal_init : Void
# We need to initialize the GC
GC.init
# We need to invoke Crystal's "main" function, the one that initializes
# all constants and runs the top-level code (none in this case, but without
# constants like STDOUT and others the last line will crash).
# We pass 0 and null to argc and argv.
LibCrystalMain.__crystal_main(0, Pointer(Pointer(UInt8)).null)
end
fun log = crystal_log(text: UInt8*): Void
puts String.new(text)
end
logger.h
#ifndef _CRYSTAL_LOGGER_H
#define _CRYSTAL_LOGGER_H
void crystal_init(void);
void crystal_log(char* text);
#endif
main.c
#include "logger.h"
int main(void) {
crystal_init();
crystal_log("Hello world!");
}
We can create a shared library with
crystal build --single-module --link-flags="-shared" -o liblogger.so
Or a static library with
crystal build logger.cr --single-module --emit obj
rm logger # we're not interested in the executable
strip -N main logger.o # Drop duplicated main from the object file
ar rcs liblogger.a logger.o
Let's confirm our functions got included
nm liblogger.so | grep crystal_
nm liblogger.a | grep crystal_
Alright, time to compile our C program
# Folder where we can store either liblogger.so or liblogger.a but
# not both at the same time, so we can sure to use the right one
rm -rf lib
mkdir lib
cp liblogger.so lib
gcc main.c -o dynamic_main -Llib -llogger
LD_LIBRARY_PATH="lib" ./dynamic_main
Or the static version
# Folder where we can store either liblogger.so or liblogger.a but
# not both at the same time, so we can sure to use the right one
rm -rf lib
mkdir lib
cp liblogger.a lib
gcc main.c -o static_main -Llib -levent -ldl -lpcl -lpcre -lgc -llogger
./static_main
With much inspiration from https://gist.github.com/3bd3aadd71db206e828f