Re: On runtimes, string wrapping and vs7 / vs8
On 14 Jul 2006 05:22:26 -0700, din.kompis@gmail.com wrote:
Hi all.
We have a dll with an api that use std::strings for a bunch of things.
It worked fine as long as you match the application & library runtimes.
Unfortunately, not all of our customers can match the runtimes due to
other restrictions such as other dll:s, mfc stuff etc. So we made an
effort to wrap the stuff we needed to wrap, ensuring that
heap-allocated things whould get allocated / deallocated in the same
heap. With VS2003, it worked like a charm. The developers were happy,
tech support was even more happy, and the customers were not
complaining as much as they used to.
Then came VS2005, and our wrapping solution doesn't work anymore. The
problems occur when you use a release-mode dll compiled with VS2003
with a debug-mode application compiled with VS2005. Now, I can imagine
that our solution really is pushing it a bit, but I'd love to get some
details on why it doesn't work.
The solution relies on that the inline functions of the library objects
gets compiled with the application code, and the objects will have some
functions compiled with VS2003 and some with VS2005. My guess would be
that there are some differences in object layout between VS2003 and
debug mode VS2005, since the errors we get are heap corruptions and
they can be triggered by using inlined setters on other object members.
That is, I can get a heap corruption from a string destructor in an
object without ever touching that string, but having touched other
members in that object. In the below sample, I'd get this from the
destructor on SampleLibClass::stringThing after a testcase that only
use the nonWrappedSetter.
So, a sketch of our solution (API_DECL is the standard dllimport /
dllexport macro):
// A wrapper for std::string
class StringH {
public:
// application side interface
API_DECL StringH();
API_DECL ~StringH();
API_DECL const char* c_str() const;
API_DECL count_t length() const;
// library side interface
void init(const char *str);
void init(const std::string& str);
private:
// defined in the cpp-file as:
// struct StringH::Impl : public string { ... }
struct Impl;
Impl* pImpl;
StringH(const StringH&) {}
};
// this is how we use it in the library
class SampleLibClass {
public:
// ... some stuff ...
void setStringThing(const std::string &str) {
setStringThing_(str.c_str());
}
const std::string getStringThing() const {
StringH sh;
getStringThing_(sh);
return std::string(sh.c_str());
}
API_DECL int nonWrappedSetter(int prm) { nonWrappedInt = prm; }
private:
API_DECL void setStringThing_(const char *cstr);
API_DECL void getStringThing_(StringH &sh) const;
std::string stringThing;
int nonWrappedInt;
// ... more stuff ...
}
So... Does the object layout differ? Is this an ok-ish solution, or are
we violating a bunch of things and we were simply lucky that it worked
with VS2003 and the different runtimes there? Would it work if we
removed all inlined non-wrapped setters and getters, moving their
implementations to the sourcefile instead?
Any hints appreciated! Our other option would be to scrap the strings,
and vector<string> that we use and use const char* and const char*
arrays instead, perhaps combined with a application side only wrapper
that gets fully compiled with the application compiler.
You were lucky. What you're doing, sharing C++ objects between Windows
modules, i.e. EXEs and DLLs, cannot work even semi-reliably unless:
1. You do not violate the ODR, the "one definition rule", which is actually
a C++ rule independent of compiler choice. This rule requires a given class
to have the same layout everywhere in the program. This can be guaranteed
in VC++ only if all modules use the same compiler and library versions, and
the libraries and library users are compiled with the same relevant
options.
2. You link all modules to the same CRT DLL, which eliminates the problem
of separate CRT state.
Getting rid of your inline functions, and this includes any potentially
compiler-generated functions like default ctor, copy ctor, dtor, and
assignment operator, may indeed help, but the end result will remain a
problem waiting to happen. The only sane way to view this situation is as
the equivalent to working with static libraries for dependency purposes.
--
Doug Harrison
Visual C++ MVP