1

I tried to compile and link the second example (see below), in the second FAQ in this link in isocpp.org.

Naturally, this works only for non-member functions. If you want to call member functions (incl. virtual functions) from C, you need to provide a simple wrapper. For example:

// C++ code:
class C {
    // ...
    virtual double f(int);
};
extern "C" double call_C_f(C* p, int i) // wrapper function
{
    return p->f(i);
}

Now C::f() can be used like this:

/* C code: */
double call_C_f(struct C* p, int i);
void ccc(struct C* p, int i)
{
    double d = call_C_f(p,i);
    /* ... */
}

After several trials, I succeeded executing the example in VS2015. But I'm still not convinced about the declaration extern "C" struct C *p = &c; that I had to use in other.cpp (I simply couldn't make the code to work with anything different than this). Note that the C++ compiler emits the following warning for the alluded declaration:

warning C4099: 'C': type name first seen using 'class' now seen using 'struct'

main.c was compiled with the C compiler and other.cpp with the C++ compiler.

main.c

/* C code: */

#include <stdio.h>
extern struct C *p;
double call_C_f(struct C* p, int i);

void ccc(struct C* p, int i)
{
    double d = call_C_f(p, i);
    printf("%f", d);

}

int main()
{
    ccc(p, 1);
}

other.cpp

// C++ code:
class C {
public:
    virtual double f(int i) { return i; };
} c;

extern "C" struct C *p = &c;    // This is the declaration that I'm concerned about
                                // Is it correct?

extern "C" double call_C_f(C* p, int i) // wrapper function
{
    return p->f(i);
}
Weather Vane
  • 33,872
  • 7
  • 36
  • 56
Belloc
  • 6,318
  • 3
  • 22
  • 52
  • If the C code doesn't directly manipulate the object, you could use a `void *` instead and cast it appropriately inside the wrapper functions. – Dmitri Sep 21 '15 at 20:50
  • Why are you using a global variable for this? – melpomene Sep 21 '15 at 20:53
  • The warning means what it says. You've defined it as a class but are referencing it as a struct. – Jonathan Potter Sep 21 '15 at 20:59
  • 1
    @JonathanPotter It is worth repeating each time this warning comes up that it is a bug in MSVC. – Marc Glisse Sep 21 '15 at 21:01
  • @JonathanPotter OK, but how should I implement the example avoiding the warning? That's basically my concern. – Belloc Sep 21 '15 at 21:01
  • Define C as a struct, not a class. – Jonathan Potter Sep 21 '15 at 21:02
  • @JonathanPotter So, what you're saying is that the example is wrong? – Belloc Sep 21 '15 at 21:03
  • Perhaps there's a reason they've done it that way that I'm missing, but structs and classes are not exactly the same thing so it seems wrong to me. In reality it doesn't cause any problems which is why VS just gives you a warning rather than an error. – Jonathan Potter Sep 21 '15 at 21:04
  • You could also disable the warning using the compiler options or a pragma if you just want to get rid of it. – Jonathan Potter Sep 21 '15 at 21:06
  • @JonathanPotter Or maybe there's another way of solving the problem, using `class` as stated in the Example. That's the answer I'm looking for. – Belloc Sep 21 '15 at 21:06
  • 1
    https://msdn.microsoft.com/en-us/library/2c8f766e.aspx e.g. `#pragma warning(disable: 4099)`. C doesn't have classes so either you have to use struct everywhere or disable the warning. I can't see a third option other than the `void*` option suggested by @Dmitri. – Jonathan Potter Sep 21 '15 at 21:09
  • @MarcGlisse is correct. This is at best a highly questionable compiler warning (enforcing a specific code style without making it clear that that is all it is), at worst (if MSVC actually makes `struct` and `class` incompatible in some special cases) proof of a conformance bug in MSVC. –  Sep 21 '15 at 21:40
  • IIRC, MSVC mangles names differently depending on whether `struct` or `class` is used. – T.C. Sep 21 '15 at 21:51
  • Discussed more here: https://stackoverflow.com/questions/4866425/mixing-class-and-struct – Jonathan Potter Sep 22 '15 at 02:27

1 Answers1

0

The line extern "C" struct C *p = &c is IMHO more confusing than really useful. In the same C++ compilation unit you first declare C to be a class, then a struct. As already said in comment with refs in that other question, declaring a class once with struct and once with class may lead to mangling issues in C++ code.

And it is useless, because as C is not a POD struct (it contains method and even a virtual one) it cannot be used from C as a struct, and in fact you only use a pointer to C from c code as an opaque pointer.

So the line should be written:

extern "C" class C *p = &c;

declaring to the compiler that you are defining a pointer to class C with extern linkage named p, pointing to c. Perfectly defined from C++ perspective.

Next from C point of view, you declare the extern pointer p, pointing to an undefined struct C. C will only allow you to use it almost a void pointer, meaning you can affect it and pass it to function, but p->xxx would cause an error because struct C is not fully declared in that compilation unit.

In fact the following code does exactly the same as yours without any warning:

main.c

/* C code: */

#include <stdio.h>
extern struct D *p;
double call_C_f(struct D* p, int i);

void ccc(struct D* p, int i)
{
    double d = call_C_f(p, i);
    printf("%f", d);

}

int main()
{
    ccc(p, 1);
    return 0; /* never return random value to environment */
}

other.cpp

// C++ code:
class C {
public:
    virtual double f(int i) { return i; };
} c;

extern "C" C *p = &c;

extern "C" double call_C_f(C* p, int i) // wrapper function
{
    return p->f(i);
}

The usage of struct D is not a typo. Of course I should have used struct C to avoid confusion for the reader, but it is not a problem for the compiler not for the linker: p in main.c is just a pointer to an opaque not fully declared struct. That's the reason why, it is common to declare such opaque pointers as void *.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • That was a very good answer, but I don't understand how does your code link? – Belloc Sep 22 '15 at 13:35
  • `p` is defined to be a pointer in other.cpp with external linkage, and is declared as extern in main.c. That is enough for the linker... at least for current linkers that only deal with addresses. That's why is could be safer to write `extern void *p` in main.c, because a pointer to T can be automatically converted to a `void *` and back in C language. – Serge Ballesta Sep 22 '15 at 13:42
  • `... at least for current linkers that only deal with addresses` But this doesn't link: **main.cpp:** `#include extern int* p; int main() { std::cout << *p << '\n'; }` **other.cpp:** `double x = 1.5; double *p = &x;` – Belloc Sep 22 '15 at 14:09
  • @Belloc: Because here you do not use `extern "C"` so C++ compiler defines *mangled* identifiers. My MSVC 2008 complains for an undefined symbol `"int * p" (?p@@3PAHA)`. But this one links: **main.cpp:** `#include extern "C" int* p; int main() { std::cout << *p << '\n'; }` **other.cpp:** `double x = 1.5; extern "C" double *p = &x;`, because both compilation units declare same symbol `_p`. – Serge Ballesta Sep 22 '15 at 14:33
  • Do you really mean `extern "C"` in both declarations, i.e., in `main.cpp` and `other.cpp`? – Belloc Sep 22 '15 at 16:45
  • @Belloc: yes because as both are cpp files and as I want to be sure what will be the symbol name, I force C linkage in both compilation units. – Serge Ballesta Sep 22 '15 at 17:30