Linking Assembly and C/C++

Here's how you write an entire function in assembly.  The "global bar" keyword in assembly tells the assembler to make the function name "bar" visible from outside the file.

global bar
bar:
add rdi,1000
mov rax,rdi
ret

(Try this in NetRun now!)

The "Link With:" box (under "Options") tells NetRun to link together two different projects, in this case one in C++ and the other in assembly.  This C++ code calls the assembly here.

extern "C" int bar(int param);

int foo(void) {
return bar(6);
}

(Try this in NetRun now!)

You can call C++ code from assembly almost as easily, by making the C++ code extern "C", using "extern someName" in assembly, and then call the function normally.

Mixing Fortran and C++ Code

You can use the linker to combine compiled code from nearly any two compiled languages: C and C++, Assembly and C++, Fortran and C++, etc.

NetRun has the GNU Fortran 77 compiler, which takes code like this:

       FUNCTION foo()
       INTEGER foo
       foo = 2
       END FUNCTION

(Try this in NetRun now!)

We can write a function bar to call from C++ in exactly the same way:

       FUNCTION bar()
       INTEGER bar
       bar = 6
       END FUNCTION

(Try this in NetRun now!)

You can now call the code from C++ by just declaring it extern "C" (to disable C++ name mangling).  My Fortran compiler seems to add a trailing underscore to the function name too. 

extern "C" int bar_();

int foo(void) {
	return bar_();
}

(Try this in NetRun now!)

I've seen other compilers add leading underscores, double underscores fore and aft, names all UPPERCASE, etc.  Because different compilers have different naming conventions, I usually make a little macro to extract the correct case and add any underscores:

#define FORTNAME(UPPER,lower) lower##_

When a fortran function takes parameters, like this:

       FUNCTION bar(i)
       INTEGER bar
       INTEGER i
       bar = i + 6
       END FUNCTION

(Try this in NetRun now!)

The function parameter i, despite being declared as an INTEGER, is actually passed in as a pointer (by reference), like this:

#define FORTNAME(UPPER,lower) lower##_

extern "C" int FORTNAME(BAR,bar)(int *i);

int foo(void) {
	int i=1000;
	return FORTNAME(BAR,bar) (&i);
}

(Try this in NetRun now!)

You can pass a bare integer as a parameter, but the Fortran function will still treat it like a pointer, usually resulting in a crash. 

Mixed Assembly and C++ at the Command Line

The most portable way to include some assembly functions in your code is to compile the assembly in a separate file, then link it with the C++.  For example, in a file name "foo.S":

section .text
global _foo
_foo:
mov eax,7
ret

(Note the weird underscore in front of the function name--this is a Windows thing!)
You'd assemble this into "foo.obj" on windows with this command line:

    nasm -f win32 foo.S

Then in a file named "main.cpp", we call foo with an extern "C" prototype:

#include <iostream>
extern "C" int foo(void);

int main() {
std::cout<<"Foo returns "<<foo()<<"\n";
return 0;
}

We compile the C++ and link it to the assembly using the Microsoft Visual C++ compiler like this:

    cl -EHsc main.cpp foo.obj

(You may have to run "vc_vars.bat" to get "cl" into your PATH.)

We now have a functioning C++/Assembly executable!  The same exact command-line trick works on Linux or OS X with gcc.

If you don't like the command line, and few people do (except me!), you can integrate NASM with Visual C++ as I explain here.


CS 301 Lecture Note, 2014, Dr. Orion LawlorUAF Computer Science Department.