Link to home
Start Free TrialLog in
Avatar of precords
precords

asked on

How do I create a background data stream in a C++ DLL?

Hello,
I'm new to C++ and this has been quite the learning curve.  I've got a dll I'm creating to access streaming market data and perform operations on it and return results to tradestation so must use __stdcall it to be compatible.  Currently this is running in Visual Studio 6 with no MFC.  My problem is that I have to use a sleep command to keep the quotes coming (or the faucet gets turned off when the return happens), BUT then the dll is locked there forever and I can't access stuff, soooo I need the connection enabled by the Ewrapper and EclientL0 and subsequent REQMKTDATA (turns on data faucet) to be in a background thread so that I can still access and call things in the main thread.  How do I do this??  I want to turn it on in the dll when DLL_PROCESS_ATTACH and then exit the background thread when DLL_PROCESS_DETACH occurs.  Also wondering if the variables in the background thread are still available to the foreground thread when they are setup in the top of the main.cpp?
Many thanks in advance for your help!!!!!

Mr. Beginner
*************  Here's the code for the DLLMAIN
BOOL APIENTRY DllMain
( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved )
{
    switch ( ul_reason_for_call )
      {
            case DLL_PROCESS_ATTACH:{
                        fp=fopen("C:/tradingfiles/mdeltadlllog.txt", "a+");  //cj
                        fprintf(fp, "Main dll case process attach\n" );
                        fclose(fp);  //cj
                        //pNewMyconnection = new EC;
                        
                        break;
                        }
            case DLL_THREAD_ATTACH:{
                        fp=fopen("C:/tradingfiles/mdeltadlllog.txt", "a+");  //cj
                        fprintf(fp, "Main dll case thread attach\n" );
                        fclose(fp);  //cj
                  break;
                        }
            case DLL_THREAD_DETACH:{
                        fp=fopen("C:/tradingfiles/mdeltadlllog.txt", "a+");  //cj
                        fprintf(fp, "Main dll case thread detach\n" );
                        fclose(fp);  //cj
                  break;
                           }
            case DLL_PROCESS_DETACH:{
                        fp=fopen("C:/tradingfiles/mdeltadlllog.txt", "a+");  //cj
                        fprintf(fp, "Main dll case process detach\n" );
                        fclose(fp);  //cj
                                          }
            break;
    }

    return TRUE ;
}
int __stdcall MD_connect
( LPSTR sIP, int iID, LPSTR sSYMBOL, LPSTR sEXP, LPSTR sSECTYPE, LPSTR sCURRENCY, LPSTR sEXCHANGE)
{
    if ( iID >= 0 )  {
		MyEWrapper	EW;
		
		EClientL0*	EC = EClientL0::New( &EW );
		//printf( "ClientVersion = %d\n", EC->clientVersion() );
		IDint = iID; 
		IPstring = (const char*)sIP;
		mysym = (const char*)sSYMBOL;
		myexpiry = (const char*)sEXP;
		mysecType = (const char*)sSECTYPE;
		mycurrency = (const char*)sCURRENCY;
		myexchange = (const char*)sEXCHANGE;
 
		if( EC->eConnect( &IPstring[0], 7496, IDint ) )  
			{print stuff
					}
		Contract C;
		C.symbol	=  &mysym[0];
		C.expiry	= &myexpiry[0];
		C.secType	= &mysecType[0];
		C.currency	= &mycurrency[0];
		C.exchange = &myexchange[0];
		
		EC->reqContractDetails( C );
		EC->reqMktData( 10, C, "", false );
 
		while( EC->IsConnected()) {	Sleep( 1000 ); }
		if (EC->IsConnected()) { return 1; } else {return -1;}
	}
	else
	{
		return -2 ; // Error code:  Element location out of range
	}
}

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of evilrix
evilrix
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of precords
precords

ASKER

Well I think I've solved the first part.  Now I'm having trouble disconnecting with endthread().  Won't seem to kick out gracefully.
precords
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
>> See the Exit thread:

There is no need to use EndThread when the threadproc function can just return, which effectively terminates the thread.

Actually, if you are using the C/C++ runtime in your code you shouldn't even use CreateThread or EndThread at all; rather, you should use _beginthread and _endthread (except you don't need to if your threadproc function just returns). This is because the other two functions by-pass the C/C++ runtime Thread Local Storage allocation and clean-up mechanisms. If the TLS allocation is by-passed it is still implicitly created; however, it is never released and as such a memory leak occurs!

http://msdn2.microsoft.com/en-us/library/kdzttdcb(VS.80).aspx
http://msdn2.microsoft.com/en-us/library/hw264s73(VS.80).aspx

Multi-threading tutorial
http://www.devarticles.com/c/a/Cplusplus/Multithreading-in-C/
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Ok, here's where I'm at.  I've done this so far to exit the thread.  This keeps the faucet (REQMKTDATA) turned on indefinitely in the background thread called datathread.  If we are disconnected or receive a disconnectflag it terminates gracefully.  Am I correct that simply returning from the thread cleans up the thread BETTER than any endthread, exitthread type of statement??

The problem I now have is that even though the MAIN thread of the program is accessible now for other calls from tradestation (which there are 5-10 more that are based upon the worker background thread) it still LOCKS up tradestation IF that worker thread is not exited gracefully by giving it the disconnectflag = 1.  For example when I program within my tradestation easy language code then 3 minutes after it turns on with md_connect it then turns off with MD_disconnect which only sets the disconnectflag = 1.
if LastBarOnChart and runonce2 = 0 and (connecttime <> 0 and timetominutes(time) >= timetominutes(connecttime) + 3) then begin
      Rtndis= MD_disconnect( IP, inptconnectID, symbolnam, exp, sectype, currency, exchange );
      runonce2 = 1;
end;
Any ideas on how to get around this problem??

Alex, I have to utilize the worker thread in the main thread and have the variables I change in the worker thread (or exe) available always in the main thread of the dll.  For example I need all these functions
int __stdcall MD_nextvalidid ( void  ) {return mdnextValidId;}
double __stdcall MD_open ( void  ) {return mdopen;}
double __stdcall MD_high ( void  ) {return mdhigh;}
double __stdcall MD_low ( void  ) {return mdlow;}
int __stdcall MD_ask ( int indexask  ) {return mdaskvol[indexask];}
int __stdcall MD_bid ( int indexbid  ) {return mdbidvol[indexbid];}

What is the code needed to share between another program (like an exe) and the dll?  I'm going to need a lot more than the suggestion of doing it.  Without concrete examples I will not get it figured out.  For instance despite all the great ideas you all have given here, it was this piece of code I found in the helps that showed me how to create another thread in the first place:

void CheckKey( void *dummy )
{
    _getch();
    repeat = 0;    /* _endthread implied */

}

then in the main they had:
 /* Launch CheckKey thread to check for terminating keystroke. */
    _beginthread( CheckKey, 0, NULL );

It was a simple concrete example of how to do it.  
I'm thinking that offloading the reqmktdata to an exe or something AS LONG AS THERE IS NO DIFFERENCE IN WHEN THE VARIABLES CALCULATED IN THE EXE ARE AVAILABLE TO THE DLL, then perhaps that would solve the Tradestation freezing up.  

Thanks again everyone for all their good ideas!!
Just about there.
precords



//flagmdconnect = 1;
		while( EC->IsConnected() && disconnectflag == 0) {
											if (disconnectflag == 1) { 
					EC->eDisconnect();
					delete EC;
					//_endthread();
					return;
				}
			Sleep( 1000 ); 
		}

Open in new window

SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thanks for all your help, could have used some real world example code in all of this.  Would have liked to implement the dll with exe called but noone helped me with how to share memory space between them
>>>> What is the code needed to share between another program (like an exe) and the dll?  
Sorry, I was busy this week and missed your request.

If you want to go the server way, the dll would

A. start the server process when it was not already running

To check if it was running, the easiest is to check for the existence of a named shared memory which could be used for communication anyhow:
 
    bool  serverWasRunning = false;
    HANDLE hmap = NULL;
    HANDLE hevent = NULL;
    hMap = CreateFileMapping(                // hMap is the handle returned
               INVALID_HANDLE_VALUE,        // no file but only shared memory
               NULL,                                       // no special access rights
               PAGE_READWRITE,                  // for read and write
               0,                                             // high word of size
               4096,                                       // e. g. 4096 bytes
               "MYSERVER");                          // name of shared memory (use exe name)

    if (hMap != NULL && GetLastError() == ERROR_ALREADY_EXISTS)
    {
          // coming here the server already was started by a previous call
          serverWasRunning = true;
          // we can use the hMap to get a pointer to the memory and
          // put our request to a queue in that memory

    }
    else if (hMap != NULL)
    {
          // the memory  wasn't established yet, so we need to start the server
          STARTUPINFO si = { 0 };
          si.cb = sizeof(si);
          PROCESS_INFORMATION pi = { 0 };

          BOOL ret = CreateProcess(
                                NULL,                                                         // application not used
                                "c:/program files/myserver.exe params",  //  command  
                                NULL, NULL, FALSE,                                   // security .. inheritage
                                DETACHED_PROCESS,                               // no console
                                NULL,                                                         // no special environment
                                "c:/program files",                                     // start directory
                                &si,                                                            // pointer to startup info
                                &pi);                                                          // pointer to process info
          if (!ret)
               return 1;   // create process failed        

          // the server would start and get a handle to the shared memory
          // then, it would wait for an event before starting to process the
          // requests of the input queue established in that memory
          hEvent = CreateEvent(NULL,
                                              FALSE,         // automatic reset
                                              FALSE,         // initial not signaled
                                              "MYSERVER_START");   // unique name
          if (hEvent == NULL)
              return 4;
    }  
    else
    {
            // create file mapping failed (rarely)
            return 2;
    }

    // coming here, we have a handle to shared memory and a
    // server waiting for requests
   
    // get a pointer to shared memory
    void* pMemory = MapViewOfFile(
                                        hMap,
                                        FILE_MAP_ALL_ACCESS,
                                        0, 4096, 4096);

     if (pMemory == NULL)
            return 3;
     
     // coming here, we can establish the input queue  
     if (!serverWasRunning)
     {
             // we created the shared memory and invoked the server
             // now we have to initialize the memory and let the server run
             memset(pMemory, 0, 4096);
             
             SetEvent(hEvent);    // sets the signal for the server to go on
             serverWasRunning = true;
     }

     // creates a mutex which initially doesn't claim ownership
     HANDLE hMutex = CreateMutex(NULL, FALSE, "MYCLIENT");
     
     if (hMutex == NULL)
           return 5;
     
     // it doesn't matter whether we created the mutex or not we
     // claim exclusive ownership by waiting for it
     // only one client would get ownership at one time
     DWORD dret = WaitForSingleObject(hMutex, 2000);
     if (dret == WAIT_TIMEOUT)
          return 6;    // timeout

     // coming here we safely can write our request to the input queue
     // we then were waiting for the request to be processed by the
     // server
     ....

I will add more code if you want to go that way ...

Regards, Alex
Hi Alex,

Well I've been working with this and I see I need to do 2 things.  First I do need to get the exe and dll working together instead of running my connection and marketdata thread in my dll as you suggest.  Before I ask for more of your code, I need to know just HOW I can get my variables in my exe to be read as such in the dll.  For example if I have a variable:
int myvariable = 42; // in the exe
How does the dll know from the shared memory that myvariable = 42??  I guess I need some specific example of sharing variables via this memory space so I can wrap my head around this better and pursue this as it would be much cleaner if I could.
2.  I need to collect more than one data stream (now that I finally got one working I'm thrilled) but each one uses up a clientId and I only have 8 total clientIDs I can use.  Thus, I'm assuming I create some kind of object such that each data stream coming in (identified by TickerID) goes into a different instance of that object.  
So that I have in the final version myobject.myvariable = 42;
Don't know if you are following this or not.
Thanks so much for the help!

precords