Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[split]The reason GCC doesn't work for us
#1
(21.08.2008, 17:40:57)DCoder Wrote: However, not all is peachy - the project is no longer compatible with GCC, only Microsoft Visual C++. To the best of my knowledge, GCC cooperation with MSVC on the level required is impossible.

I'd like to know more about what these GCC compatibility problems are.
Reply
#2
Quote:I'd like to know more about what these GCC compatibility problems are.
Put it simply, it's got to do with gcc producing a different binary layout of the classes involved, which means all virtual functions get directed to the wrong locations.

To go into more detail, the game and MSVC-compiled DLLs translate, for example, TechnoClass::GetType() to "call [edx + 84h]", but GCC translates it to "call [edx + A0h]" instead. For objects that were constructed inside GCC code, that breaks when you return control to the game, and on object pointers created by the game it breaks right off the bat. That different layout shifts the offsets for member variables as well, making their access also incompatible. I've googled for hours and I haven't seen any mention of a correction for this anywhere.
Reply
#3
(22.08.2008, 08:45:57)DCoder Wrote: Put it simply, it's got to do with gcc producing a different binary layout of the classes involved, which means all virtual functions get directed to the wrong locations.

To go into more detail, the game and MSVC-compiled DLLs translate, for example, TechnoClass::GetType() to "call [edx + 84h]", but GCC translates it to "call [edx + A0h]" instead. For objects that were constructed inside GCC code, that breaks when you return control to the game, and on object pointers created by the game it breaks right off the bat. That different layout shifts the offsets for member variables as well, making their access also incompatible. I've googled for hours and I haven't seen any mention of a correction for this anywhere.
Thank you for the concise detailed reply.

If I understand the situation correctly then pd compiled a .dll using Visual C which crated a .lib Import Library, but GCC has no idea how to use such .lib files.

Then when you compiled the exact same code using GCC to produce you're own .dll the GCC created its own .a Import Library. However, there is no standard in C++ on how everything is arranged/ordered in a binary. The GCC and Visual C Import Libraries map things differently because the .dll files they create are physically different and incompatible.

This isn't unusual. Read the secion Linking Against DLLs. Your wxDev-C++ IDE probably included the nm and dlltool programs needed to create a correct .a Import Library (which will need to be updated every time a class is added or modified.) As the article states make sure the VisualC .dll has not had its symbols stripped.

http://www.cygwin.com/cygwin-ug-net/dll.html

Here is another URL on some importing caveats on spotting calling conventions, though you probably already know about this stuff.
http://www.cygwin.com/ml/cygwin/2007-06/msg00696.html

I hope this helps. Let me know if the problem is somehow worse than this.

EDIT:
If it is worse then read this for another work-a-round.
http://www.cygwin.com/ml/cygwin/1997-06/msg00352.html
Also
http://www.cygwin.com/faq/
Reply
#4
Thank you for the interest and suggestions, but DLL linking is not the issue. The problem is that we're dealing with three types of vtables (the ones in the game, the ones in the DLL produced by MSVC, the ones in the DLL produced by GCC) and they all have to match perfectly. The first two match just fine given correct declarations since they're produced by the same compiler, but the GCC version does a lot of things differently.

The requirement for perfect match comes from the way vtables work in C++ - at compilation time, a table of pointers to virtual functions of that class is composed and a pointer to said table is included in each object of that class. When you call a virtual function on an object of that class or its derivates, the compiler uses its index (X) in this table and the generated assembly code instructs the cpu to access the vtable through the object's pointer and call the X'th function in it.

Now, when we extract a pointer to an instance of, say, InfantryClass, from the game, the vtable pointer points to the vtable the compiler composed and weaved into the game at compilation time. When we actually create an instance of InfantryClass in the DLL, the initial vtable pointer points to a table that was generated at DLL compilation time, but is immediately overwritten with the one from the game. But in both cases, the actual calls to the object's virtual functions we write in the DLL source are transformed to the offset calls by the DLL compiler who follows only its own vtable layout.

For things to work correctly, both the game and the DLL vtables have to point to the same functions in the same order. Unfortunately, MSVC uses one class composition model, GCC uses another one, documented at http://www.codesourcery.com/public/cxx-abi/abi.html and deceivingly titled "for Itanium". The two models are incompatible and classes will have largely different vtables. The longer the inheritance chain grows, the more different they will be. In my experience, GCC also adds/removes some additional data to the object itself, shifting the offsets of member variables too. I'm not certain whose composition model is better, gcc's or msvc's, but since the game dictates the rules, we have to go with msvc.

If you want a testcase, you can checkout the actual YRPP source from svn://renegadeprojects.com/yrpp/trunk and tell gcc to compile YRPP.h with -fdump-class-hierarchy , open the .class file it produces, search for "vtable for InfantryClass" and compare the list of function names with the list of virtual functions that were actually declared.

Administrative Notice:

Splitting off to a separate geeky thread.

Worth playing: 1 | 2 | 3
Reply
#5
Just adding a very short but precise description of the problem:

Different compilers generate virtual tables and their access differently, there is no standard defined that all compilers follow.
The GCC way to generate them is not compatible to the way MSVC does it and it cannot be altered.

We need 100% binary compability, which GCC cannot grant, because the game was compiled using MSVC.
[Image: jsfml.png]
Reply
#6
Thank you for letting me muck around with the code. To me it looks like the answer should look something like the code below.

From what I have read online the com_interface attribute was originally made to get GCC to make vtables compatible with COM objects. (With my compiler ("gcc (GCC) 3.4.2 (mingw-special)") com_interface is the default.) All that would be needed is replacing com_interface with the correct class attribute, if it exists. (That's why I said the answer should look something like the code below. The mechanism is there, but not necessarily the attribute you need, although my guess is the attribute already exists.)

Code:
#if defined(__GNUC__)
# define VC_VTABLE __attribute__((com_interface))
#else
# define VC_VTABLE
#endif

class InfantryClass {
  public:
  int SelectWeapon(TechnoClass *target);
  virtual void Update();
} VC_VTABLE;
EDIT:
I'm rambling a bit right now because I have dialup (the bad kind) and it will take me some time to get wxDev-C++, the YRPP source, and a svn client and don't want to forget to check this out. Sorry about that.
Reply
#7
I'm afraid that doesn't help:

D:\bin\gcc3\bin>mingw32-g++ a.cpp -fdump-class-hierarchy --save-temps -DWIN32 -D_WINDOWS -DUNICODE -D_REFGUID_DEFINED
a.cpp:4: warning: 'com_interface' is obsolete; g++ vtables are now COM-compatible by default

D:\bin\gcc4\bin>mingw32-g++ a.cpp -fdump-class-hierarchy --save-temps -DWIN32 -D_WINDOWS -DUNICODE -D_REFGUID_DEFINED
a.cpp:4: warning: 'com_interface' is obsolete; g++ vtables are now COM-compatible by default

The produced code is completely identical to the old version, and gcc documentation says:
/*
__attribute__((com_interface)) is obsolete in __GNUC__ >= 3
g++ vtables are now COM-compatible by default
*/

Thanks for your efforts, though Wink
Reply
#8
Initially I thought the GCC would attempt to be compatible similar to how how the GCC supports DirectX and COM, but it seems the incompatibility is intentional. From other things I have read this binary incompatibility is a C++ standards issue which I'm not thrilled about. gcc_fixes.h also has some astute comments about the situation.

http://www.cs.nyu.edu/courses/spring01/G...tml#TOC116
Quote:GNU C++ does not do name mangling in the same way as other C++
compilers. This means that object files compiled with one compiler
cannot be used with another.

This effect is intentional, to protect you from more subtle problems.
Compilers differ as to many internal details of C++ implementation,
including: how class instances are laid out, how multiple inheritance is
implemented, and how virtual function calls are handled.

Thank you for putting up with my efforts.
Reply




Users browsing this thread: 1 Guest(s)