您的位置:首页 > 其它

Use Named Pipes and Shared Memory for inter process communication with a child process or two

2014-01-14 18:55 489 查看

I wanted to inject some very low impact code that would run in any “parent” process, like Notepad or Excel or Visual Studio. I wanted to have some
User Interface for the data that my injected code gathered about the parent process, and that would work best in a different “child” process, preferably using WPF.

In the old days, I could call a COM server to handle the job. A DLL server would be in process, but it could be made out of process by making it a
COM+ application (see Blogs get 300 hits per hour: Visual FoxPro can count. and

Create multiple threads from within your application).

.Net Remoting
seemed to be a little heavyweight for Parent->Child process communication.

About 5 years ago, I wrote this:
Use Named Pipes to communicate between processes or machines, so I thought I’d use a combination of Named
Pipes and
Shared Memory. Luckily
.Net 3.5 added support for Named Pipes making the child process pretty simple.

Pipes could be used to send messages, and the lion’s share of data movement could be in the Shared memory.

Synchronization and lifetime management are a little tedious. We want the parent process to continue optionally if the child process terminates, but
we want the child to terminate automatically when the parent terminates for any reason. Similarly, the child process should terminate if the parent has gone.

This sample shows a parent process in C++ and 2 child processes in C# and VB. The parent spins off a thread to use to service incoming requests from
the children. Events are used to synchronize communication. A timer in each child process fires off requests to the parent.

I was using Visual Studio 2010: you can use VS 2008, but you’ll have to adjust for some of the new features I use, especially in the VB code.

Start Visual Studio. File->New->Project->C++ Win32 Project->Windows Application. In the wizard, Click Add common header files for ATL
Now hit F5 to build and see it execute: there’s a window and a couple menus.

Now add a second EXE project to your solution: choose File->Add->New Project->VB WPF Application.
(Repeat to add a 3rd project for C# !)

Fiddle with the Project->Properties->Compile->Build Output path so it builds into the same folder as the parent exe (for me, it was” ..\Debug\”)
Paste in the VB code below into MainWindow.Xaml.Vb

Somewhere inside the _tWinMain of your CPP project, add these 2 lines to instantiate a class that calls the WpfApplication as a child process, with
a shared memory size of 2048 (make sure to change the name of the EXE to match your VB and C# EXEs):

CreateChildProcess opCreateChildProcessCS(_T("NamedPipesCS.exe"),2048, 1);

CreateChildProcess opCreateChildProcessVB(_T("NamedPipesVB.exe"),2048, 2);

Paste the CPP code below before the _tWinMain.

F5 will show both processes launched. You can alt-tab between the 2: they behave like independent processes. Try terminating one of them.

If you uncomment the MsgBox, then hit F5, you can actually use VS to attach to a child process before it does too much. Try attaching to all 3!

See also:
Remove double spaces from pasted
code samples in blog

<C++ Code>
// CreateChildProcess : class in parent process to instantiate and communicate with a child process
// usage:
CreateChildProcess opCreateChildProcess(_T("WpfApplication1.exe"),2048);
class CreateChildProcess

{

HANDLE m_hChildProcess;// handle to the child process we create

HANDLE m_hNamedPipe; // handle to the named pipe the paren process creates

HANDLE m_hEvent;

HANDLE m_hThread; // thread in parent process to communicate with child

LPVOID m_pvMappedSection;

DWORD m_cbSharedMem;
public:

CreateChildProcess(TCHAR* szChildExeFileName,DWORD cbSharedMemSize,
int ChildNo )

{

m_cbSharedMem = cbSharedMemSize;

TCHAR szPipeName[1000];

TCHAR szEventName[1000];

swprintf_s(szPipeName, L"Pipe%d_%d", ChildNo, GetCurrentProcessId());
//make the names unique per child and per our (parent) process

swprintf_s(szEventName,L"Event%d_%d", ChildNo, GetCurrentProcessId());
//

SECURITY_ATTRIBUTES SecurityAttributes = {

sizeof( SECURITY_ATTRIBUTES ),
// nLength

NULL, // lpSecurityDescriptor. NULL = default for calling process

TRUE
// bInheritHandle

};

HANDLE hFileMapping = CreateFileMapping(

INVALID_HANDLE_VALUE, // backed by paging file

&SecurityAttributes,

PAGE_READWRITE,

0,

static_cast< DWORD >(m_cbSharedMem),
// Truncation in 64-bit

NULL);

m_pvMappedSection = MapViewOfFile( hFileMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0 );

swprintf_s((TCHAR *)m_pvMappedSection, m_cbSharedMem,_T("here i am in shared mem %d"), 1);

CComBSTR bstrFullPipeName(L"\\\\.\\pipe\\");

bstrFullPipeName.Append(szPipeName); // the pipe name is "\\.\pipe\MyName"

m_hNamedPipe = CreateNamedPipe(

bstrFullPipeName,

PIPE_ACCESS_DUPLEX + FILE_FLAG_FIRST_PIPE_INSTANCE,

PIPE_TYPE_MESSAGE + PIPE_WAIT,

PIPE_UNLIMITED_INSTANCES,

100, //nOutBufferSize

100, //nInBufferSize

100, // nDefaultTimeout

0); // lpSecurityAttributes

m_hEvent = CreateEvent(

&SecurityAttributes,

FALSE, //bManualReset

FALSE, //bInitialState

szEventName); // name

STARTUPINFO StartupInfo = { sizeof(STARTUPINFO) };

PROCESS_INFORMATION ProcessInformation = {};

TCHAR achCommandLine[MAX_PATH*2];

// like WpfApplication1.exe 92 Event2648 Pipe2648 2048

swprintf_s( achCommandLine, _T("%s %d %s %s %d"),szChildExeFileName, hFileMapping, szPipeName, szEventName, m_cbSharedMem);

if( !CreateProcess(

NULL,

achCommandLine,

NULL,

NULL,

TRUE, // inherit handles

CREATE_DEFAULT_ERROR_MODE | NORMAL_PRIORITY_CLASS,

NULL,

NULL,

&StartupInfo,

&ProcessInformation))

{

OutputDebugString(L"failed to create process");

return;

}

::CloseHandle( ProcessInformation.hThread );// We don't need the thread handle

DWORD dwThreadId = 0;

m_hChildProcess = ProcessInformation.hProcess;

// Create the actual background thread in the parent process

m_hThread = CreateThread(NULL, // no security

0, // default stack size

CreateChildProcess::ThreadProc, // initial method

(void *)static_cast<CreateChildProcess *>(this),
// parameter to the thread proc

0, // run immediately

&dwThreadId);

//DWORD res = ::WaitForSingleObject( hThread, INFINITE );

return ;

}

static DWORD WINAPI ThreadProc(void *pvThreadObject)

{

CreateChildProcess *pCreateChildProcess = static_cast<CreateChildProcess *>(pvThreadObject);

BOOL fDone = false;

while (!fDone)

{

DWORD res = ::WaitForSingleObject( pCreateChildProcess ->m_hEvent, 2000 );

switch(res)

{

case WAIT_OBJECT_0:
// the event signalled: we got a request

{

DWORD nTotBytesAvail = 0;

PeekNamedPipe(pCreateChildProcess ->m_hNamedPipe,0,0,0,&nTotBytesAvail,0);

if (nTotBytesAvail != 0)

{

char data[1000];

DWORD nRead = 0;

//read the msg

if (ReadFile(pCreateChildProcess->m_hNamedPipe,data,nTotBytesAvail, &nRead,NULL))

{

data[nTotBytesAvail] = 0; //null terminate

CComBSTR bstrdat( data);

OutputDebugString(bstrdat);

OutputDebugString(L"\n");

if (_wcsnicmp(L"Quit",bstrdat,4)==0)

{

OutputDebugString(L"Child Proc sent Quittin' time msg\n");

fDone=true;

exit(0);

}

// write some stuff to shared mem

swprintf_s(

(wchar_t *)pCreateChildProcess->m_pvMappedSection,

pCreateChildProcess->m_cbSharedMem,

L"Written to shared mem %s", bstrdat);

// send a msg to the child

data[0]='C';

data[1]='+';

data[2]='+';

DWORD nBytesWritten= 0;

WriteFile( pCreateChildProcess->m_hNamedPipe, data, nTotBytesAvail,&nBytesWritten,0);

}

}

}

break;

case WAIT_TIMEOUT:

// test to see if child process still alive

if (WaitForSingleObject(pCreateChildProcess->m_hChildProcess,0) == WAIT_OBJECT_0)
// did the proc terminate?

{

fDone = true;

}

break;

default:

fDone=true;

}

}

// interprocess comm is over

CloseHandle(pCreateChildProcess->m_hChildProcess);

pCreateChildProcess->m_hChildProcess = INVALID_HANDLE_VALUE;

CloseHandle(pCreateChildProcess->m_hThread);

CloseHandle(pCreateChildProcess->m_hNamedPipe);

return 0;

}
};

</C++ Code>

<VB Code>

Imports System.IO.Pipes
Imports System.Runtime.InteropServices
Imports System.Windows.Threading
Imports System.Text

Module
NativeImports

Public Const FILE_MAP_READ
As Int32 = &H4

Public Const FILE_MAP_WRITE
As Int32 = &H2

Declare Function MapViewOfFile
Lib "kernel32" (ByVal hFileMappingObject
As IntPtr,
ByVal dwDesiredAccess As
UInt32, ByVal dwFileOffsetHigh
As UInt32,
ByVal dwFileOffsetLow As
UInt32, ByVal dwNumberOfBytesToMap
As UInt32)
As UInt32
End
Module

Class
MainWindow

Private WithEvents _timer
As New
DispatcherTimer With {.Interval =
TimeSpan.FromSeconds(2)}

Private _pipestream
As NamedPipeClientStream

Private _event As System.Threading.EventWaitHandle

Private _sharedMemAddr
As UInteger

Private _sharedMemSize
As Integer

Private _txtStatus
As New
TextBox With {

.AcceptsReturn = True,

.AcceptsTab = True,

.VerticalScrollBarVisibility = ScrollBarVisibility.Auto

}

Private Sub Window_Loaded(ByVal sender
As System.Object,
ByVal e As System.Windows.RoutedEventArgs)
Handles MyBase.Loaded

' MsgBox("attach a debugger", "VB")

Me.Title =
"VB Child Process"

Me.Content = _txtStatus

Dim args = System.Environment.GetCommandLineArgs

If args.Count < 4
Then

MessageBox.Show("No args")

Close()

Return

End If

Dim hFileMapping =
CInt(args(1))

_sharedMemAddr = MapViewOfFile(hFileMapping, FILE_MAP_READ
Or FILE_MAP_WRITE, 0, 0, 0)

Dim pipename = args(2)

_pipestream = New
NamedPipeClientStream(".", pipename,

PipeDirection.InOut,

PipeOptions.Asynchronous)

Dim eventname = args(3)

_event = System.Threading.ManualResetEvent.OpenExisting(eventname)

_sharedMemSize = CInt(args(4))

_pipestream.Connect()

UpdateStatus("start: shared mem " + ReadSharedMem())

_timer.Start()

End Sub

Shared _numticks
As Integer

Private _byteEncoding
As New
UTF8Encoding

Private _IsClosed =
False

Private _decoder = _byteEncoding.GetDecoder

Sub OnTimerTick()
Handles _timer.Tick

Try

If _pipestream
Is Nothing Or
Not _pipestream.IsConnected
Then

DoForceClose()

Return

End If

SendMsg("WPFClient msg" + _numticks.ToString +
" " +
DateTime.Now.ToLongTimeString)

_numticks += 1

Dim str = GetMsg()

UpdateStatus("Client got data: " + str)

Dim ss = ReadSharedMem()

UpdateStatus("shared mem: " + ss)

Catch ex As IO.IOException
' a named pipe IO op failed

DoForceClose()

Catch ex As
Exception

DoForceClose()

End Try

End Sub

Sub DoForceClose()

If Not _IsClosed
Then

_IsClosed = True

_timer = Nothing

_pipestream = Nothing

UpdateStatus("Disconnected")

Me.Close()

End If

End Sub

Sub SendMsg(ByVal szMsg
As String)

Dim barray = _byteEncoding.GetBytes(szMsg)

_pipestream.Write(barray, 0, barray.Length)

_event.Set()

End Sub

Function GetMsg()
As String

Dim str =
""

Dim barrRead(90)
As Byte

Dim charsRead(90)
As Char

Dim nBytesRead = _pipestream.Read(barrRead, 0, barrRead.Length)

Dim nChars = _decoder.GetChars(barrRead, 0, nBytesRead, charsRead, 0)

str = New String(charsRead, 0, nChars)

Return str

End Function

Function ReadSharedMem()
As String

Dim sResult =
""

For i = 0 To _sharedMemSize
Step 2 ' unicode

Dim aaddr = New
IntPtr(_sharedMemAddr + i)

Dim aByte =
Marshal.ReadByte(aaddr)

If aByte = 0 Then

Exit For

End If

sResult += Chr(aByte)

Marshal.WriteByte(aaddr, aByte + 1)
' demo: change the mem we just read: under a debugger you can see that shared mem was written

Next

Return sResult

End Function

Private Sub UpdateStatus(ByVal newStat
As String)

newStat = DateTime.Now.ToString +
" " + newStat + vbCrLf

Debug.Write(newStat)

Me._txtStatus.AppendText(newStat)

Me._txtStatus.ScrollToEnd()

End Sub

Sub On_Closed() Handles
Me.Closed

_timer = Nothing

If Not _IsClosed
Then

SendMsg("Quit")

End If

If _pipestream IsNot
Nothing Then

_pipestream.Dispose()

_pipestream = Nothing

End If

End Sub
End
Class

</VB Code>

<C# Code>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO.Pipes;
using System.Runtime.InteropServices;

namespace NamedPipesCS
{

/// <summary>

/// Interaction logic for MainWindow.xaml

/// </summary>

public partial
class MainWindow :
Window

{

[DllImport("kernel32.dll", SetLastError =
true)]

static extern
IntPtr MapViewOfFile(

IntPtr hFileMappingObject,

FileMapAccess dwDesiredAccess,

uint dwFileOffsetHigh,

uint dwFileOffsetLow,

uint dwNumberOfBytesToMap);

[Flags]

public enum
FileMapAccess :
uint

{

FileMapCopy = 0x0001,

FileMapWrite = 0x0002,

FileMapRead = 0x0004,

FileMapAllAccess = 0x001f,

fileMapExecute = 0x0020,

}

private
TextBox _txtStatus;

private
IntPtr _sharedMemAddr;

private
NamedPipeClientStream _pipestream;

private System.Threading.EventWaitHandle _event;

private int _sharedMemSize;

private System.Windows.Threading.DispatcherTimer _timer;

static int _numticks;

private
UTF8Encoding _byteEncoding = new
UTF8Encoding();

private bool _IsClosed;

private
Decoder _decoder;

public MainWindow()

{

//MessageBox.Show("attach a debugger","C#");

InitializeComponent();

_decoder = _byteEncoding.GetDecoder();

}

private void Window_Loaded(object sender,
RoutedEventArgs e)

{

_txtStatus = new
TextBox();

_txtStatus.AcceptsReturn = true;

_txtStatus.AcceptsTab = true;

_txtStatus.VerticalScrollBarVisibility =
ScrollBarVisibility.Auto;

this.Title =
"C# Child Process";

this.Content = _txtStatus;

var args =
Environment.GetCommandLineArgs();

if (args.Count() < 4)

{

MessageBox.Show("No args");

Close();

return;

}

var hFileMapping =
int.Parse(args[1]);

_sharedMemAddr = MapViewOfFile(new
IntPtr(hFileMapping), FileMapAccess.FileMapRead |
FileMapAccess.FileMapWrite, 0, 0, 0);

var pipeName = args[2];

_pipestream = new System.IO.Pipes.NamedPipeClientStream(".", pipeName, System.IO.Pipes.PipeDirection.InOut,
System.IO.Pipes.PipeOptions.Asynchronous);

var Eventname = args[3];

_event = System.Threading.ManualResetEvent.OpenExisting(Eventname);

_sharedMemSize = int.Parse(args[4]);

_pipestream.Connect();

UpdateStatus(string.Format("start: shared mem {0}" , ReadSharedMem()));

_timer = new System.Windows.Threading.DispatcherTimer();

_timer.Interval = TimeSpan.FromSeconds(2);

_timer.Tick += new
EventHandler(OnTimerTick);

_timer.Start();

this.Closed += new
EventHandler(on_Closed);

}

void OnTimerTick(Object o,
EventArgs e)

{

try

{

if (_pipestream ==
null || !_pipestream.IsConnected)

{

DoForceClose();

return;

}

SendMsg(String.Format("WPFClient msg {0} {1}" , _numticks,
DateTime.Now.ToLongTimeString()));

_numticks += 1;

var str = GetMsg();

UpdateStatus(String.Format("Client got data: {0}" , str));

var ss = ReadSharedMem();

UpdateStatus(String.Format("shared mem: {0}" , ss));

}

catch (Exception)

{

DoForceClose();

}

}

void DoForceClose()

{

if (!_IsClosed)

{

_IsClosed = true;

_timer = null;

_pipestream = null;

UpdateStatus("Diconnected");

Close();

}

}

void SendMsg(string szMsg)

{

var barray = _byteEncoding.GetBytes(szMsg);

_pipestream.Write(barray, 0, barray.Length);

_event.Set();

}

string GetMsg()

{

var str =
"";

var barrRead =new
Byte[90];

var charsRead = new
char[90];

var nByteRead = _pipestream.Read(barrRead, 0, barrRead.Length);

var nChars = _decoder.GetChars(barrRead, 0, nByteRead, charsRead, 0);

str = new string(charsRead, 0, nChars);

return str;

}

String ReadSharedMem()

{

var sResult =
"";

for (int i = 0; i < _sharedMemSize; i += 2)

{

var aaddr = _sharedMemAddr + i;

var aByte =
Marshal.ReadByte(aaddr);

if (aByte == 0)

{

break;

}

sResult += Convert.ToChar(aByte);

Marshal.WriteByte(aaddr,(byte)(aByte+(byte)1));// ' demo: change the mem we just read: under a debugger you can see that
shared mem was written

}

return sResult;

}

private void UpdateStatus(String newStat)

{

newStat =string.Format("{0} {1}\n",
DateTime.Now.ToString(),newStat);

System.Diagnostics.Debug.Write(newStat);

_txtStatus.AppendText(newStat);

_txtStatus.ScrollToEnd();

}

void on_Closed(Object o,
EventArgs e)

{

_timer = null;

if (!_IsClosed)

{

SendMsg("Quit");

}

if (_pipestream !=
null)

{

_pipestream.Dispose();

_pipestream = null;

}

}

}
}

</C# Code>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐