在Delphi中两个应用程序之间共享数据数组

11

我想在两个应用程序之间共享数组数据。我的想法是,第一个程序创建数组,第二个程序可以从已分配的内存区域读取该数组。该数组不是动态数组。

我发现可以使用 OpenFileMappingMapViewOfFile 共享指针。但我没有成功实现数组共享,并且我认为我暂时不想使用IPC方法。

是否有可能计划像这样的方案(共享数组)?我的目的是尽量减少内存使用并快速读取数据。


1
你尝试过什么,以及它是如何失败的?因为这正是你要做的,两个应用程序都映射了同一个文件的视图,因此它们都使用相同的内存。你的数组里有什么? - Cosmin Prund
Cosmin是正确的,内存映射文件是正常处理此问题的方法。网络上有无数这方面的例子。我觉得有趣的是你想要两个进程共享内存却不使用IPC。你不能在两个进程之间共享内存而不使用IPC。 - David Heffernan
@Cosmin,我会在你的回答中回复。 - user
@David,我的意思是我希望两个程序都可以从一个内存区域读取数据。在我看来,IPC 是一种进行数据交换的方法吗? - user
IPC是进程间通信,你想要进行数据交换! - David Heffernan
嗯..我的意思是程序A提供数据一次,程序B可以在不需要程序A再次提供数据的情况下检索数据。我是对的吗?或者通过应用程序映射,程序A总是响应程序B的请求? - user
2个回答

18

思考了一下如何展示两个应用程序之间共享内存的一个简短但完整的示例。唯一的选择是控制台应用程序,GUI 应用需要至少 3 个文件 (DPR + PAS + DFM)。因此我构建了一个小示例,其中使用内存映射文件共享了一个整数数组(由页面文件支持,因此我不需要在磁盘上拥有一个物理文件才能让它工作)。控制台应用程序响应 3 个命令:

  • EXIT
  • 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.

@Cosmin,为什么我不能使用循环来显示所有项?它显示了许多0。`for j:= low(SharedArray ^)to high(SharedArray ^)do WriteLn(SharedArray ^ [j]); '。我需要将特定数据与数组匹配。我只是尝试循环,我希望共享的数组可以使用二进制搜索。 - user
共享内存块并不神奇,它只是一个共享的内存块。您可以使用它来实现任何算法。它显示许多零,因为内存块已初始化为零。 - Cosmin Prund
我已经编译了你的程序并填充了数组。两个程序实例仍在运行。首先我运行了这两个程序,然后在第一个程序中发出了“SET 1 2”命令。第二个程序可以通过使用“DUMP”访问数组,但我无法显示所有项。我正在尝试在第二个程序中使用OpenFileMapping而不是CreateFileMapping。 - user
使用OpenFileMapping可以得到相同的结果。 - user
2
因为SharedArray的类型是array [0..1024] of Integer。如果你编译时使用了{$R+},那么就没有SharedArray[1025],并且会出现范围检查错误。你可以根据需要定义TSharedArray - Cosmin Prund
显示剩余13条评论

7
一个命名文件映射将是最简单的解决方案,下面是一些简短的示例代码。在这个示例中有一个主程序写入一些数据和读取器,只从它读取数据。

主程序:

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;

您正在泄漏句柄:CreateFileMapping 返回的句柄仅分配给 TForm1.Button1Click 中的局部变量,因此无法关闭它。这是不必要的:ZeroMemory(SharedData, BUF_SIZE); - 由页面文件支持的文件映射(值得庆幸的是)会被初始化为零。如果该内存未被初始化为零,则可能构成安全威胁! - Cosmin Prund
@Cosmin Prund:感谢您提供有关零初始化的提示(我不知道)。关于句柄,是的,您是正确的(这也是为读者准备的)。 - Remko
2
你的CreateHiddenWindow函数是做什么用的? - Warren P

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接