Pointer corruption calling across a DLL boundary

N

Nick Bishop

I have a problem where I call a method in a C++ class with a pointer
which is a static member in that class. When I use a debugger, I see
the pointer having a certain value, but when I step into the method
(F11), the pointer has a different value and is not pointing at
anything valid.

The small program (complete source below) demonstrates what I am
trying to achieve. The small program works perfectly, but when I
incorporate this logic into a much larger program, I see the pointer
corruption happening. My workaround is that I use a file scope static
pointer declared with the same contents as the class static pointer,
in the style of the commented out workaround in the source code below.

This leaves some problems:
1. I can't reproduce the behaviour in a small program
2. I'm not in a position to put the Big Program source here, and
even if I could, you wouldn't read it
3. The differences between the small program and the Big Program are
a. ... "umm, the size".
b. the Big Program is coded as an older style Windows Service,
which I believe was migrated from Visual C++ ver 6.0
c. the Big Program version of "Nick" has a .DEF file, in debug
mode is generated dynamically, but in release mode is edited by hand,
putting in the mangled names of Nick::OneTwo and Nick::printTheList
4. There's SOMETHING about the Big Program that is screwing it up,
but what?

My question is what should I check for?

Nick Bishop
email replies ignored. Additional information below.

(Description of environment starts here)
I am using Visual Studio .NET, the original Enterprise Architect
version from 2002 (or rather it is using me).
This is being done on Windows 2000 Professional.
I created the solution (with TheConsole project) as a Win32 project,
and clicked Console Application in the wizard. I then added another
project (Nick) as a DLL project.

I therefore have one solution, and two projects, and a fully expanded
Solution Explorer for the Small Program looks like this:

Solution 'TheConsole' (2 projects)
- Nick
- Source Files
Nick.cpp
stdafx.cpp
- Header Files
Nick.h
stdafx.h
- Resource Files (with no files)
ReadMe.txt
- TheConsole (as startup project)
- Source Files
TheConsole.cpp
stdafx.cpp
- Header Files
stdafx.h
- Resource Files (with no files)
ReadMe.txt

(Source code starts here)
// ----- Nick.h -----------------------------------------------------
#ifndef NICK_H
#define NICK_H
#ifdef NICK_EXPORT
#define EXPORTTESTER_API __declspec(dllexport)
#else
#define EXPORTTESTER_API __declspec(dllimport)
#endif

class EXPORTTESTER_API Nick
{
public:
// This static pointer is intended to be a convenient, commonly used
list-of-strings
// that can be used by callers as a parameter to PrintTheList, but
they still have
// the freedom to supply their own list.
// I do NOT want to use the C++ default argument feature.
static const char* const OneTwo[];

// This is the (non-static) method.
void PrintTheList(const char* const* theList);
};

#endif

// ----- Nick.cpp ---------------------------------------------------
// Nick.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}

#define NICK_EXPORT
#include "Nick.h"
#include <iostream>

const char* const Nick::OneTwo[] =
{
"One", "Two", 0
};

void Nick::printTheList(const char* const* theList)
{
const char* iter;
int i = 0;
for (iter = theList[0]; iter; iter = theList)
{
std::cout << iter << std::endl;
++i;
}
std::cout << "Finished" << std::endl;
}

// ----- TheConsole.cpp ---------------------------------------------
// TheConsole.cpp : Defines the entry point for the console
application.
//

#include "stdafx.h"
#include "..\Nick\Nick.h"

//In the Big Program, the workaround is to declare a local copy of the
pointer.
//static const char* const myOneTwo[] =
// {
// "One", "Two", 0
// };


int _tmain(int argc, _TCHAR* argv[])
{
Nick n;
// Call the method with the "commonly used list"
n.PrintTheList(Nick::OneTwo);

// In the big program, the workaround is to use the local pointer
// n.PrintTheList(myOneTwo);
return 0;
}

// ----- TheConsole.cpp end -----------------------------------------
 
F

Feng Yuan [MSFT]

Make sure they are compiled using the same compile option. One thing could
be wrong is struct packing.

This kind of problem could normally be figured out easily by stepping throug
in assembly code.
 
N

Nick Bishop

Feng Yuan said:
Make sure they are compiled using the same compile option. One thing could
be wrong is struct packing.

This kind of problem could normally be figured out easily by stepping throug
in assembly code.

and we also got this answer.
There's no difference. Maybe the compiler or linker chokes on
exported array variables. Try char** instead of char*[]. May
I ask why they're doing this? The first thing I'd suggest
(strongly) is to turn it into a function call.

Creating .DEF files with mangled names could be an issue. I
wonder if something gets buggered up in the process.

One more thing. As a general rule I avoid C++ DLL interfaces
where practicable.

Thanks for the answers. If the top answer is true, I am surprised
that this is the first time we've discovered it in the life of this
project (about 2 years).

The last sentence of the bottom answer raises a point, but that is
more applicable if you are handing over a DLL to a separate group of
developers

Nick Bishop.
email replies ignored.
 
D

Doug Harrison [MVP]

Nick said:
Feng Yuan said:
Make sure they are compiled using the same compile option. One thing could
be wrong is struct packing.

This kind of problem could normally be figured out easily by stepping throug
in assembly code.

and we also got this answer.
There's no difference. Maybe the compiler or linker chokes on
exported array variables. Try char** instead of char*[].

Except in function parameter declarations, there's a huge difference between
char** and char*[], which respectively declare a pointer to a pointer and an
array of indeterminate size of pointers. In the array to pointer conversion,
char*[whatever] is converted to char**, so Ritchie thought it cool to allow
the array syntax when declaring function parameters. But in order to do
this:

//In the Big Program, the workaround is to declare a local copy of the
//pointer.
//static const char* const myOneTwo[] =
// {
// "One", "Two", 0
// };

The object myOneTwo must be an array. You can't just replace it with char**.
And the above is not a local copy of a pointer; it's an array, a duplication
of the entire array defined by your DLL.

I agree with Feng Yuan. You need to look at the disassembly of the call
point and determine exactly what is being passed to your function. In
addition, you should be linking every module you expect to share C++ objects
like this with the same CRT DLL.
 
N

Nick Bishop

Doug Harrison said:
//In the Big Program, the workaround is to declare a local copy of the
//pointer.
//static const char* const myOneTwo[] =
// {
// "One", "Two", 0
// };

... the above is not a local copy of a pointer;

Correct. I apologise for my loose use of the english language. The
correct thing to say would be something like
// ... the workaround is to declare a local copy of the ARRAY.

And I also understand that the strings are duplicated (ie stored
twice).
I agree with Feng Yuan. You need to look at the disassembly of the call
point and determine exactly what is being passed to your function. In
addition, you should be linking every module you expect to share C++ objects
like this with the same CRT DLL.

I will employ this strategy:
a) Trace the call in disassembly mode in the big program (where the
corruption occurs)
b) Trace the call in disassembly mode in the small program (which
works OK)
c) Trace an intra-DLL call in the big program (which probably works
- will advise)
d) Focus on the differences.

Question:
The Big Program uses a .DEF file, but the small program does not.
Is that likely to make a difference?

Put a watch on this, guys, coz it might take a week or two.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top