본문 바로가기

Software/Tools

[API] Process Communication[1]

 

Using Windows file mapping for inter-process communications, Part 1

by Mark G. Wiseman

There are times when you want two or more processes running on the same computer to carry on a simple conversation with each other. There are several ways to do this under Windows.

You could use Dynamic Data Exchange (DDE). This is an older methodology that has been in Windows for some time. It is a little clunky and can be difficult to use.

You could also use the Component Object Model (COM). This is more modern that DDE, but it is even more difficult to use.

Writing and reading data to and from a shared file would normally be too slow for most applications and might put excessive stress on the hard drive.

A shared space in memory would be ideal for simple inter-process communications. In Windows, shared memory is implemented through file mapping. In this article, I뭢 going to show you how to use file mapping to set up communication between two processes. In a follow-up article (Part 2) I뭠l discuss how to move the communication into its own thread and how to make the entire process more robust.

Demo programs

There are two demo programs that accompany this article. The source code for both of these programs can be found on the Bridges Publishing web site. The programs are a simple client and server.

The server program, Listings A and B, sets up a shared memory space using file mapping and waits for text to be placed into the memory by the client program, Listings C and D. Once the server finds some text in the shared memory it displays it. Figure A shows these programs in use.

Figure A

The client and server demo programs.

File mapping

In Windows, all memory allocated by Windows API functions such as GlobalAlloc() and LocalAlloc() can only be accessed by the process or application that allocated the memory. And, since C++ uses these underlying Windows API functions to implement the new operator, we can뭪 allocate shared memory using new either.

The Windows API does have functions that support file mapping. Using file mapping, your application (process) can treat a file as if it were a block of memory in the address space of the process. This way, you can use simple pointer operations to read and write to the file. The Windows API also allows two or more processes to share a file mapping. Each process has a pointer to a memory in its own address space; but the pointers are all actually maps to a file.

The file itself can be a named file on a disk or it can be part of the Windows paging file. We will use the Windows paging file for programs.

Creating a file mapping

To create a file mapping, you use the Window function CreateFileMapping(). The prototype is:

HANDLE CreateFileMapping(
   HANDLE hFile,
   LPSECURITY_ATTRIBUTES
      lpFileMappingAttributes,
   DWORD flProtect,
   DWORD dwMaximumSizeHigh,
   DWORD dwMaximumSizeLow,
   LPCTSTR lpName
   );

I will only discuss a two of the functions arguments. You can look up this, and the other functions, in the Windows online help provided with C++Builder.

The hFile argument is a handle to a file on disk. Since we are going to use the Windows page file, we use a special value of 0xFFFFFFFF for hFile. If you wanted to use a named file, hFile would be the file handle you got when you created the name file with the Windows API function CreateFile().

The lpName argument is a pointer to the name of the file mapping. This is different from any file name you might have used to create a file handle. This is an important name, because we are going to use this same name in both the client and the server demo programs so we can share the same file mapping. The name should be unique to these programs, but the same in both. I use the name "ReisdorphDemoFileMap" in the demo programs and actually create the file mapping in the server program.

When a file mapping has been created, you can open it in another process using the Windows API function OpenFileMapping() as I뭭e done in the client program.

Both CreateFileMapping() and OpenFileMapping() return handles to the file mapping if they are successful. To convert these handles to a memory pointer, both programs use the MapViewOfFile() function, which takes as one of its arguments the file handle and returns a void pointer to a memory location within the application뭩 memory space. I cast this void pointer to a char and use like any other pointer to char.

For these simple programs to work, the server application must be run first.

Communicating

I tried to make the communication between the two applications as simple as possible. The server program receives text messages from the client program. The server program uses a timer to periodically check the first byte of in the file mapping. If the byte is zero, nothing is done. If the byte is non-zero, the server application assumes that there is a text message sitting in the file mapping and it copies that text into a TMemo component for display. Finally, the server sets the first byte to zero, in effect resetting the file mapping.

The client program sends a text message to the server program by taking the text in a TMemo component and copying it to the memory address of the file mapping.

Cleaning up

When the client and server programs are finished, they have to do a little clean up. First the view of the file mapping is released using the UnmapViewOfFile() function and then the file-mapping handle is released using the CloseHandle() function.

Windows is smart enough to not actually release a file mapping until every program using it has released it. So, the order in which two demo programs are closed is not important.

Conclusion

The demonstration programs discussed in this article represent the simplest example of inter-process communication I could think of using file mapping. Although, they give you a good idea of how file mapping works, there is a lot missing that you need to incorporate to make file mapping truly robust and useful.

In particular, the communication should take place in a separate thread, and the communication should be synchronized to prevent clashes.

I뭠l discuss this in Part 2 and I뭠l also show you how to deal with a missing server or client.

Listing A: ServerMain.h

class TServerForm : public TForm
{
  __published:
    TMemo *Memo1;
    TButton *CloseBtn;
    TTimer *Timer;
      
    void __fastcall OnTimer(TObject *Sender);
    void __fastcall Exit(TObject *Sender);

  public:
    __fastcall TServerForm(TComponent* Owner);
    __fastcall ~TServerForm();

  private:
    char *memmap;
    HANDLE fmHandle;      
};

Listing B: ServerMain.cpp

#include <vcl.h>
#pragma hdrstop

#include "ServerMain.h"

#pragma package(smart_init)
#pragma resource "*.dfm"

TServerForm *ServerForm;

__fastcall TServerForm::TServerForm(
  TComponent* Owner) : TForm(Owner)
{
  Caption = Application->Title;

  fmHandle = CreateFileMapping(
    (HANDLE)0xFFFFFFFF, 0,
    PAGE_READWRITE, 0, 1024,
    "ReisdorphDemoFileMap");
  if (fmHandle == 0)
  {
    ShowMessage("Unable to create File Mapping.");
    Application->Terminate();
  }

  memmap =
    (char *)MapViewOfFile(fmHandle,
      FILE_MAP_ALL_ACCESS, 0, 0, 0);

  if (memmap == 0)
  {
    ShowMessage("Error!");
    Application->Terminate();
  }

  *memmap = 0;
}

__fastcall TServerForm::~TServerForm()
{
  if (memmap != 0)
    UnmapViewOfFile(memmap);

  if (fmHandle != 0)
    CloseHandle(fmHandle);
}

void __fastcall TServerForm::OnTimer(
  TObject *Sender)
{
  if (*memmap != 0)
  {
    Memo1->Lines->Text = memmap;
    *memmap = 0;
  }
}

void __fastcall TServerForm::Exit(TObject *Sender)
{
  Close();
}

Listing C: ClientMain.h

class TClientForm : public TForm
{
  __published:
    TMemo *Memo1;
    TButton *SendBtn;
    TButton *CloseBtn;
    void __fastcall Exit(TObject *Sender);
    void __fastcall Send(TObject *Sender);

  public:
    __fastcall TClientForm(TComponent* Owner);
    __fastcall ~TClientForm();

  protected:
    void __fastcall OnIdle(
      TObject *Sender, bool &Done);

  private:
    HANDLE fmHandle;
    char *memmap;
};

Listing D: ClientMain.cpp

__fastcall TClientForm::TClientForm(
   TComponent* Owner) : TForm(Owner)
{
  Caption = Application->Title;

  fmHandle =
    OpenFileMapping(FILE_MAP_ALL_ACCESS,
      false, "ReisdorphDemoFileMap");
  if (fmHandle == 0)
  {
    ShowMessage("Unable to create File Mapping.");
    Application->Terminate();
  }

  memmap =
    (char *)MapViewOfFile(fmHandle,
       FILE_MAP_ALL_ACCESS, 0, 0, 0);
  if (memmap == 0)
  {
    ShowMessage("Unable to map view of file.");
    Application->Terminate();
  }

  Application->OnIdle = OnIdle;
}

__fastcall TClientForm::~TClientForm()
{
  if (memmap != 0)
    UnmapViewOfFile(memmap);
  if (fmHandle != 0)
    CloseHandle(fmHandle);
}

void __fastcall TClientForm::Exit(TObject *Sender)
{
   Close();
}

void __fastcall TClientForm::Send(TObject *Sender)
{
  lstrcpy(memmap, Memo1->Text.c_str());
  Memo1->Clear();
}

void __fastcall TClientForm::OnIdle(
  TObject *Sender, bool &Done)
{
  SendBtn->Enabled =
    Memo1->Lines->Text.IsEmpty() == false;
}

Copyright ?2003, Baseline Grid Publications. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Baseline Grid Publications is prohibited. All other product names and logos are trademarks or registered trademarks of their respective owners.