Неявное выделение памяти в динамической библиотеке
Модератор: Модераторы
Неявное выделение памяти в динамической библиотеке
Здравствуйте, когда-то давно мне понадобилось сделать функцию, которая бы возвращала переменную типа Variant из динамической библиотеки. По-видимому сразу возникли проблемы, поэтому тогда описал ее не как функцию, а как процедуру следующим образом:
procedure (pv:PVariant);cdecl;
Какое-то время все замечательно работало, но сейчас, в случае если функция возвращает текст, то программа вылетает. По-видимому, вылет происходит в момент очистки памяти (сразу после завершения той процедуры в которой вызывалась бибилиотека). При этом само возвращаемо значение вполне читаемо и корректно. В случае если функция возвращает число, проблем нет.
Что делать?
procedure (pv:PVariant);cdecl;
Какое-то время все замечательно работало, но сейчас, в случае если функция возвращает текст, то программа вылетает. По-видимому, вылет происходит в момент очистки памяти (сразу после завершения той процедуры в которой вызывалась бибилиотека). При этом само возвращаемо значение вполне читаемо и корректно. В случае если функция возвращает число, проблем нет.
Что делать?
Скомпилируй fpcmemdll.pp (найди в папке лазаруса), получившуюся dll таскай рядом с остальными и подключи uses sharemem САМЫМ первым модулем каждой DLL и основной программы (если проект лазаруса, .lpr: Project → View source).
runewalsh писал(а):Скомпилируй fpcmemdll.pp (найди в папке лазаруса), получившуюся dll таскай рядом с остальными и подключи uses sharemem САМЫМ первым модулем каждой DLL и основной программы (если проект лазаруса, .lpr: Project → View source).
У меня Linux =(
gluhow писал(а):У меня Linux =(
Попробуй скопировать к себе в проект sharemem.pp и fpcmemdll.pp, перебить названия, заменить в их тексте название dll-ки на соответствующую .so и использовать их, как сказано выше.
Дело в том что sharemem.pp и fpcmemdll.pp у меня находятся по адресу ../fpc/rtl/win что как-бы говорит что они предназначаются для Windows
Я сегодня придумал и сейчас успешно проверил следующую штуку:
В библиотеке создал переменную
А функцию реализовал следующим образом
И все работает! Причем если делать сразу
То выдается такая же ошибка как была первоначально
Так что всем большое спасибо, проблему можно считать решенной
После тестирования разных функций нашел один случай когда все падает:
В остальных случаях, когда над x производятся какие-нибудь действия (хоть +'', хоть +0) всё работает
Я сегодня придумал и сейчас успешно проверил следующую штуку:
В библиотеке создал переменную
Код: Выделить всё
Res:Variant;А функцию реализовал следующим образом
Код: Выделить всё
Function (x:Variant):Variant;
begin
Res:=fnc(x);
Result:=Res;
end;
И все работает! Причем если делать сразу
Код: Выделить всё
Result:=fnc(x)То выдается такая же ошибка как была первоначально
Так что всем большое спасибо, проблему можно считать решенной
После тестирования разных функций нашел один случай когда все падает:
Код: Выделить всё
Function (x:Variant):Variant;
begin
Res:=x;
Result:=Res;
end;
В остальных случаях, когда над x производятся какие-нибудь действия (хоть +'', хоть +0) всё работает
Variant - это тип с подсчетом ссылок.
Если хотите передать его куда-либо вне библиотеки - его необходимо скопировать до потери всех внешних ссылок в коде библиотеки.
Иными словами:
Если хотите передать его куда-либо вне библиотеки - его необходимо скопировать до потери всех внешних ссылок в коде библиотеки.
Иными словами:
- сохраняете (именно сохраняете, потому что назначая другой переменной вы увеличили количество ссылок, а запомнили лишь указатель) в глобальную переменную (если не нужно многопоточности)
подготавливаете callback в котором передаете переменную куда Вам требуется и вызываете этот код из библиотеки, при обработке копируете данных из Variant-а (тогда можно не думать о потоках)
А ты всё же ПОПРОБУЙ мой способ.
Глобальная переменная может не сработать в случае, когда она заменена новым значением (функция вызвана более одного раза), а затем уничтожена в основной программе. Получится, что выделила память библиотека, а освободила — программа. Копировать варианты в коллбеке, по идее, можно, но муторно.
Вот ещё вариант.
Добавь в интерфейс DLL функцию
И перед вызовом любых других функций этой DLL сделай
Глобальная переменная может не сработать в случае, когда она заменена новым значением (функция вызвана более одного раза), а затем уничтожена в основной программе. Получится, что выделила память библиотека, а освободила — программа. Копировать варианты в коллбеке, по идее, можно, но муторно.
Вот ещё вариант.
Добавь в интерфейс DLL функцию
Код: Выделить всё
procedure setmm(const mm: TMemoryManager); stdcall;
begin
System.SetMemoryManager(mm);
end;
exports
setmm name 'SetMemoryManager';И перед вызовом любых других функций этой DLL сделай
Код: Выделить всё
procedure setLIBNAMEmm(const mm: TMemoryManager); stdcall; external 'LIBNAME' name 'SetMemoryManager';
procedure InitLIBNAME;
var
mm: TMemoryManager;
begin
System.GetMemoryManager(mm);
setLIBNAMEmm(mm);
end;Второй способ поробовал, вызывал InitLIBNAME в основной программе перед вызовом функций из библиотеки. Это не принесло никакого результата.
Код библиотеки
Код вызывающей программы с двумя Edit-ами и динамической линковкой
Попробовал первый способ, для этого скопировал к себе модули libfpcmemdll и sharemem. Скомпилил libfpcmemdll.so, подправил в sharemem, добавил его первым модулем в библиотеку
на
Библиотека скомпилировалась, но при этом LoadLibrary выдает 0.
Код библиотеки
Код: Выделить всё
library project1;
{$mode objfpc}{$H+}
uses
Classes
{ you can add units after this };
var C:variant; //Без этой переменной не работает
procedure setmm(const mm: TMemoryManager); stdcall;
begin
System.SetMemoryManager(mm);
end;
function FormatVal(X:Variant):variant;
begin
Result:=X+'!';
end;
exports
setmm name 'SetMemoryManager',
FormatVal;
begin
end.
Код вызывающей программы с двумя Edit-ами и динамической линковкой
Код: Выделить всё
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, dynlibs, variants;
type
{ TForm1 }
TFormatVal=function(x:Variant):variant;
Tsetmm=procedure (const mm: TMemoryManager); stdcall;
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
procedure Edit1Change(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ private declarations }
FormatVal:TFormatVal;
LibHandle:THandle;
setmm:Tsetmm;
public
{ public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.Edit1Change(Sender: TObject);
var
mm: TMemoryManager;
begin
if Assigned(FormatVal) then
begin
System.GetMemoryManager(mm);
setmm(mm);
Edit2.Text:=FormatVal(Edit1.Text);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
LibHandle:=LoadLibrary('../Lib/libproject1.so');
if LibHandle=0 then
begin
ShowMessage('Библиотека не загружена.');
Exit;
end;
FormatVal:=TFormatVal(GetProcAddress(LibHandle, 'FormatVal'));
setmm:=Tsetmm(GetProcAddress(LibHandle, 'SetMemoryManager'));
end;
end.
Попробовал первый способ, для этого скопировал к себе модули libfpcmemdll и sharemem. Скомпилил libfpcmemdll.so, подправил в sharemem, добавил его первым модулем в библиотеку
Код: Выделить всё
const
fpcmemdll = 'fpcmemdll.dll';Код: Выделить всё
fpcmemdll = 'libfpcmemdll.so';Библиотека скомпилировалась, но при этом LoadLibrary выдает 0.
Хорошо, то есть жаль, а что если так: в дополнение к TMemoryManager/SetMemoryManager/GetMemoryManager сделай то же с TVariantManager/SetVariantManager/GetVariantManager?
Я к dll (so) прилагаю модуль для добавления в проект. Этот модуль содержит (простой) хелпер, который не только строки помогает возвращать, но и прозрачной трансляцией исключений занимается, версию реализации интерфейса контролирует и ещё по мелочи. Идея следующая: библиотека экспортирует только одну функцию: GetMyInterfase(Helper), которая получает экземпляр хелпера и возвращает интерфейс.
Всё, что надо экспортировать из библиотеки, оформлено как методы интерфейса. Удобно.
Хелпер, кроме прочего, имеет метод:
В библиотеке метод, возвращающий строку, выглядит так:
В приложении, использующим библиотеку, так:
То есть, приложение передаёт в библиотеку нетипизированную ссылку на строку, библиотека тут же возвращает эту ссылку и PChar на текст в основное приложение (хелперу),
который и занимается распределением памяти. В-общем, вариант колбэка. Один минус: получаются не функции, а процедуры. Если б можно было к интерфейсам хелперы объявлять..
Всё, что надо экспортировать из библиотеки, оформлено как методы интерфейса. Удобно.
Хелпер, кроме прочего, имеет метод:
Код: Выделить всё
procedure TIHtCallbackHelper.AllocStr(Src: PChar; Chars: integer; var Dst); stdcall;
var Buf: array[0..1023] of Char;
s: ^string;
begin
try
s:= @Dst;
if Chars < 0 then // autodetect, zero terminated
Chars:= StrLen(Src);
if Chars = 0 then
s^:= '' // empty
else begin
SetLength(s^, Chars);
Move(Src^, Pointer(s^)^, SizeOf(Char) * Chars);
end;
except
ExceptionErrorMessage(ExceptObject, ExceptAddr, Buf, SizeOf(Buf));
DebugStr(Buf);
end;
end;
В библиотеке метод, возвращающий строку, выглядит так:
Код: Выделить всё
procedure THtConnect.sc_DbName(var Dst); safecall;
begin
_Helper.AllocStr(PNChar(s2x(FDbName)), -1, Dst);
end;
В приложении, использующим библиотеку, так:
Код: Выделить всё
procedure TCCbHandler.ConnectNotify(const HtConnect: IHtConnect; Operation: TConnectOperation);
var s: string;
begin
if Operation <> copStopped then Exit;
CheckLog(true);
if HtConnect = con then // в случае, например, HtConnect.Crash
con:= nil;
HtConnect.DbName(s);
WriteLn;
CWriteLn(['-- Disconnected from ', s], [caNote, caInfo]);
end;
То есть, приложение передаёт в библиотеку нетипизированную ссылку на строку, библиотека тут же возвращает эту ссылку и PChar на текст в основное приложение (хелперу),
который и занимается распределением памяти. В-общем, вариант колбэка. Один минус: получаются не функции, а процедуры. Если б можно было к интерфейсам хелперы объявлять..
