2016-12-09 2 views
1

Я пытаюсь выяснить, как разделить объект между несколькими приложениями в Delphi. Я знаю, что способ сделать это через вызовы IPC/Windows для общей памяти (т. Е. CreateFileMapping и т. Д.), Однако во всем примере кода, который я нашел, они используют простой тип, например строку, тогда как мне нужно разделить объект.обмен объектами между несколькими приложениями в Delphi

Мне интересно, возможно ли это, так как я продолжаю заканчивать приложение, которое просто разделяет ссылку указателя на объект, но не сама память объекта. Когда я пытаюсь получить и затем получить доступ к объекту из другого приложения, я получаю нарушение доступа. Я думаю, это потому, что указатель относится к защищенной памяти из другого приложения.

Вот код, который я пробовал до сих пор (как вы можете видеть, я пытаюсь разделить объект TADOConnection между несколькими приложениями, так что между приложениями используется только одно соединение с базой данных). Если там есть лучший/более простой способ сделать это (совместное использование ADO-соединения), мне интересно узнать, как это сделать.

TSharedData = record 
    Connection: TAdoConnection; 
    end; 

    PSharedData = ^TSharedData; 

var 
    SharedData: PSharedData; 
    hFileMapping: THandle; 
    Form1: TForm1; 

implementation 

{$R *.dfm} 

function CreateNamedFileMapping(const Name: String): THandle; 
begin 
    Result := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, 
    SizeOf(TSharedData)*8, PChar(Name)); 

    if Result > 0 then 
    SharedData := MapViewOfFile(Result, FILE_MAP_ALL_ACCESS, 0, 0, 0); 
end; 

function GetSharedData: PSharedData; 
begin 
    result := nil; 
    hFileMapping := OpenFileMapping(FILE_MAP_ALL_ACCESS, False, 'MySharedMemory'); 
    if (hFileMapping > 0) then 
    Result := MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0); 
end; 

procedure TForm1.createClick(Sender: TObject); 
begin 
    hFileMapping := CreateNamedFileMapping('MySharedMemory'); 
    if (hFileMapping > 0) and Assigned(SharedData) then 
    begin 
    SharedData^.Connection := TAdoConnection.Create(nil); 
    // can't use Assign as it is not supported by _Connection 
    SharedData^.Connection.ConnectionObject := AdoConnection1.ConnectionObject; 
    end; 
end; 

procedure TForm1.retrieveClick(Sender: TObject); 
begin 
    SharedData := GetSharedData; 
    if assigned(SharedData) then 
    // should be set to true if everything was ok 
    ShowMessage(BoolToStr(SharedData.Connection.Connected, true)); 
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
begin 
    AdoConnection1.Connected := False; 

    if assigned(SharedData) then 
    UnmapViewOfFile(SharedData); 
    if hFileMapping > 0 then 
    CloseHandle(hFileMapping); 
end; 


procedure TForm1.FormCreate(Sender: TObject); 
begin 
    AdoConnection1.Connected := true; 
end; 

end. 

ADOConnection1 - это объект на моей форме. Я знаю, что мне нужно скопировать всю память объекта, используя что-то вроде «Assign», однако этого не существует в ADO ConnectionObject. Чтобы убедиться, что проблема не только в ConnectionObject, я также попытался передать простой объект, например, TStringlist, а затем использовать назначение для копирования памяти, но он все еще получает AV в приложении # 2.

Если я запустил создание и извлечение в одном приложении, он отлично работает. Когда я беру копию этого приложения и запускаю функцию «создать» в приложении №1 и «извлекаю» в приложении №2, я получаю нарушение доступа.

+1

Общая память не может помочь. Доступ к многоуровневой базе данных - это то, что вам нужно. Средний уровень доступа к базе данных и клиенты разговаривают с этим средним уровнем, скорее прямым в БД. –

+3

Невозможно передать экземпляр объекта через границы процесса. IPC/файлы с общей памятью/памятью не изменят его. Кроме того, даже если вы можете это сделать, соединения с базой данных выполняются по-потоку, и два процесса явно не будут выполняться в одном потоке. –

+5

Почему, по-вашему, вам нужно разделить ADO-соединение? –

ответ

0

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

В этой версии объект, который не будет использоваться совместно, намного проще, чем ADOConnection, с одним целым полем. Первый экземпляр приложения создает сопоставление и записывает запись с полями целого, объекта и строки. Любой последующий экземпляр приложения пытается извлечь и отобразить эту информацию.

unit Unit1; 

interface 

uses 
    Winapi.Windows, System.SysUtils, System.Classes, 
    Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; 

type 
    TForm1 = class(TForm) 
    procedure FormCreate(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
    end; 

var 
    Form1: TForm1; 

implementation 

{$R *.dfm} 

type 
    TSimpleObject = class(TObject) 
    private 
    FInt: Integer; 
    end; 

    TSharedData = record 
    Int: Integer; 
    SimpleObject: TSimpleObject; 
    Str: string; 
    end; 
    PSharedData = ^TSharedData; 

var 
    SharedData: PSharedData; 
    hFileMapping: THandle; 

const 
    MapName = 'MySharedMemory'; 

procedure TForm1.FormCreate(Sender: TObject); 
var 
    MapExists: Boolean; 
begin 
    hFileMapping := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 
     0, SizeOf(TSharedData), MapName); 
    Win32Check(hFileMapping <> 0); 
    MapExists := GetLastError = ERROR_ALREADY_EXISTS; 

    SharedData := MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0); 
    Win32Check(SharedData <> nil); 

    if MapExists then begin 
    ShowMessage(IntToStr(SharedData.Int)); 
    ShowMessage(IntToStr(SharedData.SimpleObject.FInt)); 
    ShowMessage(SharedData.Str); 
    end else begin 
    SharedData.Int := 555; 
    SharedData.SimpleObject := TSimpleObject.Create; 
    SharedData.SimpleObject.FInt := 666; 
    SharedData.Str := 'test string'; 
    end; 
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
begin 
    Win32Check(CloseHandle(hFileMapping)); 
    Win32Check(UnmapViewOfFile(SharedData)); 
end; 

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


Я разместил памятку на форме и сфотографировал некоторые адреса памяти, чтобы иметь возможность увидеть, что происходит.

procedure TForm1.FormCreate(Sender: TObject); 
var 
    MapExists: Boolean; 
begin 
    Memo1.Clear; 
    hFileMapping := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 
     0, SizeOf(TSharedData), MapName); 
    Win32Check(hFileMapping <> 0); 
    MapExists := GetLastError = ERROR_ALREADY_EXISTS; 

    SharedData := MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0); 
    Win32Check(SharedData <> nil); 

    if MapExists then begin 
    Memo1.Lines.Add(Format('Found at %p', [SharedData])); 
// ShowMessage(IntToStr(SharedData.Int)); 
// ShowMessage(IntToStr(SharedData.SimpleObject.FInt)); 
// ShowMessage(SharedData.Str); 
    end else begin 
    Memo1.Lines.Add(Format('Created at %p', [SharedData])); 
    SharedData.Int := 555; 
    SharedData.SimpleObject := TSimpleObject.Create; 
    SharedData.SimpleObject.FInt := 666; 
    SharedData.Str := 'test string'; 
    end; 
    Memo1.Lines.Add(''); 
    Memo1.Lines.Add(Format('@SharedData.Int: %p', [@SharedData.Int])); 
    Memo1.Lines.Add(Format('PInteger(@SharedData.Int)^: %d', [PInteger(@SharedData.Int)^])); 
    Memo1.Lines.Add(''); 
    Memo1.Lines.Add(Format('@SharedData.SimpleObject: %p', [@SharedData.SimpleObject])); 
    Memo1.Lines.Add(Format('@SharedData.SimpleObject.FInt: %p', [@SharedData.SimpleObject.FInt])); 
    Memo1.Lines.Add(Format('PInteger(@SharedData.SimpleObject.FInt)^: %d', [PInteger(@SharedData.SimpleObject.FInt)^])); 
    Memo1.Lines.Add(''); 
    Memo1.Lines.Add(Format('Pointer(@SharedData.Str)^: %p', [Pointer(Pointer(@SharedData.Str)^)])); 
    Memo1.Lines.Add('character payload'); 
    Memo1.Lines.Add('PChar(Pointer(SharedData.Str)): ' + PChar(Pointer(SharedData.Str))); 
end; 

Ниже, на левой есть кулак экземпляр приложения, право один является вторым экземпляром. Обратите внимание, что второй экземпляр может предпринять несколько попыток показать без нарушения доступа.

enter image description here

Первая строка начальный адрес карты. Обратите внимание, что они не совпадают в обоих случаях (есть вероятность, что они могут быть одинаковыми).Это не является проблемой для нашего случая, но причина в том, что documentation of MapViewOfFile предупреждает:

Не храните указатели в файле с отображением памяти; смещения хранилища от базы сопоставления файлов, так что отображение может использоваться на любом адресе .

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

Четвертая строка сбрасывает адрес объекта, содержащегося в записи. Для обоих экземпляров это четыре байта смещены от начала карты. Правильно? Продолжайте следующую строку, которая выгружает адрес целочисленного поля, содержащегося в объекте.

Есть две тревожные вещи с адресом целочисленного поля класса. Во-первых, они равны в обоих случаях. Это не должно происходить, поскольку файлы карт не запускаются с одинаковыми адресами. Во-вторых, они находятся далеко от адреса файла карты. Ведь мы зарезервировали 12 байт, а не 34 мегабайта.

Причина в том, что ссылка на объект (с адресом, сбрасываемым в предыдущей строке) на самом деле является указателем на экземпляр объекта, который живет где-то в другом месте, и этот указатель указывает на местоположение за пределами сегмента сопоставленного сегмента памяти , Это не проблема для первого экземпляра, так как это процесс, который выделяет память. Однако для второго экземпляра этот адрес имеет совершенно другое значение. Оставьте в покое, что в этом адресе нет объекта, есть вероятность, что адрес может быть даже нечитаемым. Неудивительно, что выгруженное значение как целое отличается от ожидаемого.

То же объяснение относится к строковому полю записи, снятой в последнем абзаце. Адрес, который должен содержать полезная нагрузка символьной строки, имеет совершенно иное значение для второго процесса.


Assign не поможет с полем объекта, он копирует свойства и другие атрибуты указанного объекта-источника к текущему объекту, он не перемещались или скопировать экземпляр объекта. Но можно задаться вопросом, можно ли создать объект с памятью экземпляра, определенной на известном адресе, например, переопределяя NewInstance. К сожалению, это также невозможно для чего-то более сложного, чем объект в этом примере. Невозможно было бы содержать всю память, необходимую для функционирования объекта в одном блоке памяти. Например, для ADOConnection, которое вы пытаетесь использовать, ConnectionObject является еще одним указателем.

Смежные вопросы