编程技术分享平台

网站首页 > 技术教程 正文

Delphi下深入Windows编程之内存映像

xnh888 2024-10-25 18:11:30 技术教程 19 ℃ 0 评论

我的理想是能够写出一个可以永不封号的游戏外G!

虽然这个理想在很多科班出身的程序员眼中会变得不值一提甚至会被抵制或者鄙视,诚然如果是用来牟利当然不能够被提倡,因为是违法的,但是如果仅仅是作为网络游戏安全来学习的话我个人认为是值得深入学习的,当然这里不讨论这个东西的好与坏,它就像一把双刃剑,主要还是看你用来做什么

我中间学习过各种语言按键精灵(甚至不能称为语言)、易语言、VB等等,中间甚至有一段时间在学习C++因为听一些前辈说,要想写一个好的外G需要驱动而C++是编写驱动程序的首选,但是学习C++的成本太高了,这其中有很多原因,例如因为C++的版本问题,它需要兼容C这时候会出来很多奇奇怪怪的写法,甚至有的人在cpp文件中编写纯C的代码,这其实我转战Delphi的一部分原因,因为实在是学不会

Delphi很多API函数都是对WindowsAPI的二次封装,这样做的好处就是我们在调用的时候变得更容易,此外就是界面的编写,MFC太古老了,而且写起来超级费劲,高手或者大佬会对界面嗤之以鼻,也会有人提出什么像QT之类的界面库,但是这些东西个人感觉远远没有VCL来的轻松愉悦,虽然在高手眼里界面不值得一提,但是我们这些小白或者菜鸟很多时候都是从界面编写开始产生兴趣,从而坚持下去的!

现如今大部分的网络游戏都是运行在windows操作系统下的,有的朋友可能会说手游,这个不在今天的讨论范围之内,那么既然在做windows下的游戏,我们就要搞明白游戏是怎么跑起来的,其实所谓的游戏就是通过调用大量的windows系统函数,而这些windows的系统函数我们称它为windowsAPI,我们需要做的就是熟悉这些API函数,此外在windows中充斥的大量的消息,比如说键盘消息、鼠标消息等等,所以我们还需要搞明白windows的消息机制是什么套路

很多时候我们需要两个进程之间进行数据通讯,例如我们把一个DLL文件附加(或者称为注入)一个进程中,而我们需要一个独立的程序来查看或者观测DLL宿主进程中的运行情况!有朋友可能会提出通过Socket实现,诚然这种方式肯定可以,但是在同一台电脑上创建服务器端和服务器端稍微显得的有点杀鸡用牛刀的感觉,那么有没有其他方式能够解决这个需求,这就是我们今天要说的内存映像,前段时间看了一本书名字是【Delphi下深入Windows编程】虽然这本书很老了,但是里面的思想或者说思路还是值得我们借鉴的,特整理如下

在win32中,通过使用映像文件在进程间实现共享文件或共享内存数据块,如果利用相同的映像名字或文件句柄,则不同的进程可以通过一个指针来读写一个文件或同一个内存数据块,并把它当做该进程内地址空间的一部分

在Windows9x/NT/200 向内存中装载文件时,使用了特殊的全局内存区。在该区域内,应用程序的虚拟内存地址和文件中的响应位置对应,由于所有进程共享了一个用于存储映像文件的全局内存区域,因而当两个进程装载相同模块(应用程序exe或dll文件)时,他们实际上是在内存中共享其执行代码

内存映像文件可以映射一个文件、一个文件中的指定区域或者指定的内存块,其中的数据就可以用内存读写指令直接访问,而不比频繁的调用ReadFile或WriteFIle这样的I/O系统函数,从而提高了文件存取速度和效率

示例代码

发送数据端

unit Unit1;

interface

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

const
  WM_DATA = WM_USER + 1024;

type
  PShareMem = ^TPShareMem;

  TPShareMem = record
    //共享数据
    Data: array[0..255] of Char;
  end;

  TForm1 = class(TForm)
    btn1: TButton;
    procedure btn1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  PShare: PShareMem;

implementation

{$R *.dfm}

var
  HMapping: THandle;
  HMapMutex: THandle;

const
  MAP_FILE_SIZE = 1000;
  REQUEST_TIME_OUT = 1000;

  {打开建立共享内存}
procedure OpenMap;
begin
  {创建一个映像文件}
  HMapping := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, SizeOf(TPShareMem), PChar('MapName'));

  if HMapping = 0 then
  begin
    ShowMessage('不能创建内存映像文件');
    Exit
  end;
    {将映像文件映射到进程的地址空间}
  PShare := PShareMem(MapViewOfFile(HMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0));
  if PShare = nil then
  begin
    CloseHandle(HMapping);
    ShowMessage('映像文件在内存中不存在');
    Application.Terminate;
    Exit
  end;
end;

{关闭共享内存}
procedure CloseMap;
begin
  if PShare <> nil then
  begin
  {从进程的地址空间中撤销映像文件}
    UnmapViewOfFile(PShare);
  end;

  if HMapping <> 0 then
  begin
    {关闭映像文件}
    CloseHandle(HMapping);
  end;

end;

{建立互斥对象}
function LockMap: Boolean;
begin
  Result := True;
  HMapMutex := CreateMutex(nil, False, PChar('My MUTEX NAME COES HERE'));
  if HMapMutex = 0 then
  begin
    ShowMessage('不能创建互斥对象');
    Result := False;
  end
  else
  begin
    if WaitForSingleObject(HMapMutex, REQUEST_TIME_OUT) = WAIT_FAILED then
    begin
      ShowMessage('不能对互斥对象枷锁');
      Result := False;
    end;
  end;

end;

procedure UnlockMAP();
begin
  {释放、关闭互斥对象}
  ReleaseMutex(HMapMutex);
  CloseHandle(HMapMutex);
end;

procedure TForm1.btn1Click(Sender: TObject);
var
  str: PChar;
begin
  str := PChar('简单的共享内存示例');
  //把数据拷贝到共享内存
  CopyMemory(@(pshare^.Data), str, Length(str)*SizeOf(Char));
  //发送消息表明有数据
  PostMessage(FindWindowW(nil, 'Form2'), WM_DATA, 1, 2);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  OpenMap();
  LockMap();
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  UnlockMAP();
  CloseMap();
end;

end.

接收数据端

unit Unit2;

interface

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

const
  //自定义消息
  WM_DATA = WM_USER + 1024;

type
  PShareMem = ^TPShareMem;

  TPShareMem = record
    //共享数据    注意要与发送数据段的定义相同
    Data: array[0..255] of Char;
  end;

type
  TForm2 = class(TForm)
    mmo1: TMemo;
    btn1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure btn1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure getShareInfo(var Msg: TMessage); message WM_DATA;
  end;

var
  Form2: TForm2;
  PShare: PShareMem;
  MapHandle: THandle;

implementation

{$R *.dfm}

{ TForm2 }

{处理wm_data 自定义消息}
procedure TForm2.btn1Click(Sender: TObject);
begin
  CloseHandle(MapHandle);
end;

procedure TForm2.FormCreate(Sender: TObject);
begin
  MapHandle := OpenFileMapping(FILE_MAP_WRITE, False, PChar('MapName'));
  if MapHandle = 0 then
  begin
    ShowMessage('不能定位内存映像文件块');

  end;
  {将映像文件映射到进程的地址空间}
  PShare := PShareMem(MapViewOfFile(MapHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0));
  if PShare = nil then
  begin
    CloseHandle(MapHandle);
    ShowMessage('不能显示映像文件');
    Application.Terminate;
    Exit;
  end;
  FillChar(PShare^, SizeOf(TPShareMem), 0);
end;

procedure TForm2.getShareInfo(var Msg: TMessage);
begin
  {如果是发送数据端发过来的参数是1}
  if Msg.LParam = 2 then
  begin

    //显示共享内存中的数据
    mmo1.Text := PShare^.Data;
  end;
  PShare := PShareMem(MapViewOfFile(MapHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0));
  if PShare = nil then
  begin
    CloseHandle(MapHandle);
    ShowMessage('不能显示映像文件');
    Application.Terminate;
    Exit;
  end;
  FillChar(PShare^, SizeOf(TPShareMem), 0);
end;

end.

【Delphi下深入Windows编程】一书中CopyMemory使用的长度是Length(str),但是在Delphi增加了Unicode字符支持以后这样会导致长度计算不够,只发送半截字符

参考万一的博客中用ByteLength函数解决,但是也有人说,ByteLength函数只能对Unicode字符串求字节长度,如果要对Ansi字符串进行计算,那么结果会是正确值的两倍

最终解决方案:Length(str)*SizeOf(Char)

吐槽一下,在网上查资料的时候发现很多人的博客Delphi代码都没有高亮,看起来乱七八糟的,难道他们不知道Delphi使用的语言叫Pascal吗

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表