I am currently developing a C project under Linux and Win32. The \'deliverable\' is a shared library, and all the development is done under Linux with the GNU tool chain. I am u
I had a similar issue a few years back, and found that cmake is much easier for cross-platform compilation AND will use whatever compiler is native for that system. Syntax is clearer and abstracts details that are unnecessary for the most part (sometimes that got in the way, but usually there was a way around it)
I use UNAME := $(shell uname)
within my Makefile
to detect the platform (Linux or MS-Windows).
I provide below a complete example based on make
and gcc
to build a shared library: *.so
or *.dll
depending on the platform.
The example is basic/simple/stupid to be more understandable :-)
To use make
and gcc
on MS-Windows, Cygwin or MinGW can be installed.
The example uses five files:
├── app
│ └── Makefile
│ └── main.c
└── lib
└── Makefile
└── hello.h
└── hello.c
Makefiles
app/Makefile
app.exe: main.o
gcc -o $@ $^ -L../lib -lhello
# '-o $@' => output file => $@ = the target file (app.exe)
# ' $^' => no options => Link all depended files
# => $^ = main.o and other if any
# '-L../lib' => look for libraries in directory ../lib
# '-lhello => use shared library hello (libhello.so or hello.dll)
%.o: %.c
gcc -o $@ -c $< -I ../lib
# '-o $@' => output file => $@ = the target file (main.o)
# '-c $<' => COMPILE the first depended file (main.cpp)
# '-I ../lib' => look for headers (*.h) in directory ../lib
clean:
rm -f *.o *.so *.dll *.exe
lib/Makefile
UNAME := $(shell uname)
ifeq ($(UNAME), Linux)
TARGET = libhello.so
else
TARGET = hello.dll
endif
$(TARGET): hello.o
gcc -o $@ $^ -shared
# '-o $@' => output file => $@ = libhello.so or hello.dll
# ' $^' => no options => Link all depended files => $^ = hello.o
# '-shared' => generate shared library
%.o: %.c
gcc -o $@ -c $< -fPIC
# '-o $@' => output file => $@ = the target file (main.o)
# '-c $<' => compile the first depended file (main.cpp)
# '-fPIC' => Position-Independent Code (required for shared lib)
clean:
rm -f *.o *.so *.dll *.exe
app/main.c
#include "hello.h" //hello()
#include <stdio.h> //puts()
int main()
{
const char* str = hello();
puts(str);
}
lib/hello.h
#ifndef __HELLO_H__
#define __HELLO_H__
const char* hello();
#endif
lib/hello.c
#include "hello.h"
const char* hello()
{
return "hello";
}
Fix the copy-paste of Makefiles
(replace leading spaces by tabulation).
> sed -i 's/^ */\t/' */Makefile
The make
command is the same on both platforms. The given output is for MS-Windows (unnecessary lines removed).
> cd lib
> make clean
> make
gcc -o hello.o -c hello.c -fPIC
gcc -o hello.dll hello.o -shared
> cd ../app
> make clean
> make
gcc -o main.o -c main.c -I ../lib
gcc -o app.exe main.o -L../lib -lhello
The application requires to know where is the shared library.
On MS-Windows, the simple/basic/stupid way is to copy the library where the application is:
> cp -v lib/hello.dll app
`lib/hello.dll' -> `app/hello.dll'
On Linux, use the LD_LIBRARY_PATH
environment variable:
> export LD_LIBRARY_PATH=lib
The run command line and output are the same on both platforms:
> app/app.exe
hello
As somebody who has used both autotools and CMake, I would recommend using CMake over rolling your own Makefiles and using autotools. CMake has so many useful, easy to use benefits, even if it is a simple project. For example, CMake will create an NSIS installer, manage production vs. debug compilation and has a nice testing framework. The one knock I had was that it was kind of hard to find real examples of how to use it. So much open source software uses autotools that realworld examples for it are easy to find. However, if you download the CMake source, there are lots of examples in the Example directory and Test directory.
In other words, the Juice is worth the squeeze.
As a primary advice, I suggest using libtool, autoconf and automake; they make cross-compilation very easy, and much easier than CMake.
If you are going the hand-crafted route, I would suggest going with different targets. Switching between makefiles tends to hide otherwise obvious errors in Makefiles, e.g. duplicately used objects with different rules. Example: The object foo.o is compiled for the DLL target and for the .so target, but with different flags. If someone switches Makefiles, the existing .o file with wrong flags is used, breaking the build. If you are using one Makefile, this will become obvious through rule conflicts.
Use a single make file and put the platform-specifics in conditionals, eg
ifeq ($(OS),Windows_NT)
DLLEXT := .dll
else
DLLEXT := .so
endif
DLL := libfoo$(DLLEXT)
lib : $(DLL)
If you are willing to use MSYS2 on Windows you might get it to run without making any changes at all compared to your code written for Linux. This goes for your C/C++ source code as well as for your makefile.(!)
I have been developing code for Linux exclusively. When I tried running it inside an MSYS2 terminal, the code turned out to work just fine, and produced a Windows binary executable. I was positively surprised.
You will need to know how to install and use MSYS2, of course. For example, to install make and g++, in an MSYS2 terminal run the commands:
yes | pacman -Syu msys/make
yes | pacman -Syu gcc
If you want to find out where in Windows g++ has been installed, you can run
where g++
in the MSYS2 terminal.
References:
https://www.msys2.org/wiki/MSYS2-installation/
https://github.com/msys2/MSYS2-packages/issues/293