2009-06-19 3 views
1

У меня есть dll и файл библиотеки для использования Dll. В файлеИспользовать функцию из dll

TOnPortStatusChanged = procedure (cardNo:integer; portNo:integer; newPortStatus:byte); stdcall; 
    TOnPortDataReady = procedure (cardNo:integer; portNo:integer; dataBuff:PByteArray; buffLen:integer); stdcall; 

    T_API_INIT_PARAMS = record 
     OnPortStatusChanged :TOnPortStatusChanged; 
     OnPortDataReady :TOnPortDataReady ; 
    end; 
    P_API_INIT_PARAMS = ^T_API_INIT_PARAMS ; 
//.... 
//... 
var 
    PR_Init: function (initParams: P_API_INIT_PARAMS):integer; stdcall; 

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

procedure PortStatusChanged(cardNo: integer; portNo: integer; newPortStatus: byte); stdcall; 
begin 
    //... 
end; 

procedure TMainThread.CheckDevices; 
var 
    vCount: integer; 
    initParams: T_API_INIT_PARAMS; 
begin 
    initParams.OnPortStatusChanged := PortStatusChanged; 
    initParams.OnPortDataReady := nil; 
    vCount := PR_Init(@initParams); 

Это прекрасно работает. Но я хочу использовать процедуру PortStatusChanged в классе TMainThread. Поэтому я пишу процедуру в классе TMainThread и изменить тип TOnPortStatusChanged подобное:

TOnPortStatusChanged = procedure (cardNo:integer; portNo:integer; newPortStatus:byte) of object; stdcall; 

Когда я запускаю свою программу таким образом; PortStatusChanged работает правильно в первый раз, а во втором - ошибка нарушения доступа.

Где я принимаю ошибку?

ответ

3

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

Указатель метода (это то, что вы получаете при добавлении of object к объявлению) не совпадает с обычным указателем функции. Это действительно закрытие, состоящее из указателя на метод и ссылки на объект, метод которого он есть. Delphi знает, как вызвать указатели на метод, взяв ссылочный объект и вызвав метод point-to на этом объекте.

Ваша DLL не знает, как вызвать указатели на методы, если только это не было написано в Delphi или C++ Builder. Но даже если бы он знал, как вы застряли, так как DLL не знает, вы даете ему указатель на метод. Предполагается, что вы даете ему обычный указатель на функцию, поскольку именно так был написан код DLL.

Вы не можете предоставить DLL метод своего класса. Есть несколько методов, которые позволяют решить эту проблему косвенно, хотя:

  • Если определение обратного вызова DLL позволяет, вы можете передать дополнительный параметр в DLL, что DLL будет проходить обратно. Вы можете использовать его для хранения ссылки на свой объект, а затем в своей функции обратного вызова вы можете использовать объект. Однако это не похоже на поддержку этой DLL.
  • Вы можете поместить ссылку на свой объект в глобальную переменную, которую вы можете затем ссылаться в своей автономной функции обратного вызова. Это не очень элегантно, и техника разваливается, если вы можете когда-либо иметь несколько вызовов в DLL одновременно (либо через несколько потоков, либо через рекурсию).
  • Вы можете выделить свою собственную память для функции, а затем поместить ссылку на объект в код, который вы генерируете «на лету» для этого метода. Фактически, каждый экземпляр вашего класса, который может вызвать в DLL, будет иметь свою собственную функцию обратного вызова. Техника немного больше, чем я готов объяснить здесь. Есть некоторые проблемы, связанные с тем, что ваша программа выглядит подозрительной для антивирусных сканеров или компьютеров, на которых установлен флаг без выполнения, определенный в определенной памяти, но каждая программа VCL уже использует этот метод для связывания форм и элементов управления с их базовыми окнами Win32.
0

Если вы создаете многопоточное приложение, то ваш код может быть небезопасным. Кроме того, DLL, которую вы вызываете, может не быть потокобезопасной ...

1

Если вы получаете нарушения доступа, скорее всего, указатели функций не установлены (или не больше).

Часто бывает полезно проверить Назначение, прежде чем вы вызываете указатель на функцию, если вы на 100% не уверены в ее действии.

Например с утверждают:

Assert(Assigned(MyPtr)); 

К сожалению, это не проверяет оборванных указателей.

Существует большая разница между указателями функций и указателями метода (с расширением объекта). Указатели метода имеют скрытый дополнительный параметр (указатель на объект).

+0

Но я думаю, что DLL автоматически вызывает мою функцию. Итак, как я могу проверить указатель fucntion? – SimaWB

2

Директива «объекта» фактически инструктирует компьютер добавить указатель на экземпляр объекта в начало списка аргументов вызова процедуры. Это изменяет способ вызова процедуры, и DLL фактически получает список аргументов, которые сдвинуты, добавив объект pionter в начало и не будет работать.

+0

Итак; нельзя ли использовать процедуру в классе TMainThread? – SimaWB

+0

«Объект» означает, что он является частью экземпляра класса, поэтому для этого требуется указатель на функцию, а также объект, частью которого она является. Вы просто вызываете функцию, а не метод для объекта. –

+0

SimaWB: есть ли только один экземпляр TMainThread? Есть ли глобальная переменная, с которой вы можете получить доступ? Затем используйте глобальную процедуру, которая содержит только вызов метода экземпляра TMainThread с теми же значениями параметров. –

1

Простите очевидный вопрос, но обновили ли вы определение указателя на функцию в обоих местах?

Кроме того, в зависимости от того, что вы делаете с объектом, передача его через границы DLL может дать вам проблемы, если тот же менеджер памяти не владеет памятью как для приложения, так и для библиотеки DLL. Убедитесь, что вы делитесь им.

2

Rob Kennedy уже дал вам an answer с информацией о возможных причинах проблемы, которую вы испытываете, и о некоторых способах кодирования этого по-разному.

Существует, однако, еще один способ сделать это, что является простым и совместимым с другими средами разработки: интерфейсы. Рассмотрим следующий код:

type 
    IPortNotification = interface 
    ['{7BECA1D9-A6E8-4406-9910-5B36A6B0D564}'] 
    procedure StatusChanged(ACardNo, APortNo: integer; ANewPortStatus: byte); 
    procedure DataReceived(ACardNo, APortNo: integer; ADataPtr: PByte; 
     ADataLength: integer); 
    end; 

function RegisterPortNotification(ANotification: IPortNotification): BOOL; 
    stdcall; external PortDLL; 
function UnregisterPortNotification(ANotification: IPortNotification): BOOL; 
    stdcall; external PortDLL; 

DLL, только экспортирует две функции регистрации и отмены регистрации указателей интерфейса, и интерфейс реализует обработчики событий в коде непосредственно. Это делает DLL довольно универсальным, вы можете переписать его в любое время на другом языке, и вы можете использовать его и в других средах разработчиков. В Delphi нет ничего особенного, как обработчики событий в T_API_INIT_PARAMS.

Обратите внимание, что теперь вы можете реализовать многоадресные события почти бесплатно, зарегистрировав несколько указателей интерфейса.

Это также намного проще, чем расширение API. С записью в исходном коде вы не можете добавлять обратные вызовы, не нарушая совместимость с двоичными файлами. С помощью RegisterPortNotification() вы всегда можете запросить переданный интерфейс для новых расширенных интерфейсов и зарегистрировать их.

+0

Спасибо, mghie. У меня нет шансов изменить dll. – SimaWB

Смежные вопросы