Как экспортировать функции перегрузки из DLL? - PullRequest
4 голосов
/ 21 декабря 2011

Delphi Xe.

В модуле Windows.pas я вижу один из способов:

function InterlockedExchangeAdd(Addend: PLongint; Value: Longint): Longint stdcall; overload;
{$EXTERNALSYM InterlockedExchangeAdd}
function InterlockedExchangeAdd(var Addend: Longint; Value: Longint): Longint stdcall; overload;
{$EXTERNALSYM InterlockedExchangeAdd}
...
function InterlockedExchangeAdd(Addend: PLongint; Value: Longint): Longint; external kernel32 name 'InterlockedExchangeAdd';
function InterlockedExchangeAdd(var Addend: Longint; Value: Longint): Longint; external kernel32 name 'InterlockedExchangeAdd';

Значит, DLL может экспортировать функции с одинаковыми именами.

Я пытаюсь повторить:

Я создаю проект

Program TestMyDll;

{$APPTYPE CONSOLE}

uses SimpleShareMem, SysUtils;

Function MyFunc(const X:Integer):string; StdCall; External 'MyDll.dll' Name 'MyFunc'; Overload;
Function MyFunc(const X:Extended):string; StdCall; External 'MyDll.dll' Name 'MyFunc'; Overload;

begin
  try
  Writeln;
  Writeln('MyDll test');
  Writeln('Int: ' + MyFunc(10));
  Writeln('Real: ' + MyFunc(10.55));
  Readln;
  except on E: Exception do Writeln(E.ClassName, ' : ', E.Message);end;
end.

Он компилируется нормально. Далее создаю DLL:

Library MyDll;

uses
  SimpleShareMem,
  DllUnit1 in 'DllUnit1.pas';

{$R *.res}

begin
//test
MyFunc(10);MyFunc(10.55);
end.

... и модуль DllUnit1.pas

Unit DllUnit1; Interface

Function MyFunc(const X:Integer):string; Overload; StdCall;
Function MyFunc(const X: Extended):string; Overload; StdCall;

Exports
MyFunc; // COMPILE ERROR

Implementation

Uses SysUtils;

Function MyFunc(const X:Integer):string;
begin
result:=Inttostr(x);
end;

Function MyFunc(const X: Extended):string;
begin
result:=Floattostr(x);
end;

end.

Но при компиляции я получаю сообщение об ошибке: [Ошибка DCC] DllUnit1.pas (7): E2273 Не существует перегруженной версии MyFunc с этим списком параметров .

В справке Delphi я вижу:

"Delphi Language Reference"/"The exports clause"
...
When you export an overloaded function or procedure from a dynamically loadable library, you must specify its parameter list in the exports clause. For example,

exports
 Divide(X, Y: Integer) name 'Divide_Ints',
 Divide(X, Y: Real) name 'Divide_Reals';

On Windows, do not include index specifiers in entries for overloaded routines.

Вопросы:

  1. Как правильно экспортировать эти функции в модуль DllUnit1 и можно ли вообще сделать это в Delphi (экспорт под одним именем), чтобы получить тот же вызов из моего проекта TestMyDll, что и в начале (пример из windows.pas)?

  2. Если такие функции можно экспортировать под одним именем, то будет ли правильно работать при вызове DLL из других языков (VB, C ++)? Или лучше сделать две функции с разными именами?

P.S. Немного похожий вопрос нашел здесь (/6594412/kak-sovmestit-peregruzku-i-stdcall-v-delphi), но ответ меня не устроил

P.S.S. Плохой английский


ДОБАВИТЬ (добавил после ответов)

Понятно, спасибо.

сделал так:

В проекте:

Function MyFunc (const X:Integer):string; StdCall; External 'MyDll.dll' Name 'MyFunc'; Overload;
Function MyFunc (const X:Extended):string; StdCall; External 'MyDll.dll' Name ' MyFunc1'; Overload;

In DllUnit1

Exports
MyFunc (const X:Integer) Name 'MyFunc',
MyFunc (const X:Extended) Name 'MyFunc1';

Он скомпилирован и работает нормально.

Еще вопросы:

  1. вроде работает, но правильно ли это?

  2. Имеет ли значение, как написать "Функция MyFunc (const X: Integer): string; Overload; StdCall;" или "Функция MyFunc (const X: Integer): строка; StdCall; Перегрузка;"?

  3. Эта функция в проекте других языков (Vb, C ++, C #) будет вызываться правильно?

Ответы [ 3 ]

11 голосов
/ 21 декабря 2011

Значит, DLL может экспортировать функции с одинаковыми именами.

Нет, это не так. Delphi объявляет 2 перегрузки InterlockedExchangeAdd() с другими параметрами, но kernel32.dll экспортирует только одну функцию InterlockedExchangeAdd(). Два объявления Delphi импортируют одну и ту же функцию DLL. Перегруженные параметры эквивалентны при вызове функции во время выполнения. Другими словами, Addend: PLongint и var Addend: Longint идентичны в том, что касается функции. Во время выполнения они оба являются указателями на Longint.

В первом объявлении используется синтаксис в стиле C для передачи параметра Addend по явному указателю:

var
  Value, Ret: Longint;
begin
  Ret := InterlockedExchangeAdd(@Value, 1); 
end;

Во втором объявлении используется синтаксис в стиле Delphi для передачи параметра Addend по ссылке:

var
  Value, Ret: Longint;
begin
  Ret := InterlockedExchangeAdd(Value, 1); 
end;

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

Мне никогда не приходилось делать это в моих DLL, но я также никогда не экспортировал перегрузки. Задание параметров позволяет компилятору различать, какой экспорт использует какую перегрузку, но, как показывает пример, эти перегрузки экспортируются под разными именами, хотя они используют одно и то же имя в кодировке DLL.

лучше сделать две функции с разными именами? **

Да.

5 голосов
/ 21 декабря 2011

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

Ваш пример с InterlockedExchangeAdd - это просто две функции с разными, но эквивалентными сигнатурами, относящимися к одной и той же функции.Это сделано для удобства звонящего.

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

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

Библиотека, которая экспортирует функции

library liba;

procedure F(X: Integer); stdcall; overload;
begin
end;

procedure F(X, Y: Integer); stdcall; overload;
begin
end;

exports
  F(X: Integer) name 'F1',
  F(X, Y: Integer) name 'F2';

begin
end.

Библиотека, которая импортирует функции

library libb;

procedure F(X: Integer); stdcall; overload; external 'liba.dll' name 'F1';
procedure F(X, Y: Integer); stdcall; overload; external 'liba.dll' name 'F2';

begin
end.

Ключевое слово overload может появляться в любом месте объявления.Неважно, где это появляется.С другой стороны, соглашение о вызовах должно появиться до external.

Обратите внимание, что языки, которые не поддерживают перегрузку (например, VB6, C), очевидно, не смогут импортировать функции и использовать те же именадля них.Аналогично для языков, которые не поддерживают переименование функции при импорте (например, C ++).Насколько я знаю, только Delphi допускает такие аккуратные приемы во время импорта.

Для таких языков, как C ++ и C #, которые поддерживают перегрузку, вам потребуется ввести еще один уровень косвенности.Например, в C # вы должны сделать это:

[DllImport("liba.dll")]
private static extern void F1(int X);

[DllImport("liba.dll")]
private static extern void F2(int X, int Y);

public static void F(int X)
{
    F1(X);
}

public static void F(int X, int Y)
{
    F2(X, Y);
}

Точно такой же подход может быть использован в C ++.Единственная реальная разница между этим подходом и кодом Delphi, который я показал выше, заключается в том, что язык Delphi поддерживает прямой синтаксис для выполнения этого отображения.


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

4 голосов
/ 21 декабря 2011

Нет, вы ошибаетесь.Функции в Windows .dll все C-callable - они не перегружены.

Вот правильный прототип для InterlockedExchangeAdd:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms683590%28v=vs.85%29.aspx

LONG __cdecl InterlockedExchange(
  __inout  LONG volatile *Target,
  __in     LONG Value
);

Синтаксис в Windows.pas позволяет вам передавать "long int" или "указатель на long int".C и C ++ с удовольствием позволят вам сделать то же самое.Но вызываемая функция - это то же самое , в любом случае.

'Надежда, которая помогает

...