Run several copies of some program from c++ program - c++

What I need to do is to run (for example) 6 copies (independent processes) of example.exe (what is also my program) with different command line arguments from my Main program. This copies should work at the same time. I'm using code like this:
const int NumberOfProcesses= 6;
STARTUPINFO si[NumberOfProcesses];
PROCESS_INFORMATION pi[NumberOfProcesses];
srand((unsigned)time(NULL));
for(int i=0;i<NumberOfProcesses;i++)
{
char fname[MAX_PATH];
strncpy(fname,"\"",1);
fname[1] = '\0';
strcat(fname,"d:\\test\\example.exe");
strcat(fname,"\"");
int id = i;
strcat(fname," ");
strcat(fname,(std::to_string(id)).c_str());
int count = (rand()%1000) + 1;
strcat(fname," ");
strcat(fname,(std::to_string(count)).c_str());
int lb = 13;
strcat(fname," ");
strcat(fname,(std::to_string(lb)).c_str());
int ub = 666;
strcat(fname," ");
strcat(fname,(std::to_string(ub)).c_str());
printf(fname);
cout<<"\n";
//Here in fname I have correct command, that runs properly
bool t = false;
t=CreateProcess( NULL, // No module name (use command line)
(LPSTR)fname, // Command line CharToLPWSTR(fname2)
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si[i], // Pointer to STARTUPINFO structure
&pi[i] ); // Pointer to PROCESS_INFORMATION structure
}
So if NumberOfProcesses==0 it will run "d:\test\example.exe" 1 2 3 4 from fname. But if NumberOfProcesses==6 (or something else) zero iteration will complete properly, but others will return false. Sometimes 4th iteration runs properly.
I think this is because when zero iteration run "d:\test\example.exe" 1 2 3 4 the example.exe is busy and can not be run one more time. So I changed code to this:
bool t = false;
getchar();
for(int i=0;i<5;i++)
{
t=CreateProcess( NULL, // No module name (use command line)
(LPSTR)fname, // Command line CharToLPWSTR(fname2)
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si[i], // Pointer to STARTUPINFO structure
&pi[i] ); // Pointer to PROCESS_INFORMATION structure
if(t) break;
}
So I got some delay between starting example.exe - and it helped. All 6 copies starts and finishes, but they don't run parallel (I have output from example.exe).
But this is not the way I want my program work.
How can I avoid this problem?
Thanks.
UPD.
According to Werner Henze's answer I've just add couple of lines (to initialize STURTUPINFO) into loop
const int NumberOfProcesses= 6;
STARTUPINFO si[NumberOfProcesses];
PROCESS_INFORMATION pi[NumberOfProcesses];
for(int i=0;i<NumberOfProcesses;i++)
{
///Next two lines is important
ZeroMemory( &si[i], sizeof(si[i]) );
si[i].cb = sizeof(si);
/*Some actions*/
t=CreateProcess( NULL, // No module name (use command line)
(LPSTR)fname, // Command line CharToLPWSTR(fname2)
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si[i], // Pointer to STARTUPINFO structure
&pi[i] ); // Pointer to PROCESS_INFORMATION structure
}
And it is work fine now.
Thanks again.

In the cases where CreateProcess returns FALSE you should check GetLastError(). In the code that you posted you are not initializing the si array. STARUPINFO is an input paramter to CreateProcess, so you must initialize it before passing it to CreateProcess.
I guess example.exe is running so fast that they are terminated before you come to look at them. If you printf a timing value like GetTickCount() after each CreateProcess, you will see that all CreateProcess calls occur very fast.

Related

How to run an exe with multiple arguments with CreateProcessW in C++

I have tried to use this example to run an external program using CreateProcessW() in C++, however, when I use multiple arguments this code seems to not work.
In my case, I pass the following path:
std::string pathToExe = "C:\\Users\\Aitor - ST\\Documents\\QtProjects\\ErgoEvalPlatform\\ErgonomicEvaluationPlatform\\FACTS\\xsim-runner.exe"
and the following arguments:
std::string arguments = "--model=facts_input.xml --output_xml=something.xml"
These parameters work from cmd, but they seem to not give any output (an xml should appear in the same folder) when I use them from C++.
Is there something I might be missing?
The following is an example for showing "How to run an exe with multiple arguments with CreateProcessW in C++". You can check if it helps.
The launcher application (a console app):
#include <iostream>
#include <windows.h>
int main()
{
STARTUPINFO si;
PROCESS_INFORMATION pi; // The function returns this
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
CONST wchar_t* commandLine = TEXT("arg1 arg2 arg3");
// Start the child process.
if (!CreateProcessW(
L"D:\\Win32-Cases\\TestTargetApp\\Debug\\TestTargetApp.exe", // app path
(LPWSTR)commandLine, // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi) // Pointer to PROCESS_INFORMATION structure
)
{
printf("CreateProcess failed (%d).\n", GetLastError());
throw std::exception("Could not create child process");
}
else
{
std::cout << "[ ] Successfully launched child process" << std::endl;
}
}
The target application (another console app) that will be launched:
#include <iostream>
#include <windows.h>
int main(int argc, char *argv[])
{
if (argc > 0)
{
for (int index = 0; index < argc; index++)
{
std::cout << argv[index] << std::endl;
}
}
return 1;
}
There's two potential problems I can infer from the code you're showing.
Space before the arguments
Depending on how you're concatenating the arguments string to the executable string, you may miss a space before the arguments. Without the code, it's impossible to tell, but try changing the arguments string like this :
std::string arguments = " --model=facts_input.xml --output_xml=something.xml;"
Current directory
CreateProcess spawns a child process that inherits the current directory from it's parent process. The XML files you specify on the arguments use relative paths.
Try specifying the full path of the XML files you're passing in the arguments, something like this :
std::string arguments = " --model=\"C:\\Users\\Aitor - ST\\Documents\\QtProjects\\ErgoEvalPlatform\\ErgonomicEvaluationPlatform\\FACTS\\facts_input.xml\" --output_xml=\"C:\\Users\\Aitor - ST\\Documents\\QtProjects\\ErgoEvalPlatform\\ErgonomicEvaluationPlatform\\FACTS\\something.xml\"";
You have to pass the complete command line in the arguments as under:
std::string arguments = "C:\\Users\\Aitor-ST\\Documents\\QtProjects\\ErgoEvalPlatform\\ErgonomicEvaluationPlatform\\FACTS\\xsim-runner.exe --model=facts_input.xml --output_xml=something.xml"
The second parameter of CreateProcessW requires complete command line and not just arguments. It passes this to the process and if the target process is a C program taking agrs, then as usual the first parameter will be module name and others that follow will be args.
Hope this helps

Using ShellExecute to send sqlite3.exe a shell script

I've got the following shell script in file c:/SQLiteData/Cmnds.txt
.open c:/SQLiteData/LGMeta.db
create table Temp {f INTEGER primary key};
insert into Temp values(-1);
.output c:/SQLiteData/Out.txt
select * from Temp;
I tried running it inside a c++ programme using
ShellExecute(NULL, L"open", L"c:/SQLiteData/sqlite3.exe",
L".read c:/SQLiteData/Cmnds.txt", NULL, 0);
ShellExecute returns 42 which suggests success but nothing happens. Neither Temp or Out.txt are created. Can anyone tell me what I'm missing?
EDIT
Thanks for the replies. I've spent the last 3 days tearing my hair out with this stuff. I patched this together from several posts on the subject
unsigned int RunCmnd(String CmndLine)
{
STARTUPINFO StartupInfo;
PROCESS_INFORMATION ProcessInfo;
memset(&ProcessInfo,0,sizeof(ProcessInfo)); // setup memory blocks
memset(&StartupInfo,0,sizeof(StartupInfo));
StartupInfo.cb=sizeof(StartupInfo); // set structure size
StartupInfo.wShowWindow=SW_HIDE; // hide window
if (CreateProcess(NULL,CmndLine.c_str(),
NULL,NULL,false,0,NULL,NULL,&StartupInfo,&ProcessInfo))
{
WaitForSingleObject(ProcessInfo.hThread,INFINITE);
return 0;
}
else return GetLastError();
}
If I start up the command shell and enter the following line
c:/SQLiteData/sqlite3.exe < c:/SQLiteData/Cmnds.txt
everything works as expected BUT if use
RunCmnd("c:/SQLiteData/sqlite3.exe < c:/SQLiteData/Cmnds.txt")
from within my c++ builder app nothing happens. I'm missing something fundamental here. Can anyone tell me what?
Finally came up with this which was adapted from an excellent post by TarmoPikaro on this thread
How to execute a command and get output of command within C++ using POSIX?.
String TempFileName(bool ForwardSlash) // ForwardSlash default = true
{
wchar_t Nm[MAX_PATH],Path[MAX_PATH];
GetTempPath(MAX_PATH,Path);
if (!GetTempFileName(Path,L"",0,Nm))
throw Exception(String("TempFileName failed - ")+
SysErrorMessage(GetLastError()));
String Name=Nm;
if (ForwardSlash)
for (int Len=Name.Length(),i=1; i<=Len; i++) if (Name[i]=='\\') Name[i]='/';
return Name;
}
//---------------------------------------------------------------------------
String SQLiteExe(String DB,String OutFile,String Cmds)
{
// Returns first error message if process doesn't execute cleanly. Otherwise
// if OutFile=="" it returns the output
// if Outfile>"" the output is sent to OutFile and return is NULL.
String Output;
HANDLE hPipeRead,hPipeWrite;
SECURITY_ATTRIBUTES saAttr={sizeof(SECURITY_ATTRIBUTES)};
saAttr.bInheritHandle=TRUE; //Pipe handles are inherited by child process.
saAttr.lpSecurityDescriptor=NULL;
// Create a pipe to get output from child's stdout.
if (!CreatePipe(&hPipeRead,&hPipeWrite,&saAttr,0))
return "Error: Unable to create pipe";
STARTUPINFO si={sizeof(STARTUPINFO)};
si.dwFlags=STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.hStdOutput=hPipeWrite;
si.hStdError=hPipeWrite;
si.wShowWindow=SW_HIDE;
// Prevents cmd window from flashing. Requires STARTF_USESHOWWINDOW in dwFlags.
TStringList *Batch=new TStringList;
Batch->StrictDelimiter=true;
Batch->Delimiter=';';
Batch->DelimitedText=Cmds;
if (OutFile>"") Batch->Insert(0,".output "+OutFile);
String S;
for (int i=0; i<Batch->Count; i++)
{
Batch->Strings[i]=Batch->Strings[i].Trim();
// .commands must have dot as first char on line
if (Batch->Strings[i]=="") continue;
S+=Batch->Strings[i]+(Batch->Strings[i][1]=='.' ? "" : ";")+"\r\n";
}
Batch->Text=S;
String BatchFile=TempFileName();
Batch->SaveToFile(BatchFile);
delete Batch;
String Cmd="sqlite3 "+DB+" \".read "+BatchFile+"\""; // assumes sqlite3.exe in PATH
PROCESS_INFORMATION pi={0};
BOOL fSuccess=CreateProcessW(NULL,Cmd.c_str(),NULL,NULL,TRUE,CREATE_NEW_CONSOLE,
NULL,NULL, &si, &pi);
if (! fSuccess)
{
CloseHandle(hPipeWrite);
CloseHandle(hPipeRead);
DeleteFile(BatchFile);
return "Error: Failed to create process";
}
bool bProcessEnded=false;
while (!bProcessEnded)
{
// Give some timeslice (50ms), so we won't waste 100% cpu.
bProcessEnded=WaitForSingleObject(pi.hProcess,50)==WAIT_OBJECT_0;
// Even if process exited - we continue reading, if there is some data available
// over pipe.
while (true)
{
char buf[1024];
DWORD dwRead=0;
DWORD dwAvail=0;
if (!::PeekNamedPipe(hPipeRead,NULL,0,NULL,&dwAvail,NULL)) break;
if (!dwAvail) break; // no data available, return
if (!::ReadFile(hPipeRead,buf,std::min(sizeof(buf)-1,
(unsigned int)(dwAvail)),&dwRead,NULL) || !dwRead)
break; // error, the child process might ended
buf[dwRead]=0;
Output+=buf;
}
int p=Output.Pos("Error:"); // if (p) return first error message
if (p) {Output.Delete(1,p-1); Output.Delete(Output.Pos("\r\n"),Output.Length());}
}
DeleteFile(BatchFile); // NB can't be deleted until ProcessEnded
CloseHandle(hPipeWrite);
CloseHandle(hPipeRead);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return Output;
}
//---------------------------------------------------------------------------
Usage
String Err=SQLiteExe("c:/SQLiteData/MyDB.db","c:/SQLiteData/MyTblDump.txt"",
".mode csv; select * from MyTbl;");
if (Err>"") throw Exception(Err);
else ........
Multiple commands should be separated by semi-colons. SQLiteExe automatically removes the redundant semi-colons at the end of lines starting with a dot.

Error "<url> is not recognized as an internal or external command, operable program or batch file."

My code should take arguments, put "+" inbetween them, and search google chrome with this, however I get the error (Command line Argument= "Stack Overflow Site"):
http://www.google.com/search?q=Stack+Overflow+Site'C:\Program' is not
recognized as an internal or external command, operable program or
batch file.
Also in my program I get this error:
error C4996: 'strcpy': This function or variable may be unsafe.
Consider using strcpy_s instead. To disable deprecation, use
_CRT_SECURE_NO_WARNINGS. See online help for details. c:\users\user\documents\visual studio
2013\projects\project1\project1\main.cpp
I have been ignoring this, because I thought it was just a warning, but I'm not sure if it is relevant.
My code:
#include <Windows.h>
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char** argv){
//Loop through arguments and put a "+" between them.
string out = "";
for (int i = 1; i < argc; ++i) {
if (i != 1){
out += "+";
}
out += argv[i];
}
string newout = "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe \"http://www.google.com/search?q=" + out + "\"";
// set the size of the structures
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
//set y to newout and convert
char *y = new char[newout.length() + 1];
strcpy(y, newout.c_str());
//Run Google Chrome with argument
CreateProcessA("C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe", // the path
y, // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure
);
delete[] y;
cin.ignore();
}
I suspect you are not getting exactly the same error since you
stopped deleting the commandline buffer before using it, but
some different, equally unexpected result.
If you will read the documentation of CreateProcess
you will learn that the parameters:
_In_opt_ LPCTSTR lpApplicationName,
_Inout_opt_ LPTSTR lpCommandLine,
can be supplied in one of three ways, as illustrated:-
Way 1
lpApplicationName = "\path\to\executable"
lpCommandLine = "args for the executable"
which results in a process initiated with the command:
\path\to\executable args for the executable
Way 2
lpApplicationName = NULL
lpCommandLine = "\path\to\executable args for the executable"
which results in the same process as Way 1
Way 3
lpApplicationName = "\path\to\executable"
lpCommandLine = NULL
which results in a process initiated with the command \path\to\executable.
You are not using Way 1 or Way 2 or Way 3, but:
lpApplicationName = "\path\to\executable"
lpCommandLine = "\path\to\executable args for the executable"
which in your case results in a process initiated with the command:
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe \
C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe \
http://www.google.com/search?q=Stack+Overflow+Site"
in which the arguments passed to Chrome are:
C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe \
http://www.google.com/search?q=Stack+Overflow+Site"
Of course this does not have the anticipated outcome.
Should you wish to correct this by adopting Way 2, bear particularly
in mind what the documentation says about lpCommandLine:
...
If lpApplicationName is NULL, the first white space–delimited token of the command line specifies the module name.
...

How do I pass an std::string environment block to CreateProcess?

I'm currently trying to use CreateProcess with the Path, Arguments and Environment Variables. My variables are stored in strings.
In the below example filePath and cmdArgs work fine, but I cannot get the envVars to work.
std::string filePath = "C:\\test\\DummyApp.exe";
std::string cmdArgs = "Arg1 Arg2 Arg3";
std::string envVars = "first=test\0second=jam\0"; // One
//LPTSTR testStr = "first=test\0second=jam\0"; // Two
CreateProcess(
LPTSTR(filePath.c_str()), //path and application name
LPTSTR(cmdArgs.c_str()), // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
TRUE, // Set handle inheritance
0, // Creation flags
LPTSTR(envVars.c_str()), // environment block
//testStr //this line works
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi ) // Pointer to PROCESS_INFORMATION structure
)
When I run this code the error that comes back is "error 87: The parameter is incorrect".
What I don't understand is that if I comment out the line labeled "one" and replace it with the line labeled "two" (and make the matching swap in the function call) then it works correctly.
The constructor of std::string you used will copy "first=test\0second=jam\0" until first \0 (C-style string).
To pass all the string use another constructor:
std::string envVars("first=test\0second=jam\0", 22);
^^^^^^^^^^^^^^^^^^^^^^^^ ^
|
22 characters -------+

Asynchronous I/O with Named Pipes WinAPI

Ok, I have asked a few questions about different facets of trying to accomplish what I want to do. This time I'm having big issues just reading from a named pipe. I think I have harvested enough information to possibly complete the project I am working on if I can set this up properly. I will include all relevant code below, but my mission is this: read the output (continuously) from a program I did not write and post it to the WinAPI. So my problem is that I have just switched from anonymous pipes to named pipes and I am having issues trying to properly set them up so I can retrieve information. I have a framework setup based off of an example from the MSDN.
#define WAIT_TIME 2 // 2s
#define INSTANCES 4 // Number of threads
#define CONNECT_STATE 0
#define READ_STATE 1
#define WRITE_STATE 2
#define WORLDRD 0
#define WORLDWR 1
#define WORLDINRD 2
#define WORLDINWR 3
#define BUFSIZE 0x1000 // Buffer size 4096 (in bytes)
#define PIPE_TIMEOUT 0x1388 // Timeout 5000 (in ms)
void Arc_Redirect::createProcesses()
{
TCHAR programName[]=TEXT("EXEC_PROGRAM.exe");
PROCESS_INFORMATION pi;
STARTUPINFO si;
BOOL bSuccess = FALSE;
ZeroMemory(hEvents,(sizeof(hEvents)*INSTANCES));
ZeroMemory(outStd,(sizeof(PIPE_HANDLES)*INSTANCES));
// Prep pipes
for(int i=0;i<INSTANCES;i++)
{
hEvents[i] = ::CreateEvent(NULL, TRUE, FALSE, NULL);
if(hEvents[i] == NULL)
throw "Could not init program!";
outStd[i].o1.hEvent = hEvents[i];
outStd[i].hPipeInst = ::CreateNamedPipe(
TEXT("\\\\.\\pipe\\arcworld"), PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES, BUFSIZE*sizeof(TCHAR), BUFSIZE*sizeof(TCHAR), PIPE_TIMEOUT, NULL);
if(outStd[i].hPipeInst == INVALID_HANDLE_VALUE)
throw "Could not init program!";
outStd[i].pendingIO = getState(outStd[i].hPipeInst,&outStd[i].o1);
outStd[i].dwState = outStd[i].pendingIO ?
CONNECT_STATE : READ_STATE;
}
// Set stuff up
ZeroMemory( &pi, sizeof(PROCESS_INFORMATION));
ZeroMemory( &si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.hStdError = outStd[WORLDRD].hPipeInst;
si.hStdOutput = outStd[WORLDRD].hPipeInst;
si.hStdInput = outStd[WORLDINWR].hPipeInst;
si.dwFlags |= STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES|FILE_FLAG_OVERLAPPED;
// Start our process with the si info
CreateProcess(programName,NULL,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);
}
BOOL Arc_Redirect::getState(HANDLE hPipe, LPOVERLAPPED lpo)
{
BOOL connected, pendingIO = FALSE;
// Overlap connection for this pipe
connected = ::ConnectNamedPipe(hPipe,lpo);
if(connected)
throw "ConnectNamedPipe(); failed!";
switch(GetLastError())
{
case ERROR_IO_PENDING:
pendingIO = TRUE;
break;
case ERROR_PIPE_CONNECTED:
if(SetEvent(lpo->hEvent))
break;
default:
throw "ConnectNamedPipe(); failed!";
break;
}
return pendingIO;
}
outStd[INSTANCES] is defined as PIPE_HANDLES (a custom struct) which is below
typedef struct
{
HANDLE hPipeInst;
OVERLAPPED o1;
TCHAR chReq[BUFSIZE];
TCHAR chReply[BUFSIZE];
DWORD dwRead;
DWORD dwWritten;
DWORD dwState;
DWORD cbRet;
BOOL pendingIO;
} PIPE_HANDLES, *LPSTDPIPE;
Now from here is where I am getting a bit lost. I'm not sure where to go. I tried using the loop in the MSDN example, but it didn't work properly for what I am looking to do. I need to take the read end of the pipe and retrieve the information (again, continuously) while having a write end opened as well for whenever I may need to write to it. Does anyone have any ideas? I have been trying to do a ReadFile() as I would do with an anonymous pipe, but it does not appear to be working in the same fashion.
Also, please note: The code is a bit sloppy because I have been working with it, so I apologize. I will definitely be cleaning it up after I get this to function properly.
You should have two OVERLAPPED structures, one for reading and one for writing. Also you need one event handle per pipe for when you want to close the pipe, and one more event when you want to abort all (and close application). You can have one WaitForMultipleObjects for every operation that all pipes are currently involved with, or separate reading from writing in two threads with one WFMO in each. I would go with only one thread, because closing the pipe is then simpler (otherwise you need to have some reference counting on the pipe handle and close it only when reference count drops to zero).
When you get one event then process it, and try WFMO with 0 seconds on all handles that were in the array after the one you just processed. This way no pipe will get starved. When 0 second WFMO elapses repeat normal WFMO from beginning.
If you need high concurrency then process events in separate threads, and omit the currently processing handles from WFMO. However, tracking all the handles then gets a little complicated.
Have you tried passing PIPE_NOWAIT instead of PIPE_WAIT in the CreateNamedPipe call? This will allow ReadFile and WriteFile to be non-blocking.
Alternatively, have you tried using async IO? You're passing the FILE_FLAG_OVERLAPPED flag, so this should work. If you've tried it, what problems did you encounter?
In the linux world, one program can write to a named pipe through write/fwrite calls and another program can read it through read/fread().
The FULL path of the named pipe must be used in read/write operations.