David Bolton wrote:
> I've been writing a little test harness to test C and C++ dlls. It's for
> this btw.
> http://cplus.about.com/od/programmingchallenges/a/onchallenge1.htm
>
> The Test harness for the C and C++ dlls was written in Delphi 5. It works
> ok
> except it kept crashing quite badly when I called a function which passed
> the address of a record in so the called function can access the record
> data. I verified that the record structure size was identical in Delphi
> and
> the dll.
>
> The call worked fine, but when the delphi function which encapsulates the
> call to the dll function exited, it bombed out. Ie I had done naughty
> things
> to the stack. The calling convention was stdcall in the Delphi app, the
> default (winapi in Microsoft parlance) in the C/C++ dll which equates to
> stdcall on Windows XP.

The default calling convention in C compilers is not stdcall. Please check
to make sure you really do use the WINAPI macro when you declare the C
function.

> I was doing this using loadlibrary btw. Ie select a dll, then use
> GetProcAddress and call it.
>
> type
> TPlayerHistory = packed record // 104 bytes in size- same as in dll
>   MoveCount : integer;
>   Moves     : array[1..100] of char;
> end;
> TmyfuncMove = function(var p : TPlayerHistory):integer;stdcall;
>
> var
> dllhandle : THandle;
> pdllpathname : pchar;
> dllpathname:string;
> s : string;
> fm : TMyFuncMove;
> p : Pchar;
> history : TPlayerHistory;

You would be wise to not use global variables. Declare them only in the
routines that first need them, and pass them as parameters to other
routines. As it is now, your code is rather hard to follow, and it's just
two routines!

> This is the function that bombs out.
>
> function GetMove:string;
> var b : byte;  // <- fixes the bug, crashes if this unused variable is
> removed!

It doesn't fix the bug. It removes the symptom of the problem.

> begin
>   result :='Failed to locate entry point GetMove';
>   try
>     fm := GetProcAddress(dllhandle,'GetMove');
>   except
>     fm := nil;
>   end;

GetProcAddress never raises an exception, so there's nothing to catch.

>   if not assigned(fm)then
>     exit
>   else
>      begin
>        try
>           result := Inttostr(fm(History));  // <- Calls the function here
>        except
>          on e:exception do
>            begin
>              ShowMessage(E.Message);
>            end;
>          end;
>      end;
> end;  // <- crashes here

Yep. The stack is trashed.

> It's called like this
>
> procedure TForm1.btnStartClick(Sender: TObject);
> var i : integer;
> begin
>   dllhandle := 0;
>   try
>     pdllpathname := pchar(dllpathname);
>     dllhandle := LoadLibrary(pdllpathname);
>     fillchar(history.moves,sizeof(history.moves),' ');
>     for i:= 1 to 10 do
>       begin
>         History.MoveCount := i-1;
>         if i >1 then
>           History.Moves[i-1] := 'R';
>         ListBox1.Items.Add('Move '+ Inttostr(i)+' = '+ GetMove);
>       end;
>   finally
>     FreeLibrary( dllhandle );
>   end;
> end;
>
> I solved it and I think it is a bug in Delphi 5.

That's awfully presumptuous of you, isn't it? How do you know it isn't a
bug in your chosen C compiler? How do you know it isn't _your_ mistake?

> If there is no local
> variable declared in GetMove, I don't think it saves the stack correctly

There's really nothing to save. With stdcall, the called function is
supposed to restore the stack.

What if there are many local variables -- enough to occupy more than four
bytes on the stack?

> (I
> had optimise turned off btw) and so crashes. It also crashes if
> optimization
> is turned on, I presume because it zaps the local variable as it is
> unused.
>
> At first I thought it was a calling convention mismatch but I proved that
> it
> wasn't, by adding two dummy parameters and checking that it could still
> access the History record in the dll.

Huh?

Adding two more parameters after the history parameter would change
nothing. Stdcall and cdecl pass their parameters in the same order, right
to left, so either one would have the history parameter at the top of the
stack, no matter how many other parameters were passed or declared.

Adding parameters _before_ the history parameter would only have an effect
if you made the change to only the DLL or only the EXE, but not both.

> So am I right that it is a bug or missing something else?

You're missing something else.

Use the CPU window to check whether the stack pointer is correct before
and after the call. Start by making it easier to debug by putting the
function call in its own statement:

try
  x := fn(history);
except
  on E: Exception do begin
    ShowMessage(E.Message);
    exit;
  end;
end;
Result := IntToStr(x);

Now put a breakpoint on the call line. When your program reaches that
point, activate the CPU window (Ctrl+Alt+C, I think). You should see code
like the following. (I don't have a Delphi compiler right now, so this is
just a guess.)

lea eax, history
push eax
mov eax, fn
call eax

That is, something to load the address of your history variable and push
it on the stack, and then one or two more instructions to call the
function. You might only see addresses instead of variable names. (The
try-except statement might make the CPU view harder to read. Try
commenting that part out -- ignore the possibility of exceptions -- while
doing this investigation.)

Note the current value of ESP, the stack pointer. Now use the "step over"
command four times so the execution point is right after the "call"
instruction. The value of ESP should be the same as it was before. If it's
not, then your function didn't do its job in cleaning up the stack.

And so you can be certain that it's not a problem with any of your C code,
your DLL function should be nothing more than this:

int WINAPI fn(TPlayerHistory*) {
  return 0;
}

-- 
Rob


Reply via email to