views:

451

answers:

3

I've an external DLL written in C++. The piece below declares a struct type and a function, which, being given a pointer, fills a variable of this type:

enum LimitType { NoLimit, PotLimit, FixedLimit };

struct SScraperState
{
    char  title[512];
    unsigned int card_common[5];
    unsigned int card_player[10][2];
    unsigned int card_player_for_display[2];
    bool  dealer[10];
    bool  sitting_out[10];
    CString  seated[10];
    CString  active[10];
    CString  name[10];
    double  balance[10];
    bool  name_good_scrape[10];
    bool  balance_good_scrape[10];
    double  bet[10];
    double  pot[10];
    CString  button_state[10];
    CString  i86X_button_state[10];
    CString  i86_button_state;
    CString  button_label[10];
    double  sblind;
    double  bblind;
    double  bbet;
    double  ante;
    LimitType limit;
    double  handnumber;
    bool  istournament;
};

extern "C" {
    SCRAPER_API int ScraperScrape(HWND hwnd, SScraperState *state);
}

I declare a similar type in my Delphi application and call the above function:

interface

type
  LimitType = (NoLimit, PotLimit, FixedLimit);

  SScraperState = record
    title: Array [0..511] of Char;
    card_common: Array [0..4] of Word;
    card_player: Array [0..9, 0..1] of Word;
    card_player_for_display: Array [0..1] of Word;
    dealer: Array [0..9] of Boolean;
    sitting_out: Array [0..9] of Boolean;
    seated: Array [0..9] of String;
    active: Array [0..9] of String;
    name: Array [0..9] of String;
    balance: Array [0..9] of Double;
    name_good_scrape: Array [0..9] of Boolean;
    balance_good_scrape: Array [0..9] of Boolean;
    bet: Array [0..9] of Double;
    pot: Array [0..9] of Double;
    button_state: Array [0..9] of String;
    i86X_button_state: Array [0..9] of String;
    i86_button_state: String;
    button_label: Array [0..9] of String;
    sblind: Double;
    bblind: Double;
    bbet: Double;
    ante: Double;
    limit: LimitType;
    handnumber: Double;
    istournament: Boolean;
  end;

  pSScraperState = ^SScraperState;

function ScraperScrape(hWnd: HWND; State: pSScraperState): Integer; cdecl; external 'Scraper.dll';

implementation

var
  CurState: SScraperState;
  pCurState: pSScraperState;

  if ScraperScrape(hWnd, pCurState) = 0 then
  ...

When the function is called I get Debugger Exception Notification:

Project ... raised exception class EAccessViolation with message 'Access violation at address 10103F68 in module 'Scraper.dll'. Read of address FFFFFFFC'. Process stopped.

Other functions exported from the same DLL work fine, so my guess is I made a mistake in the type declaration. Any tips will be highly appreciated, as I'm dead stuck at this point.

+2  A: 

The main problem id that C++ CString and Delphi String are incompatible types.

If you want to pass data in this manner, you should use either fixed length character arrays or C-Style null terminated strings (PChar in Delphi).

C++ would be something like:

char Dealer[100][10];

Please edit if wrong - it been many years since I done any C coding

Delphi

Dealer : packed array[0..9, 0..99] of char;

or

type 
  TDealer = packed array[0..99] of char;
  ...
  Dealer : arry[0..9] of TDealer;

or if using C-string (TCHAR in API code)

Dealer: array[0..9] of PAnsiChar; // or PWideChar if source is UCS-16

Also note that String, Char (and hence PChar) changed from single byte to double byte (UCS 16) in Delphi 2009.

Other data types may be different as well e.g. In Delphi Word is 16bit, but may be different in C++. If possible use specific types that are common in the Windows API, such as USHORT instead of "unsigned int" and Word

Gerry
Yes, the problem seems to be caused by CString variables. I guess I'll have to request some changes to the C++ code. Thanks.
Mikhail
+3  A: 

The first thing you need to do is make sure your struct definitions are the same. Unless you're using a 16-bit C++ compiler, the type unsigned int is definitely not a 16-bit type, and yet Delphi's Word type is. Use Cardinal instead. If you have Delphi 2009 or later, then your Char type is a two-byte type; use AnsiChar instead.

Even with those changes, though, you're doomed. Your C++ type uses the Microsoft-specific CString type. There is no equivalent to that in Delphi or any other non-Microsoft-C++ language. You've attempted to use Delphi's string type in its place, but they are only similar in their names. Their binary layout in memory is not the same at all.

There is nothing you can with that struct definition.

If you or someone else in your organization is the author of that DLL, then change it to look more like every other DLL you've ever used. Pass character pointers or arrays, not any class type. If the DLL is from another party, then request the author to change it for you. That choice of API was irresponsible and short-sighted.

If you can't do that, then you'll have to write a wrapper DLL in C++ that takes the C++ struct and converts it to another struct that's more friendly to non-C++ languages.

Rob Kennedy
+2  A: 

As long as you only reading data from the DLL and not trying to write data to it, then you try replacing CString with PAnsiChar (or PWideChar if the DLL was compiled for Unicode), ie:

type
  LimitType = ( NoLimit, PotLimit, FixedLimit );

  SScraperState = record
    title: array[0..511] of AnsiChar;
    card_common: array[0..4] of Cardinal;
    card_player: array[0..9, 0..1] of Cardinal;
    card_player_for_display: array[0..1] of Cardinal;
    dealer: array[0..9] of Boolean;
    sitting_out: array[0..9] of Boolean;
    seated: array[0..9] of PAnsiChar;
    active: array[0..9] of PAnsiChar;
    name: array[0..9] of PAnsiChar;
    balance: array[0..9] of Double;
    name_good_scrape[0..9] of Boolean;
    balance_good_scrape[0..9] of Boolean;
    bet: array[0..9] of Double;
    pot: array[0.99]: Double;
    button_state: array[0.9] of PAnsiChar;
    i86X_button_state: array[0..9] of PAnsiChar;
    i86_button_state: PAnsiChar;
    button_label: array[0..9] of PAnsiChar;
    sblind: Double;
    bblind: Double;
    bbet: Double;
    ante: Double;
    limit: LimitType;
    handnumber: Double;
    istournament: Boolean; 
  end;

With that said, the crash you are experiencing is more likely a result of the uninitialized pointer that you are passing to ScraperScrape(). You need to change your Delphi code to initialize that variable, ie:

...
pSScraperState = ^SScraperState;       

function ScraperScrape(wnd: HWND; state: pSScraperState): Integer; cdecl; external 'Scraper.dll';  

...

var
  CurState: SScraperState;        
  pCurState: pSScraperState;        
begin        
  pCurState := @CurState;
  if ScraperScrape(hWnd, pCurState) = 0 then  
    ...
end;

Better would be to get rid of the pCurState variable altogether:

var
  CurState: SScraperState;        
begin        
  if ScraperScrape(hWnd, @CurState) = 0 then  
    ...
end;

Better would be to get rid of the pSScraperState alias altogether:

function ScraperScrape(wnd: HWND; var state: SScraperState): Integer; cdecl; external 'Scraper.dll';  

var
  CurState: SScraperState;        
begin        
  if ScraperScrape(hWnd, CurState) = 0 then  
    ...
end;
Remy Lebeau - TeamB
I've changed the type declaration as well as the declaration and calling of the function according to the above, but am still getting an access violation error message (though with different addresses now).
Mikhail