Как экспортировать функции перегрузки из DLL?
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 Error] DllUnit1.pas (7): E2273 Не существует перегруженной версии MyFunc с этим списком параметров.
В Delphi Help я вижу:
"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.
Вопросы:
Как правильно экспортировать эти функции в модуль DllUnit1 и можно ли это вообще сделать в Delphi (экспорт под одним именем), чтобы получить тот же вызов из моего проекта TestMyDll, что и в начале (пример из windows.pas)?
Если такие функции можно экспортировать под одним именем, то будет ли корректно работать при вызове DLL из других языков (VB, C ++)? Или лучше сделать две функции с разными именами?
PS Немного похожий вопрос нашел здесь (http://stackru.com/questions/6257013/how-to-combine-overload-and-stdcall-in-delphi), но ответ меня не устраивал
PSS Плохой английский
ДОБАВИТЬ (добавил после ответов)
Понятно, спасибо.
Сделал так:
В проекте:
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;
В DllUnit1
Exports
MyFunc (const X:Integer) Name 'MyFunc',
MyFunc (const X:Extended) Name 'MyFunc1';
Он скомпилирован и работает нормально.
Еще вопросы:
Вроде работает, но правильно ли это?
Имеет ли значение, как написать "Функция MyFunc (const X:Integer): строка; Перегрузка; StdCall;" или "Функция MyFunc (const X:Integer): строка; StdCall; Перегрузка;"?
Эта функция в проекте других языков (Vb, C ++, C #) будет правильно вызываться?
3 ответа
Значит, 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.
лучше сделать две функции с разными именами?**
Да.
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.
Нет, ты ошибаешься. Функции в 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++ с удовольствием позволят вам сделать то же самое. Но вызываемая функция одинакова в любом случае.
'Надеюсь, это поможет