Re: using dot_product from c++ II
Couple of things.
First: Have a look at this, maybe it contains some info you will find
useful:
http://www.yolinux.com/TUTORIALS/LinuxTutorialMixingFortranAndC.html
Second: I thought it's about time I took a look at Fortran so I
downloaded g77 and did a little bit of research. I have to say, the
significance of leading whitespace on lines in Fortran kind of weirds
me out a little... but Fortran programmers also kind of weird me out a
little too, so I guess it all makes sense. However, I managed to get
it working and learned a few things.
Now, I'm not a Fortran programmer, so I don't know any tricks. Also I
was looking at a Fortran 77 tutorial the whole time, some of the
things you used in your original example weren't covered in that
tutorial, so I don't know about those (like "external", "kind-8", and
the "::" syntax). Please pardon any common gotcha's in my Fortran
code, it's the first Fortran program I've ever compiled.
I have pasted 3 files at the end of this message (Google groups
doesn't let me send attachments, sorry): hellof.f, is a fortran
program that initializes two real vectors and calls a C++ function to
compute the square root of their dot product. helloc.cpp contains the C
++ function. And there's a Makefile, too. I used G++ and G77. Here are
some of the things that I found out:
* It seems that by default Fortran passes parameters by "reference" --
that is if you call a function in your Fortran code with an integer
parameter, it passes a pointer to that integer to the function, so, in
Fortran:
function(4)
Would call this in C:
void function__ (int *value);
Where *value is 4. If you passed a variable to that function in
Fortran you could modify it's value in your C function if you want.
* For external functions in Fortran, it looks for the function name,
all lowercase, with two underscores on the end of it. So if you call
"SomeFunction" in Fortran then you will need to define
"somefunction__" in your other library. At least for me it appends a
few underscores. Also, Fortran expects C linkage, not mangled C++
names. So if you are using C you are cool, if you are using C++ you
must declare your C++ function as extern "C":
extern "C" double dot_product_sqrt__ (.......) { ..... }
Note the extern "C" and the double underscore.
* Where did you hear that Fortran would pass an array as a C++
std::vector? From what I can tell it simply passes a pointer to the
first element in the array. So if you have this in fortran:
real data(4)
function(data)
Then in C++ you can access it like this:
extern "C" int function__ (float *data) {
}
If you use "real*8" or "double precision" you'd use "double" instead
of "float" in your C++. Also note that in C++ the first element in the
array is index 0, this appears to be independent of the lower
dimension you declare on your Fortran array.
The few examples I looked at seemed to indicate that no information
about the size of the array is passed to external functions. You have
to specify the size yourself. So you'd do this in Fortran:
real data(4)
integer size
size = 4
function(data, size)
And then this in C++:
extern "C" int function__ (float *data, int *size) {
for (int i = 0; i < *size; ++ i) {
// do stuff to data[i] here
}
return 0;
}
* When compiling, compile your Fortran and C++ source files to object
files, but use G77 to link if you don't have a main() anywhere. I am
not sure what the exact rules are here; but at least in the example
files I've attached if you try to link with GCC you will, of course,
get an "undefined reference to main", since you have no main(). But if
you link with G77, then G77 does what it needs to do and compiles a
main() function from your Fortran code -- thus starting the program in
the Fortran code:
g++ -c cppfile.cpp
g77 -c ffile.f
g77 cppfile.o ffile.o -o program.exe
Next, some specific things in your last post:
On Feb 28, 6:16 pm, "Gerry Ford" <inva...@invalid.net> wrote:
I think I have configuration troubles with too many compilers on the
block.
This may be true. I can't help you with this right now if this is the
case; I'd have to think about it. But I did not have a problem using G+
+ and G77 -- although my C++ code did not have a main() in it.
[snip]
std::vector<double> vec_a;
[snip]
float *rp = &r;
[snip]
rp = & vec_a;
[snip]
The vec_a variable is an std::vector<double>. Taking it's address
yields an "std::vector<double> *". This is not the same as a "float
*". A float and an std::vector<double> are two entirely different
types, you can't do that conversion. An std::vector<T> is a complex
utility class that wraps a dynamically sizeable array of T's, a float
is just a primitive, scalar data type. Also, just FYI, "float" and
"double" are different types in C++ as well. Doubles are higher
precision (well technically that's not necessarily true, the C++
standard says doubles must be the same precision or higher as floats,
but they'll never be less). A "float" is like a "real" in Fortran and
a "double" is like a "real*8" (I guess, or "double precision") in
Fortran.
It is hard for me to "correct" your C++ mistake here since it's not
really something you should be doing, and I'm having trouble
interpreting what you meant. If you want to access the data in the
std::vector<double> as a pointer you *could* do this:
double *rp = &(vec_a[0]);
That is, assign rp to the address of the first element in vec_a.
But... that's not generally a good idea for a number of reasons that I
won't get into unless you ask, because it's not quite relevant to
getting your Fortran and C++ code working together, mostly because:
The biggest mistake I think you are making in general here is the use
of std::vectors in the first place. Like I said I'm not a Fortran
programmer so maybe some newfangled Fortran compiler also passes
things around as std::vectors -- but AFAIK you need to stick to plain
old float/double arrays, and you also need to pass the array
dimensions as separate function parameters (unlike using an
std::vector, which has a size() member function).
The above version has main in c++, which is going to be different from the
ultimate program that calls the function from elsewhere.
To avoid issues, you may want to remove that main() in the final
build, and use G77 to link (like I mentioned above), assuming that
your program starts in Fortran code.
vec_a.push_back( std::sqrt( static_cast<double>( i )) );
To pick nits: AFAIK std::sqrt is defined in the standard only for
'float' and 'long double'. Just use "sqrt", no "std::". Somebody
correct me if I'm wrong. This is been a minor point of confusion for
me lately, as well.
I don't have the
kinks worked out in herding the address of two vectors into the function.
It looks like I either need a radical cast, or I need to revise either
main's or the external function on what is being passed.
See the code at the end of this message. Then, using the new
dot_product_sqrt__ below, your tester code in main() would be
rewritten like this:
#include <cmath>
#include <iostream>
#include <iterator>
// dot_product_sqrt__ implementation (see below) goes here.
int main () {
double vec_a[4]; // fixed size arrays.
double vec_b[4];
int size;
for (int i = 0; i < 4; ++ i) {
vec_a[i] = sqrt((double)i);
vec_b[i] = i * i;
}
std::cout.precision(16);
// you can use std::copy with regular arrays too, pass start and end
// addresses of arrays:
std::copy(vec_a, vec_a + 4,
std::ostream_iterator<double>(std::cout, "\n"));
std::copy(vec_b, vec_b + 4,
std::ostream_iterator<double>(std::cout, "\n"));
size = 4; // stick it in 'size' to pass pointer to function.
std::cout << "Dot Product: " << dot_product_sqrt__(vec_a, vec_b,
&size) << "\n";
return 0;
}
Hopefully this is all relevant. Again, the Fortran you are using
appears to be a slightly different syntax than what I see in the
Fortran 77 tutorial, so I may be doing unnecessary or irrelevant
things...
Jason
-------- hellof.f --------
program callcpp
real*8 vec_a(4), vec_b(4), dp
integer size
do 1 i = 1, 4
vec_a(i) = i
vec_b(i) = i
1 continue
dp = dot_product_sqrt(vec_a, vec_b, 4)
write (*,*) dp
stop
end
-------- helloc.cpp --------
#include <cmath>
extern "C" double dot_product_sqrt__ (double *a, double *b, int *size)
{
// i removed NULL pointer checks here; but you can add them back in
if
// you want -- i've assumed fortran will never pass nulls.
// this function also assumes a and b are already the same size,
which
// is given in "*size". so that isn't checked either.
double sum = 0.0;
for (int i = 0; i < *size; ++ i)
sum += a[i] * b[i];
// p.s.: this does assume sum is positive!
return sqrt(sum);
}
-------- Makefile --------
# Provided mostly as an example of what I did to get it to compile.
..PHONY: all clean
..SUFFIXES: .cpp .f .o
all: hello.exe
clean:
rm -f *~ hello.exe helloc.o hellof.o
hello.exe: helloc.o hellof.o
g77 helloc.o hellof.o -o $@
..cpp.o:
g++ -c $<
..f.o:
g77 -c $<