Совместное использование массива данных между двумя приложениями в Delphi

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

Я нашел способ поделиться указателем, используя OpenFileMapping и MapViewOfFile. Мне не повезло реализовать совместное использование массива, и я думаю, что пока не хочу использовать метод IPC.

Можно ли спланировать такую ​​схему (совместное использование массива)? Моя цель - свести к минимуму использование памяти и быстрое чтение данных.


person user    schedule 13.04.2011    source источник
comment
Что вы пробовали и почему это не удалось? Поскольку именно так вы это и делаете, оба приложения отображают представление одного и того же файла, поэтому они оба работают с одной и той же памятью. Что в вашем массиве?   -  person Cosmin Prund    schedule 13.04.2011
comment
Космин прав, файлы с отображением памяти - это обычный способ сделать это. Таких примеров в сети бесчисленное множество. Я нахожу забавным, что вы хотите, чтобы два процесса разделяли память без использования IPC. Вы не можете разделить память между двумя процессами без использования IPC.   -  person David Heffernan    schedule 13.04.2011
comment
@Cosmin, я отвечу в твоем ответе.   -  person user    schedule 13.04.2011
comment
@ Дэвид, я просто хочу, чтобы обе программы могли считывать данные из одной области памяти. На мой взгляд, IPC - это метод обмена данными?   -  person user    schedule 13.04.2011
comment
IPC — это межпроцессное взаимодействие, а вы хотите обмениваться данными!   -  person David Heffernan    schedule 13.04.2011
comment
Хм... Я имею в виду, что программа А предоставляет данные один раз, а программа Б может получить данные без повторного предоставления программой А данных. Я прав или с помощью карты приложений программа А всегда отвечает на запрос программы Б?   -  person user    schedule 13.04.2011


Ответы (2)


Почесал голову, думая о том, каким может быть короткий, но полный пример разделения памяти между двумя приложениями. Единственный вариант — консольное приложение, приложения с графическим интерфейсом требуют как минимум 3 файла (DPR + PAS + DFM). Итак, я приготовил небольшой пример, в котором один массив целых чисел используется совместно с использованием файла с отображением памяти (поддерживаемого файлом подкачки, поэтому мне не нужно иметь физический файл на диске, чтобы это работало). Консольное приложение отвечает на 3 команды:

  • ВЫХОД
  • SET NUM VALUE Изменяет значение индекса NUM в массиве на VALUE.
  • DUMP NUM отображает значение в массиве с индексом NUM.
  • DUMP ALL отображает весь массив

Конечно, код обработки команд занимает около 80% всего приложения. Чтобы проверить это, скомпилируйте следующее консольное приложение, найдите исполняемый файл и запустите его дважды. Перейдите в первое окно и введите:

SET 1 100
SET 2 50

Перейдите во вторую консоль и введите это:

DUMP 1
DUMP 2
DUMP 3
SET 1 150

Перейдите в первую консоль и введите это:

DUMP 1

Вот и все, вы только что стали свидетелями разделения памяти между двумя приложениями.

program Project2;

{$APPTYPE CONSOLE}

uses
  SysUtils, Windows, Classes;

type
  TSharedArray = array[0..10] of Integer;
  PSharedArray = ^TSharedArray;

var
  hFileMapping: THandle; // Mapping handle obtained using CreateFileMapping
  SharedArray: PSharedArray; // Pointer to the shared array
  cmd, s: string;
  num, value, i: Integer;
  L_CMD: TStringList;

function ReadNextCommand: string;
begin
  WriteLn('Please enter command (one of EXIT, SET NUM VALUE, DUMP NUM, DUMP ALL)');
  WriteLn;
  ReadLn(Result);
end;

begin
  try
    hFileMapping := CreateFileMapping(0, nil, PAGE_READWRITE, 0, SizeOf(TSharedArray), '{C616DDE6-23E2-425C-B871-9E0DA54D96DF}');
    if hFileMapping = 0 then
      RaiseLastOSError
    else
      try
        SharedArray := MapViewOfFile(hFileMapping, FILE_MAP_READ or FILE_MAP_WRITE, 0, 0, SizeOf(TSharedArray));
        if SharedArray = nil then
          RaiseLastOSError
        else
          try
            WriteLn('Connected to the shared view of the file.');

            cmd := ReadNextCommand;
            while UpperCase(cmd) <> 'EXIT' do
            begin
              L_CMD := TStringList.Create;
              try
                L_CMD.DelimitedText := cmd;
                for i:=0 to L_CMD.Count-1 do
                  L_CMD[i] := UpperCase(L_CMD[i]);

                if (L_CMD.Count = 2) and (L_CMD[0] = 'DUMP') and TryStrToInt(L_CMD[1], num) then
                  WriteLn('SharedArray[', num, ']=', SharedArray^[num])
                else if (L_CMD.Count = 2) and (L_CMD[0] = 'DUMP') and (L_CMD[1] = 'ALL') then
                  begin
                    for i:= Low(SharedArray^) to High(SharedArray^) do
                      WriteLn('SharedArray[', i, ']=', SharedArray^[i]);
                  end
                else if (L_CMD.Count = 3) and (L_CMD[0] = 'SET') and TryStrToInt(L_CMD[1], num) and TryStrToInt(L_CMD[2], value) then
                  begin
                    SharedArray^[num] := Value;
                    WriteLn('SharedArray[', num, ']=', SharedArray^[num]);
                  end
                else
                   WriteLn('Error processing command: ' + cmd);

              finally L_CMD.Free;
              end;

              // Requst next command
              cmd := ReadNextCommand;
            end;


          finally UnmapViewOfFile(SharedArray);
          end;
      finally CloseHandle(hFileMapping);
      end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
person Cosmin Prund    schedule 13.04.2011
comment
@Cosmin, почему я не могу показать все элементы, используя цикл? Он показывает много 0. `для j := low(SharedArray^) to high(SharedArray^) do WriteLn(SharedArray^[j]);'. Мне нужно сопоставить определенные данные с массивом. Я только что попробовал зациклиться, надеюсь, общий массив может работать с бинарным поиском. - person user; 13.04.2011
comment
В общем блоке памяти нет ничего волшебного, это просто общий блок памяти. Вы можете реализовать любой алгоритм, который вы хотите, используя его. Он показывает много нулей, потому что блок памяти инициализирован нулями. - person Cosmin Prund; 13.04.2011
comment
Космин, я пытался показать все элементы, но вижу ^только^ много нулей. - person user; 13.04.2011
comment
Вы скомпилировали мою программу? Вы дали команду SET 1 2? Вы получите только НУЛИ, если сами не напишете что-то еще. - person Cosmin Prund; 13.04.2011
comment
Другая возможная проблема: вам нужно, чтобы хотя бы один экземпляр программы работал, чтобы хранить данные в памяти. В момент закрытия последнего экземпляра разделяемой памяти все данные теряются. Если вы снова запустите приложение, оно получит новый буфер, заполненный нулями. Если вы хотите иметь возможность остановить программу, вам нужно использовать файлы с отображением памяти, поддерживаемые реальными файлами на диске. - person Cosmin Prund; 13.04.2011
comment
Я скомпилировал вашу программу и заполнил массив. Экземпляры обеих программ все еще работают. Сначала я запустил обе программы, затем ввел команду SET 1 2 в первой программе. Вторая программа может получить доступ к массиву с помощью DUMP, но я не могу показать все элементы. Я пробую OpenFileMapping вместо CreateFileMapping во второй программе. - person user; 13.04.2011
comment
Тот же результат с использованием OpenFileMapping. - person user; 13.04.2011
comment
Я не понимаю, что ты делаешь. Я не предоставил команду для сброса всего массива, поэтому я предполагаю, что вы делаете что-то еще (не используете мою программу). После выдачи set 1 2 выдача dump 1 должна отображать значение 2. Не имеет значения, делаете ли вы dump 1 из того же экземпляра или из другого экземпляра, результат должен быть одинаковым. - person Cosmin Prund; 13.04.2011
comment
Я изменил вторую программу, добавив команду DIR. иначе, если Copy(cmd, 1, 3) = 'DIR', тогда начните с j := low(SharedArray^) to high(SharedArray^) do WriteLn(SharedArray^[j]); конец;. Все, что у меня есть, это много нулей. - person user; 13.04.2011
comment
Хорошо, я могу сделать трюк. set 1 3 3 - это размер массива, затем я выгружаю весь массив с for j := 2 to (SharedArray^[1])+1 do WriteLn(SharedArray^[j]); . Я могу это сделать, потому что в моем случае я знаю размер своего массива. - person user; 13.04.2011
comment
Я отредактировал свой ответ, чтобы упростить обработку команд и предоставить команду DUMP ALL для отображения всего массива. Я уменьшил размер массива до [0..10], чтобы сделать DUMP ALL управляемым (в конце концов, это всего лишь пример). Я понятия не имею, почему ваш способ не работает, потому что я не вижу вашего кода, я вижу только свой. А мой работает. Если возможно, скопируйте и вставьте измененный код в pastebin, и я посмотрю на него. - person Cosmin Prund; 13.04.2011
comment
Я не знаю, упускаю ли я что-то. Ваш код работает! Мой не pastebin.com/00m7WVqj - person user; 14.04.2011
comment
@user, твой код тоже работает. Попробуйте это SET 1024 1024, а затем DIR. Поскольку вы сохранили массив [0..1024], в последней напечатанной строке будет отображаться 1024. (На самом деле я скопировал, скомпилировал и протестировал ваш код) - person Cosmin Prund; 14.04.2011
comment
Да, спасибо. Но почему «High(SharedArray^)» равен 1024? Блок памяти? Почему я пробую SET 1025 1025, а потом DIR, в SharedArray^[1025] ничего нет? Это сбивает меня с толку. - person user; 14.04.2011
comment
Я имею в виду зацикливание только до 1024 - person user; 14.04.2011
comment
Поскольку SharedArray имеет тип array [0..1024] of Integer. Там нет SharedArray[1025], и вы получите ошибку проверки диапазона, если скомпилируете с {$R+}. Очевидно, вы можете определить TSharedArray таким, каким вам нужно. - person Cosmin Prund; 14.04.2011
comment
Ok. Собственно у меня еще есть один вопрос, надеюсь вы дадите направление. Мне нужно использовать динамический массив в моей программе. Итак, мое объявление массива TSharedArray = array of Integer;, и я не забываю поставить SetLength(SharedArray^, 10);. Это работает для первой программы. Я пытаюсь поставить и не поставить SetLength(SharedArray^, 10); для второй программы, но я все еще не могу получить доступ к данным из первой программы. pastebin.com/mfD3tak2 - person user; 14.04.2011
comment
Динамические массивы — это совершенно другая проблема: они размещаются в куче, где переменная — это только указатель. Выделенная память не выделяется в общем буфере, и даже если бы это было так, выделенный буфер не виден всем приложениям по одному и тому же адресу (поэтому указатели, установленные первым приложением, недействительны для второе приложение). Вы можете обойти эту проблему, разработав собственную структуру данных, которая использует не абсолютные указатели, а относительные указатели (индексы). Или просто выделите достаточно большой массив для начала. - person Cosmin Prund; 21.04.2011

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

Основной:

type
  TSharedData = record
    Handle: THandle;
  end;
  PSharedData = ^TSharedData;

const
  BUF_SIZE = 256;
var
  SharedData: PSharedData;
  hFileMapping: THandle;  // Don't forget to close when you're done

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

  Win32Check(Result > 0);

  SharedData := MapViewOfFile(Result, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE);

  Win32Check(Assigned(SharedData));
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  hFileMapping := CreateNamedFileMapping('MySharedMemory');
  Win32Check(hFileMapping > 0);
  SharedData^.Handle := CreateHiddenWindow;
end;

читатель:

var
  hMapFile: THandle;   // Don't forget to close

function GetSharedData: PSharedData;
begin
  hMapFile := OpenFileMapping(FILE_MAP_ALL_ACCESS, False, 'MySharedMemory');
  Win32Check(hMapFile > 0);

  Result := MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE);

  Win32Check(Assigned(Result));
end;
person Remko    schedule 13.04.2011
comment
Вы пропускаете дескрипторы: дескриптор, возвращаемый CreateFileMapping, назначается только локальной переменной в TForm1.Button1Click, поэтому его невозможно закрыть. Это не требуется: ZeroMemory(SharedData, BUF_SIZE); — Сопоставления файлов, поддерживаемые файлом подкачки, (к счастью) инициализированы нулями. Это было бы угрозой безопасности, если бы эта память не была инициализирована нулями! - person Cosmin Prund; 13.04.2011
comment
@Cosmin Prund: спасибо за подсказку об нулевой инициализации (я этого не знал). По поводу рукоятки да вы правы (и это тоже к читателю). - person Remko; 13.04.2011
comment
Что делает ваш CreateHiddenWindow? - person Warren P; 06.03.2013