views:

1087

answers:

4

There's a function, written in C++ and compiled as DLL, which I want to use in my Delphi application.

Scraper.cpp:

SCRAPER_API bool ScraperGetWinList(SWin winList[100])
{
    iCurrWin=0;
    memset(winList,0,100 * sizeof(SWin));
    return EnumWindows(EnumProcTopLevelWindowList, (LPARAM) winList);
}

Scraper.h:

#ifdef SCRAPER_EXPORTS
#define SCRAPER_API __declspec(dllexport)
#else
#define SCRAPER_API __declspec(dllimport)
#endif

struct SWin
{
    char title[512];
    HWND hwnd;
};

extern "C" {
    SCRAPER_API bool ScraperGetWinList(SWin winList[100]);
}

This is how I declare the function in the Delphi application:

type
  tWin = record
    Title: Array [0..511] of Char;
    hWnd: HWND;
  end;

  tWinList = Array [0..99] of tWin;

function ScraperGetWinList(var WinList: tWinList): Boolean; stdcall; external 'Scraper.dll';

The function works, but when it's finished, I receive Debugger Fault Notification: Project ... faulted with message: ''access violation at 0x0012f773: write of address 0xffffffc0'. Process Stopped. Use Step or Run to continue.

If I add __stdcall (after SCRAPER_API bool) in Scraper.cpp and Scraper.h, then the Delphi application doesn't start at all: The procedure entry point ScraperGetWinList could not be located in the dynamic link library Scraper.dll.

+3  A: 

You need to put __stdcall after bool. The complete declaration, after all macros expand, should look like this:

extern "C"
{
    __declspec(dllexport)
    bool __stdcall ScraperGetWinList(SWin winList[100]);
}

EDIT: Looks like you'll also need a .def file there. It's a file that lists every function exported in the DLL, and in this case it's needed only to force C++ compiler not mangle the exported names. Contents would be this:

EXPORTS
ScraperGetWinList

I'm not sure which C++ compiler you're using, but normally you'd just specify the .def file along with .cpp; for example, the following works for VC++:

cl.exe foo.cpp foo.def

Also, you will need to tell Delphi to use stdcall as well, by inserting stdcall keyword right before external in your Delphi function declaration.

Pavel Minaev
I tried this and received an 'entry point not found' error upon running the Delphi application.
Mikhail
Did you add the __stdcall in the Delphi defintion of the external function as well?
MadKeithV
Yes: function ScraperGetWinList(var WinList: tWinList): Boolean; stdcall; external 'Scraper.dll';
Mikhail
Oh, did you by chance remove that `extern "C"` around when you added `__stdcall`? It needs to stay there, otherwise your function name will be mangled by C++ rules. `__stdcall` only makes sure the calling convention matches, but doesn't by itself disable name mangling...
Pavel Minaev
You will need __stdcall (or you will need `cdecl` in Delphi code) to make calling conventions match; that was the reason for your access violation. Now the idea is to get exported C++ function name to match with the one Delphi wants to see. I've updated the answer, that should finally do the trick.
Pavel Minaev
Changing stdcall to cdecl did the trick. Cheers!
Mikhail
A: 

Check that the definition of your Delphi function matches what you are declaring the C++ function as too. In particular, make sure that you have stdcall at the end, and that your bool values are going to be consistent. C++ and Delphi use different values and sizes for bool, depending on the C++ compiler, so it may be better to use an appropriately sized Integer. As the size of the bool may not match the C++ size, this can affect the stack, and thus cause access violations.

[edited to remove mixed language duff response]

mj2008
You're confusing your Delphi with your C++.
Rob Kennedy
Hmm, yes, you are right. I read that this was trying to get the Delphi side of things working. Will edit.
mj2008
A: 

If you use a packed array[1..512] of char you will not need the ConvertToString() function.

"packed array of char" is assignment compatible with Delphi string (this goes back to very early forms of Pascal - packed array of char WAS the string type). You nmay need to scab the result for a null ($0) char to find the end of the C-string

Also what Delphi version are you using? if Delphi 2009 + you will need to use packed array[1..512] of AnsiChar ;

Gerry
A: 

It would be good to know where exactly your access violation occurs. What variable/memory location is your runtime trying to access?

Then find out if this location should actually be accessible, and if so, why it's not.

My suspicion: you access an array element that isn't initialized correctly.

  Index := 0;
  S := ConvertToString(myWinList[Index].Title); 
  while S <> '' do
  begin
    WinListMemo.Lines.Add(S);
    Inc(Index);
    //////// Is Index pointing to a valid entry here?  No check!
    S := ConvertToString(myWinList[Index].Title);
  end;

Either

  • the dll does not initialize it correctly,
  • or there is another way to find out the last element.
  • or you simply ran off the array altogether: the 101th element is also dereferenced. and the 102nd, if that memory location happens to contain a 0 character.
xtofl
Access violation occurs at the very end. The last line of the procedure is ShowMessage, and I get the error message only after I click the OK button. I can even see the WinListMemo being populated with the correct results.
Mikhail