Нарушение доступа при вызове внешней функции (C ++) из приложения Delphi - PullRequest
3 голосов
/ 10 ноября 2009

У меня есть внешняя DLL, написанная на C ++. В приведенном ниже фрагменте описывается тип struct и функция, которая, имея указатель, заполняет переменную этого типа:

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);
}

Я объявляю аналогичный тип в моем Delphi приложении и вызываю вышеуказанную функцию:

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
  ...

Когда вызывается функция, я получаю уведомление об исключении отладчика:

Проект ... возникла исключительная ситуация класса EAccessViolation с сообщением «Нарушение прав доступа по адресу 10103F68 в модуле« Scraper.dll ». Прочитайте адрес FFFFFFFC '. Процесс остановлен.

Другие функции, экспортированные из той же DLL, работают нормально, поэтому я предполагаю, что допустил ошибку в объявлении типа. Любые советы будут высоко оценены, так как я застрял в этой точке.

Ответы [ 3 ]

3 голосов
/ 10 ноября 2009

Первое, что вам нужно сделать, это убедиться, что ваши определения структуры совпадают. Если вы не используете 16-битный компилятор C ++, тип unsigned int определенно не является 16-битным, а тип Delphi Word - тем не менее. Вместо этого используйте Cardinal. Если у вас Delphi 2009 или более поздняя версия, тогда ваш тип Char является двухбайтовым; используйте AnsiChar вместо.

Даже с этими изменениями вы обречены. Ваш тип C ++ использует специфичный для Microsoft тип CString. Этого нет в Delphi или любом другом языке, отличном от Microsoft-C ++. Вы пытались использовать тип Delphi string вместо него, но они похожи только по именам. Их двоичное расположение в памяти совсем не одно и то же.

Ничего не поделаешь с этим определением структуры.

Если вы или кто-то еще в вашей организации являетесь автором этой DLL, то измените ее так, чтобы она выглядела больше, чем любая другая DLL, которую вы когда-либо использовали. Передайте указатели или массивы символов, а не классы. Если DLL от другой стороны, то попросите автора изменить ее для вас. Этот выбор API был безответственным и недальновидным.

Если вы не можете этого сделать, то вам придется написать DLL-оболочку на C ++, которая принимает структуру C ++ и преобразует ее в другую структуру, более дружественную к языкам не-C ++.

3 голосов
/ 10 ноября 2009

Основная проблема заключается в том, что C ++ CString и Delphi String являются несовместимыми типами.

Если вы хотите передавать данные таким образом, вам следует использовать либо символьные массивы фиксированной длины, либо строки с нулевым окончанием в стиле C (PChar в Delphi).

C ++ будет выглядеть примерно так:

char Dealer[100][10];

Пожалуйста, отредактируйте, если не так - прошло много лет с тех пор, как я написал C-код

Delphi

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

или

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

или при использовании C-строки (TCHAR в коде API)

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

Также обратите внимание, что String, Char (и, следовательно, PChar) изменились с одного байта на двухбайтовый (UCS 16) в Delphi 2009.

Другие типы данных также могут отличаться, например, В Delphi Word 16-битный, но может отличаться в C ++. Если возможно, используйте определенные типы, которые являются общими в Windows API, такие как USHORT вместо unsigned int и Word

2 голосов
/ 10 ноября 2009

Пока вы только читаете данные из DLL и не пытаетесь записать данные в нее, вы пытаетесь заменить CString на PAnsiChar (или PWideChar, если DLL была скомпилирована для Unicode), то есть:

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; 

С учетом вышесказанного сбой, который вы испытываете, скорее всего, является результатом неинициализированного указателя, который вы передаете в ScraperScrape (). Вам нужно изменить код Delphi для инициализации этой переменной, например:

...
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;

Лучше было бы вообще избавиться от переменной pCurState:

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

Лучше было бы вообще избавиться от псевдонима pSScraperState:

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

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