Неявное выделение памяти в динамической библиотеке

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

Неявное выделение памяти в динамической библиотеке

Сообщение gluhow » 08.09.2015 17:00:14

Здравствуйте, когда-то давно мне понадобилось сделать функцию, которая бы возвращала переменную типа Variant из динамической библиотеки. По-видимому сразу возникли проблемы, поэтому тогда описал ее не как функцию, а как процедуру следующим образом:
procedure (pv:PVariant);cdecl;

Какое-то время все замечательно работало, но сейчас, в случае если функция возвращает текст, то программа вылетает. По-видимому, вылет происходит в момент очистки памяти (сразу после завершения той процедуры в которой вызывалась бибилиотека). При этом само возвращаемо значение вполне читаемо и корректно. В случае если функция возвращает число, проблем нет.

Что делать?
gluhow
новенький
 
Сообщения: 41
Зарегистрирован: 13.08.2015 15:30:20

Re: Неявное выделение памяти в динамической библиотеке

Сообщение runewalsh » 08.09.2015 17:37:24

Скомпилируй fpcmemdll.pp (найди в папке лазаруса), получившуюся dll таскай рядом с остальными и подключи uses sharemem САМЫМ первым модулем каждой DLL и основной программы (если проект лазаруса, .lpr: Project → View source).
Аватара пользователя
runewalsh
энтузиаст
 
Сообщения: 579
Зарегистрирован: 27.04.2010 00:15:25

Re: Неявное выделение памяти в динамической библиотеке

Сообщение gluhow » 08.09.2015 19:40:16

runewalsh писал(а):Скомпилируй fpcmemdll.pp (найди в папке лазаруса), получившуюся dll таскай рядом с остальными и подключи uses sharemem САМЫМ первым модулем каждой DLL и основной программы (если проект лазаруса, .lpr: Project → View source).

У меня Linux =(
gluhow
новенький
 
Сообщения: 41
Зарегистрирован: 13.08.2015 15:30:20

Re: Неявное выделение памяти в динамической библиотеке

Сообщение runewalsh » 08.09.2015 21:17:46

gluhow писал(а):У меня Linux =(

Попробуй скопировать к себе в проект sharemem.pp и fpcmemdll.pp, перебить названия, заменить в их тексте название dll-ки на соответствующую .so и использовать их, как сказано выше.
Аватара пользователя
runewalsh
энтузиаст
 
Сообщения: 579
Зарегистрирован: 27.04.2010 00:15:25

Re: Неявное выделение памяти в динамической библиотеке

Сообщение gluhow » 09.09.2015 08:58:46

Дело в том что sharemem.pp и fpcmemdll.pp у меня находятся по адресу ../fpc/rtl/win что как-бы говорит что они предназначаются для Windows
Я сегодня придумал и сейчас успешно проверил следующую штуку:
В библиотеке создал переменную
Код: Выделить всё
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) всё работает
gluhow
новенький
 
Сообщения: 41
Зарегистрирован: 13.08.2015 15:30:20

Re: Неявное выделение памяти в динамической библиотеке

Сообщение wavebvg » 09.09.2015 11:10:04

Variant - это тип с подсчетом ссылок.
Если хотите передать его куда-либо вне библиотеки - его необходимо скопировать до потери всех внешних ссылок в коде библиотеки.
Иными словами:
    сохраняете (именно сохраняете, потому что назначая другой переменной вы увеличили количество ссылок, а запомнили лишь указатель) в глобальную переменную (если не нужно многопоточности)
    подготавливаете callback в котором передаете переменную куда Вам требуется и вызываете этот код из библиотеки, при обработке копируете данных из Variant-а (тогда можно не думать о потоках)
wavebvg
постоялец
 
Сообщения: 355
Зарегистрирован: 28.02.2008 04:57:35

Re: Неявное выделение памяти в динамической библиотеке

Сообщение runewalsh » 09.09.2015 14:17:58

А ты всё же ПОПРОБУЙ мой способ.
Глобальная переменная может не сработать в случае, когда она заменена новым значением (функция вызвана более одного раза), а затем уничтожена в основной программе. Получится, что выделила память библиотека, а освободила — программа. Копировать варианты в коллбеке, по идее, можно, но муторно.

Вот ещё вариант.
Добавь в интерфейс 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;
Аватара пользователя
runewalsh
энтузиаст
 
Сообщения: 579
Зарегистрирован: 27.04.2010 00:15:25

Re: Неявное выделение памяти в динамической библиотеке

Сообщение gluhow » 10.09.2015 11:07:50

Второй способ поробовал, вызывал InitLIBNAME в основной программе перед вызовом функций из библиотеки. Это не принесло никакого результата.
Код библиотеки
Код: Выделить всё
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.
gluhow
новенький
 
Сообщения: 41
Зарегистрирован: 13.08.2015 15:30:20

Re: Неявное выделение памяти в динамической библиотеке

Сообщение runewalsh » 10.09.2015 13:39:53

Хорошо, то есть жаль, а что если так: в дополнение к TMemoryManager/SetMemoryManager/GetMemoryManager сделай то же с TVariantManager/SetVariantManager/GetVariantManager?
Аватара пользователя
runewalsh
энтузиаст
 
Сообщения: 579
Зарегистрирован: 27.04.2010 00:15:25

Re: Неявное выделение памяти в динамической библиотеке

Сообщение ssl » 16.09.2015 22:26:49

Я к dll (so) прилагаю модуль для добавления в проект. Этот модуль содержит (простой) хелпер, который не только строки помогает возвращать, но и прозрачной трансляцией исключений занимается, версию реализации интерфейса контролирует и ещё по мелочи. Идея следующая: библиотека экспортирует только одну функцию: GetMyInterfase(Helper), которая получает экземпляр хелпера и возвращает интерфейс.
Всё, что надо экспортировать из библиотеки, оформлено как методы интерфейса. Удобно.
Хелпер, кроме прочего, имеет метод:

Код: Выделить всё
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 на текст в основное приложение (хелперу),
который и занимается распределением памяти. В-общем, вариант колбэка. Один минус: получаются не функции, а процедуры. Если б можно было к интерфейсам хелперы объявлять.. :roll:
ssl
новенький
 
Сообщения: 59
Зарегистрирован: 17.05.2005 11:27:01


Вернуться в Lazarus

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 242

Рейтинг@Mail.ru
cron