Вызов Delphi DLL из C ++ с использованием GetProcAddress: функция обратного вызова завершается с ошибочным параметром - PullRequest
4 голосов
/ 15 марта 2012

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

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

Моя проблема в том, что в функции обратного вызова невозможно оценить один из двух параметров (адрес 0x000001).

Вот объявления функции Pascal DLL

type
HANDLE = Pointer;       /// handle

(** This function Registers the callback function OnACLNeeded 
  *)
function RegisterCallback(
    h: HANDLE; 
    OnACLNeeded: MyCallbackFunc; 
    UserData: DWORD): Integer; stdcall;

Это Паскаль версия вызывающего приложения, функция обратного вызова. Оба параметра передаются по ссылке (var).

function TSNAPICongigF.OnACLNeeded(var keySettings, numOfKeys: Byte): Integer; stdcall;
begin
  keySettings:=$0F;
  numOfKeys:=1;
  Result:=0;
end;

Это моя C ++ версия функции обратного вызова

int __stdcall OnACLNeeded(byte& keySettings, byte& numOfKeys)
{
  keySettings = 0x0F;
  numOfKeys = 1;
  return 1;
}

Это мой код вызова C ++

int _tmain()
{
    HMODULE hLib = LoadLibrary(PASCAL_DLL);

    // DLL function pointer 
    typedef int (__stdcall *FnRegisterCallback)(HANDLE hKeyProvider,
        int (__stdcall *)(byte&, byte&),
        DWORD);

    FnRegisterCallback pfnRegisterCallback =
        (FnRegisterCallback)GetProcAddress(hLib, "RegisterCallback");

    // register my callback function
    int ret = (*pfnRegisteraCllback)(h, OnACLNeeded, (DWORD)1);
}

При работе в отладчике я достигаю точку останова в первой строке функции обратного вызова keySettings = 0x0F;
Я считаю, что numOfKeys является действительным, но keySettings имеет адрес 0x00000001 и не может быть назначен.
Приложение завершится с AccessViolation, если я продолжу.

int __stdcall OnACLNeeded(byte& keySettings, byte& numOfKeys)
{
    keySettings = 0x0F;

Я пытался объявить __cdecl безрезультатно.
Я попытался объявить байтовые параметры как байты *, и я получил тот же недопустимый параметр.

Я использую Visual Studio 2010, работающую на 64-разрядной версии Win7, но компилирующую как Win32.
Это мои команды компиляции и компоновки

/ZI /nologo /W3 /WX- /Od /Oy- /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /Gm /EHsc /RTC1 /GS /fp:precise /Zc:wchar_t /Zc:forScope /Fp"Debug\CallPascal.pch" /Fa"Debug\" /Fo"Debug\" /Fd"Debug\vc100.pdb" /Gd /analyze- /errorReport:queue 

/OUT:"...\Debug\CallPascal.exe" /INCREMENTAL /NOLOGO "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" /MANIFEST /ManifestFile:"Debug\CallPascal.exe.intermediate.manifest" /ALLOWISOLATION /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /DEBUG /PDB:"...\Debug\CallPascal.pdb" /SUBSYSTEM:CONSOLE /PGD:"...y\Debug\CallPascal.pgd" /TLBID:1 /DYNAMICBASE /NXCOMPAT /MACHINE:X86 /ERRORREPORT:QUEUE

Любые предложения с благодарностью приняты. Спасибо.

------ изменить -----

Я добавил структуру

struct Bodge {
  void* code;
  void* instance;
};

Bodge bodge;

bodge.code = OnACLNeeded;
bodge.instance = (void*)0x99; // just to test

Мой обратный вызов становится

Integer __stdcall OnACLNeeded(void* instance, Byte& keySettings, Byte& numOfKeys);

И мой вызов функции для регистрации обратного вызова становится

typedef Integer (__stdcall *FnRegisterCallback)(
    HANDLE,
    Bodge,
    DWORD);

и называется так

ret = (*pfnRegisterCallback)(
    h, 
    bodge, 
    (DWORD)1);

Этот вызов выдает ошибку

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

, что также может указывать на поврежденный стек, я думаю.
НО, если я проигнорирую ошибку и продолжу, я попаду в тело функции обратного вызова, и ОБА параметры теперь действительны! Так что успех такого рода, но также и параметр void* instance имеет значение ноль, а не 0x99, который я установил.
Я чувствую, что мы туда добираемся!

----- редактировать -----

Это вызов функции для регистрации обратного вызова из исходного кода вызова Паскаля.

  * @param hKeyProvider the key provider handle for Desfire card created previously with LASSeOKeyProvider_CreateHandle
  * @param OnACLNeeded supplies a callback to be called for quering host application for the PICC master key settings
  * @param UserData unsigned integer values specifiing any custom provided data to be returned when the callback is called
  * @return 0 - on success; <>0 - denotes error code

  RegisterCallback(hKeyProv,
                  @TSNAPICongigF.OnACLNeeded,
                  (Self));

Обратите внимание на ссылку на себя. Что будет эквивалентно С ++? (Я не пользуюсь классами)

1 Ответ

3 голосов
/ 15 марта 2012

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

Метод экземпляра Delphi передается как два указателя, один на код и один на экземпляр. Вы можете подделать это в C ++, объявив функцию RegisterCallback следующим образом:

typedef int (__stdcall *FnRegisterCallback)(
    HANDLE hKeyProvider,
    void* code,
    void* instance,
    DWORD
);

Затем, как только вы загрузите его с GetProcAddress, назовите его так:

int ret = (*pfnRegisterCallback)(h, OnACLNeeded, NULL, (DWORD)1);

Неважно, что это за параметр instance, так как мы собираемся игнорировать его, когда он передается OnACLNeeded.

Последний шаг - организовать, чтобы ваша функция работала как метод экземпляра Delphi. Сделайте это, добавив дополнительный параметр для представления экземпляра.

int __stdcall OnACLNeeded(void* instance, byte& keySettings, byte& numOfKeys)
{
    keySettings = 0x0F;
    numOfKeys = 1;
    return 1;
}

Вы получите в параметре instance все, что вы передали в качестве параметра instance при вызове RegisterCallback.

С этими изменениями вы сможете обмануть DLL-библиотеку Delphi, полагая, что ваш код является методом экземпляра Delphi!

Для справки предлагаю прочитать тему Program Control в Delphi Language Guide.

Наконец, удачи!


Обновление

Последнее редактирование вопроса проливает новый свет на происходящее. Конкретно этот код:

RegisterCallback(
    hKeyProv,
    @TSNAPICongigF.OnACLNeeded,
    DWORD(Self)
);

Параметр @TSNAPICongigF.OnACLNeeded приводит только к части кода метода. То есть это всего лишь один указатель. Экземпляр передается в следующем параметре с DWORD(Self). Предположение, сделанное автором этого кода, состоит в том, что функции обратного вызова будут переданы три параметра, пользовательские данные, за которыми следуют два байта переменной. Хитрость в том, что такая функция эквивалентна вызову метода, потому что вызов метода реализуется за сценой, передавая экземпляр как скрытый, неявный параметр перед фактическими параметрами.

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

int __stdcall OnACLNeeded(DWORD UserData, byte& keySettings, byte& numOfKeys)
{
    keySettings = 0x0F;
    numOfKeys = 1;
    return 1;
}

Теперь я уверен, что это сработает.

Вы вызываете функцию регистрации следующим образом:

int ret = (*pfnRegisteraCllback)(h, OnACLNeeded, (DWORD)1);

и ваша функция обратного вызова должна видеть значение 1 в параметре UserData.

...