Sunday 20 November 2011

How can I pass data from one program to another? in C programming

How can I pass data from one program to another?

You can accomplish this task in a couple of basic ways—you can pass the data via a file or via memory. The steps for any of the methods are fairly straightforward: you define where the data is to be placed, how to get it there, and how to notify the other program to get or set the data; then you get or set the data in that location. Although the file technique is simple to define and create, it can easily become slow (and noisy). This answer will therefore concentrate on the memory data-transfer technique. The parts of the process will be detailed one at a time:

Define where the data is to be placed. When you write the two applications (it takes two to share), you must build into them a way to know where the data is going to be when you need to retrieve it. Again, there are several ways you can solve this part. You can have a fixed data buffer internal to one (or each of the programs) and pass a pointer to the buffer back and forth. You could dynamically allocate the memory and pass a pointer to the data back and forth. If the data is small enough, you could pass the information through the CPU’s general purpose registers (this possibility is unlikely due to the pitifully few number of registers in the x86 architecture). The most flexible and modular method is to dynamically allocate the memory. Define how to get the data there. This part is straightforward—you use _fmemcpy() or an equivalent memory copy routine. This naturally applies for both getting and setting the data. Define how to notify the other program. Because DOS is not a multitasking operating system, it is obvious that one (or both) of the programs must have some part of the software already resident in memory that can accept the call from the other program. Again, several choices are available. The first application could be a device

driver that is referenced in CONFIG.SYS and loaded at boot time. Or it could be a TSR (terminate-andstay-
resident) application that leaves the inter-application part of the program resident in memory when itexits. Another option is to use the system() or spawn() calls (see FAQ XX.11) to start the second application from within the first one. Which option you select depends on your needs. Because data passing to and from a DOS device driver is already well documented, and system() and spawn() calls were documented earlier, I’ll describe the TSR method.

The following example code is a complete program but is admittedly thin for the purposes of grasping only the critical pieces of the process (see FAQ XX.15 regarding example code). The following example shows aTSR that installs an interrupt service routine at interrupt 0x63, then calls the terminate-and-stay-resident exit function. Next, another program is executed that simply initiates an interrupt call to 0x63 (much like you make DOS int21 calls) and passes “Hello World” to that program.

#include <stdlib.h>
#include <dos.h>
#include <string.h>
void SetupPointers(void);
void OutputString(char *);
#define STACKSIZE 4096
unsigned int _near OldStackPtr;
unsigned int _near OldStackSeg;
unsigned int _near MyStackOff;
unsigned int _near MyStackSeg;
unsigned char _near MyStack[STACKSIZE];
unsigned char _far * MyStackPtr = (unsigned char _far *) MyStack;
unsigned short AX, BX, CX, DX, ES;
/* My interrupt handler */
void _interrupt _far _cdecl NewCommVector(
unsigned short es, unsigned short ds, unsigned short di,
unsigned short si, unsigned short bp, unsigned short sp,
unsigned short bx, unsigned short dx, unsigned short cx,
unsigned short ax, unsigned short ip, unsigned short cs,
unsigned short flags);
/* Pointers to the previous interrupt handler */
void (_interrupt _far _cdecl * comm_vector)();
union REGS regs;
struct SREGS segregs;
#define COMM_VECTOR 0x63 /* Software interrupt vector */
/* This is where the data gets passed into the TSR */
char _far * callerBufPtr;
char localBuffer[255]; /* Limit of 255 bytes to transfer */
char _far * localBufPtr = (char _far *)localBuffer;
unsigned int ProgSize = 276; /* Size of the program in paragraphs */
void
main(int argc, char ** argv)
{
int i, idx;
/* Set up all far pointers */
SetupPointers();
/* Use a cheap hack to see if the TSR is already loaded
If it is, exit, doing nothing */
comm_vector = _dos_getvect(COMM_VECTOR);
if(((long)comm_vector & 0xFFFFL) ==
((long)NewCommVector & 0xFFFFL)){
OutputString(“Error: TSR appears to already be loaded.\n”);
return;
}
/* If everything’s set, then chain in the ISR */
_dos_setvect(COMM_VECTOR, NewCommVector);
/* Say we are loaded */
OutputString(“TSR is now loaded at 0x63\n”);
/* Terminate, stay resident */
_dos_keep(0, ProgSize);
}
/* Initializes all the pointers the program will use */
void
SetupPointers()
{
int idx;
/* Save segment and offset of MyStackPtr for stack switching */
MyStackSeg = FP_SEG(MyStackPtr);
MyStackOff = FP_OFF(MyStackPtr);
/* Initialize my stack to hex 55 so I can see its footprint
if I need to do debugging */
for(idx=0;idx<STACKSIZE; idx++){
MyStack[idx] = 0x55;
}
}
void _interrupt _far _cdecl NewCommVector(
unsigned short es, unsigned short ds, unsigned short di,
unsigned short si, unsigned short bp, unsigned short sp,
unsigned short bx, unsigned short dx, unsigned short cx,
unsigned short ax, unsigned short ip, unsigned short cs,
unsigned short flags)
{
AX = ax;
BX = bx;
CX = cx;
DX = dx;
ES = es;
/* Switch to our stack so we won’t run on somebody else’s */
_asm{
; set up a local stack
cli ; stop interrupts
mov OldStackSeg,ss ; save stack segment
mov OldStackPtr,sp ; save stack pointer (offset)
mov ax,ds ; replace with my stack s
mov ss,ax ; ditto
mov ax,MyStackOff ; replace with my stack ptr
add ax,STACKSIZE - 2 ; add in my stack size
mov sp,ax ; ditto
sti ; OK for interrupts again
}
switch(AX){
case 0x10: /* print string found in ES:BX */
/* Copy data from other application locally */
FP_SEG(callerBufPtr) = ES;
FP_OFF(callerBufPtr) = BX;
_fstrcpy(localBufPtr, callerBufPtr);
/* print buffer ‘CX’ number of times */
for(; CX>0; CX--)
OutputString(localBufPtr);
AX = 1; /* show success */
break;
case 0x30: /* unload; stop processing interrupts */
_dos_setvect(COMM_VECTOR, comm_vector);
AX = 2; /* show success */
break;
default:
OutputString(“Unknown command\r\n”);
AX = 0xFFFF; /* unknown command -1 */
break;
}
/* Switch back to the caller’s stack */
_asm{
cli ; turn off interrupts
mov ss,OldStackSeg ; reset old stack segment
mov sp,OldStackPtr ; reset old stack pointer
sti ; back on again
}
ax = AX; /* use return value from switch() */
}
/* avoids calling DOS to print characters */
void
OutputString(char * str)

{
int i;
regs.h.ah = 0x0E;
regs.x.bx = 0;
for(i=strlen(str); i>0; i--, str++){
regs.h.al = *str;
int86(0x10, &regs, &regs);
}
}

The preceding section is the TSR portion of the application pair. It has a function, NewCommVector(), installed at interrupt 0x63 (0x63 is typically an available vector). After it is installed, it is ready to receive commands. The switch statement is where the incoming commands are processed and actions are taken. I arbitrarily chose 0x10 to be the command “copy data from ES:BX and print that data to the screen CX number of times.” I chose 0x30 to be “unhook yourself from 0x63 and stop taking commands.” The next piece of code is the other program—the one that sends the commands to 0x63. (Note that it must be compiled in the Large memory model.)

#include <stdlib.h>
#include <dos.h>
#define COMM_VECTOR 0x63
union REGS regs;
struct SREGS segregs;
char buffer[80];
char _far * buf = (char _far *)buffer;
main(int argc, char ** argv)
{
int cnt;
cnt = (argc == 1 ? 1 : atoi(argv[1]));
strcpy(buf, “Hello There\r\n”);
regs.x.ax = 0x10;
regs.x.cx = cnt;
regs.x.bx = FP_OFF(buf);
segregs.es = FP_SEG(buf);
int86x(COMM_VECTOR, &regs, &regs, &segregs);
printf(“ISR returned %d\n”, regs.x.ax);
}

You might think that this short program looks just like other programs that call int21 or int10 to set or retrieve information from DOS. If you did think that, you would be right. The only real difference is that you use 0x63 rather than 0x21 or 0x10 as the interrupt number. This program simply calls the TSR and requests it to print the string pointed to by es:bx on-screen. Lastly, it prints the return value from the interrupt handler (the TSR).

By printing the string “Hello There” on-screen, I have achieved all the necessary steps for communicating between two applications. What is so cool about this method is that I have only scratched the surface of possibilities for this method. It would be quite easy to write a third application that sends a command such as “give me the last string that you were asked to print.” All you have to do is add that command to the switch statement in the TSR and write another tiny program that makes the call. In addition, you could use the system() or spawn() calls shown in FAQ XX.11 to launch the TSR from within the second example program., Because the TSR checks to see whether it is already loaded, you can run the second program as often as you want, and only one copy of the TSR will be installed. You can use this in all your programs that “talk” to the TSR.

Several assumptions were made during the creation of the TSR. One assumption is that there is no important
interrupt service routine already handling interrupt 0x63. For example, I first ran the program using interrupt 0x67. It loaded and worked, but I could no longer compile my programs because interrupt 0x67 is also hooked by the DOS extender used by Microsoft to run the C compiler. After I sent command 0x30 (unload yourself), the compiler ran flawlessly again because the DOS extender’s interrupt handler was restored by the TSR.

Another assumption was in the residency check. I assumed that there will never be another interrupt handler with the same near address as NewCommVector(). The odds of this occurring are extremely small, but you should know this method is not foolproof. Although I switched stacks in NewCommVector() to avoid running
on the calling program’s stack, I assume that it is safe to call any function I want. Note that I avoided calling printf because it is a memory hog and it calls DOS (int21) to print the characters. At the time of the interrupt, I do not know whether DOS is available to be called, so I can’t assume that I can make DOS calls.

Note that I can make calls (like the one to OutputString()) that do not use the DOS int21 services to perform
the desired task. If you must use a DOS service, you can check a DOS busy flag to see whether DOS is callable at the time. A note about _dos_keep(). It requires you to tell it how many paragraphs (16-byte chunks) of data to keep in memory when exiting. For this program, I give it the size of the entire executable in paragraphs, rounded up a bit (276). As your program grows, you must also grow this value, or strange things will happen.

Cross Reference:

XX.10: How can I run another program after mine?
XX.11: How can I run another program during my program’s execution?
XX.15: Some of your examples are not very efficient. Why did you write them so badly?

No comments:

Post a Comment