Articles - Arrays page 2

Coding

The OneWorld BSFN

Now this is where we're going to write our code.  Again, this isn't meant to be a MFC tutorial, so I'm going to leave out a few steps from this description.   Hopefully, you'll understand our destination and can extrapolate the essence of what I'm doing into your own solution.

 

We'll start within OneWorld by creating the Business Function that will execute our DLL.  Remember that what we're really doing here is using a tool outside of OneWorld to accomplish something that OneWorld can't do.  In this case we want a small dialog window to pop up and do some work without obscuring everything behind it.  OneWorld Forms always hide prior forms in an application if any of the forms are maximized. 

The Business Function we're creating for this tutorial is named B5501 and will use the Data Structure D5501.  Both of these will, of course, be created from scratch.

Our first step then is to create a Data Structure for our OneWorld Business Function.   Here I've created a structure with Address Book Number (AN8) and Alpha Name (ALPH).

DataStructCreate.jpg (44068 bytes)

Now, in this tutorial we're not passing anything into the dialog, but we could.   For illustration purposes we're passing values back out of the dialog, however.  

You can see that our OneWorld BSFN will take two parameters and then will pass them into our custom DLL program. 

 

Next we create the Business Function itself and attach our new Data Structure to it.   The name of this function as I mentioned is B5501.  Generate the skeleton and paste the typedef into the header.  This step follows general OneWorld procedures so, I'm assuming you know how to do this.  I'm restricting this function to run only on CLIENTS.  It's calling an interactive program, so that's important.

The only purpose for this function is to be a passthrough for it's data structure to the DLL.  As a conduit it only needs to receive the structure, call the DLL and pass any results back to whatever initiated the function to begin with.  This means we don't have very much work to do, which is good news.

DLLs are, as a rule, executables that are 'linked' during compile-time into their calling programs.  Unfortunately, OneWorld doesn't give us enough control of the compiler in BusBuild to specify our own libraries for incorporation into OneWorld DLLs.   This means we have to compile and call our custom DLL outside of OneWorld.   Here's what you're about to see: in our B5501 we're going to tell the software to load our custom DLL into memory and call it's main routine by pointing to its location in memory.  We're calling the DLL at runtime! If you're familiar with DLLs you've recognized that this method frees us from linking the library when we compile the OneWorld Business Function.

So, lets look at the code:  (My code is Blue.   Notes are in Yellow.)


Below the copyright information add the typedef that tells the system how to find your DLLs entry point.

#include <b5501.h>

typedef void (FAR PASCAL *PFNDLL)(LPBHVRCOM ,LPVOID, LPDSD5501);
/**************************************************************************
* Business Function: ABSearch
*


` ` ` ` ` `` ` ` ` ` ` ` ` ` `break ` ` ` ` `  ` ` ` ` ` ` ` ` `


/************************************************************************
* Declare pointers
*************************
***********************************************/
HMODULE hLibrary;
PFNDLL lpfndllTemplate;

` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `break ` ` ` ` `` ` ` ` ` ` ` ` ` `


/************************************************************************
* Main Processing
************************************************************************/

In the Main Processing section simply load the dll into memory and call the entry point.


   //
    // -Open the dll. If NULL is returned, the dll could not be opened.
    //
        if((hLibrary = LoadLibrary(TEXT("ABReturn.dll")))==NULL)
    {
            MessageBox(lpBhvrCom->hDlg,"ABReturn.dll Could not be found. Please Notify MIS",
            "dll Not Found", MB_ICONEXCLAMATION);
            return(ER_ERROR);
    }
    //
    // -Get the memory location of the Entry Point. If NULL is returned, there is no
    // such routine in the dll.
    //
        if ((lpfndllTemplate= (PFNDLL)GetProcAddress(hLibrary,TEXT("dllEntry"))) = = NULL)
            return(ER_ERROR);
    //
    //    -Call your dll.
    //
        (*lpfndllTemplate)(lpBhvrCom,lpVoid, lpDS);

/************************************************************************
* Function Clean Up
************************************************************************/

    FreeLibrary(hLibrary);

 

Okay, that second block of code may need some explanation.  It does three relatively simple things: loads the library, finds the entry point in memory, and calls the dll.  The first command you see in the 'if' statement is LoadLibrary.   This is where the dll is loaded into memory, and yes, this is done at run time.   You can also see that the dll is named right in the code "ABReturn.dll".   Now, if the dll isn't found, or fails to load we fall into that 'if' statement and, as you can see, a message dialog tells the user to call MIS.  Of special note is the fact that no path is given for the location of ABReturn.dll.  Instead, the PATH is used from Windows to locate the dll.  This may be a problem for some installations, so just keep this in mind if you fall victim to this limitation.  Placing the path to the dll in the quotes will solve your problem.

The second command you see is GetProcAddress and in quotes soon after is "dllEntry".  You'll soon see that "dllEntry" is the name of the entry point in the dll itself.  You can have many entry points in the same dll, so we need to tell the system the precise name of the one we want.  This command returns a pointer to the location of the entry point in memory.

Lastly, we use the pointer returned from the prior step to call the dll.  It's the single line following the "Call your dll" comment block.  You can see that we're also passing the exact parameters that the Business Function receives.  Think about that for a moment, because it's critically important and the implications are staggering.  With these values the new program will be able to use all of the OneWorld APIs as if it were written with the OneWorld Toolset!  Now, any language capable of making calls to 'C' APIs is capable of incorporating OneWorld APIs and running interactively within OneWorld.

One final thought; because we're passing lpDS (which is the DSTR we created) any changes to the values in the structure will be passed back to OneWorld!  We can now accept, process, and return data using tools of our choosing. 

 

The Custom DLL Code:

So we're done with OneWorld proper, lets look at our dll:

In MFC we've our new dll framework.  I'm now going to add a dialog class.   Here's something to consider here.  You don't have to make your dll with dialogs, or even an interface.  It's entirely possible to call an external dll that does behind the scenes work and then returns some value, or perhaps just does it's own work.  Keep in mind though that depending on your system these custom DLLs may not work on your server.

Adding the Dialog:

Right-click on your DLLs resource directory and pick "insert" InsertRec.jpg (48100 bytes)
Select "Dialog" and click 'New' InsertRecDial.jpg (17647 bytes)
The interface displays your new, empty' dialog. DialEmpty.jpg (72355 bytes)
Place your controls. DialDrawn.jpg (14312 bytes)
Double click on the dialog to add a New Class.

 

In the new class, add variables to your controls using the ClassWizard.

 

Add a class method for the 'Find' button.

DialNewClass.jpg (72683 bytes)

More Code:

The Header of your new Class:

In the Header of your new Dialog Class, declare the OneWorld headers to include and the variables for the passed-in parameters.

#if !defined(AFX_ABDIALOG_H__5F7F5FC5_8257_41B0_BCCB_23A5DF949AB2__INCLUDED_)
#define AFX_ABDIALOG_H__5F7F5FC5_8257_41B0_BCCB_23A5DF949AB2__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// ABDialog.h : header file
//

#include <jde.h>                        The OneWorld Headers

#include <b5501.h>
#include <f0101.h>

////////////////////////////////////////////////////////////////////////////
// ABDialog dialog

class ABDialog : public CDialog
{
// Construction
public:
    ABDialog(CWnd* pParent = NULL); // standard constructor

   
// OneWorld Variables           
    LPBHVRCOM lpBhvrCom;   
The OneWorld parameters

    LPVOID lpVoid;
    LPDSD5501 lpDS;

` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `break ` ` ` ` ` ` ` ` ` ` ` ` ` ` `

 

Write your code in the 'Find' button method:

` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `break ` ` ` ` ` ` ` ` ` ` ` ` ` ` `

void ABDialog::OnButtonFind()
{
/************************************************************************
* Data Structures & Pointers
************************************************************************/

    ID             idReturnValue         = ER_SUCCESS;
    ID             idJDEDBReturn      = JDEDB_PASSED;
    ID             idErrorFlag             = ER_SUCCESS;
    HUSER        hUser                = (HUSER)NULL;
    HREQUEST hRequest            = (HREQUEST)NULL;

    NID             szTableID             = NID_F0101;
    ID               idIndexID              = ID_F0101_ADDRESS;

F0101 dsF0101;
KEY1_F0101 dsF0101Key1;

/************************************************************************
* Main Processing
************************************************************************/

// Initialize hUser and Return if this fails
idJDEDBReturn = JDB_InitBhvr((void *)lpBhvrCom, &hUser, (char *) NULL, JDEDB_COMMIT_AUTO);
   
    if (idJDEDBReturn != JDEDB_PASSED)
    {
       return;
    }

    // Read the Screen
    UpdateData();

    //
    // Open the Address Book File
    //
   idJDEDBReturn = JDB_OpenTable(hUser,szTableID,idIndexID,0,
                             (unsigned short)0,(char *) NULL,
                             &hRequest);

    // Prepare the Key and Return Structure
    memset((void *)&dsF0101,(int)'\0',sizeof(F0101));
    memset((void *)&dsF0101Key1,(int)'\0',sizeof(KEY1_F0101));

   ParseNumericString(&dsF0101Key1.aban8,(char*)(LPCTSTR) m_AN8);

    // Fetch the Record
    idJDEDBReturn = JDB_FetchKeyed(hRequest,(ID)0,
                              (void *)&dsF0101Key1,
                              (unsigned short)1,(LPVOID)&dsF0101,
                              (int)FALSE);

    if (idJDEDBReturn != JDEDB_PASSED)
    {
        // Nothing was found, blank out the result field
        m_ALPH=" ";
    }
    else
    {
        // A record was found, place ALPH onto the dialog
        // Also, fill the lpDS to pass results back to calling program
        m_ALPH.Format("%s",dsF0101.abalph);
        sprintf(lpDS->szNameAlpha,"%s",dsF0101.abalph);
        ParseNumericString(&lpDS->mnAddressNumber,(char*)(LPCTSTR) m_AN8);
    }

    // Update the Screen
    UpdateData(FALSE);

    // Close the table and free the hUser.   
    JDB_CloseTable (hRequest);
    JDB_FreeBhvr(hUser);

    return;

}

Above is the code in the Find button's method.  This should look very familiar to anyone who's written any OneWorld business functions.  Aside from some MFC specific keywords it uses the OneWorld API almost exclusively!  I won't go into a detailed description here because I'm assuming you've seen, or done similar work to this in OneWorld.  The function opens the F0101 Address Book file, searches for the Address Number typed in and returns the Alpha Name to the dialog box.  If nothing is found, it blanks out the name field on the dialog.  Notice that the values are placed in the lpDS structure.  This means these values will be passed back to the initiating program in OneWorld.  That's right, we're passing the results back to OneWorld.

Obviously this routine could be made more efficient.  Presently it gets hUser, and opens/closed the F0101 table every time the Find button is pressed.  A savy programmer would break this method into pieces so all this work isn't done every time the user presses Find.

 

The Code from the Wizard created APP Class:

// ABReturn.cpp : Defines the initialization routines for the DLL.
//

#include "stdafx.h"
#include "ABReturn.h"

#include "ABDialog.h"                    Add the header for your new Dialog Class

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//
//    Note!
//
//        If this DLL is dynamically linked against the MFC
//        DLLs, any functions exported from this DLL which
//        call into MFC must have the AFX_MANAGE_STATE macro
//        added at the very beginning of the function.
//
//        For example:
//
//        extern "C" BOOL PASCAL EXPORT ExportedFunction()
//        {
//             AFX_MANAGE_STATE(AfxGetStaticModuleState());
//            // normal function body here
//        }
//
//        It is very important that this macro appear in each
//        function, prior to any calls into MFC. This means that
//        it must appear as the first statement within the
//        function, even before any object variable declarations
//        as their constructors may generate calls into the MFC
//        DLL.
//
//        Please see MFC Technical Notes 33 and 58 for additional
//        details.
//

/////////////////////////////////////////////////////////////////////////////
// CABReturnApp

BEGIN_MESSAGE_MAP(CABReturnApp, CWinApp)
    //{{AFX_MSG_MAP(CABReturnApp)
        // NOTE - the ClassWizard will add and remove mapping macros here.
        // DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CABReturnApp construction

CABReturnApp::CABReturnApp()
{
    // TODO: add construction code here,
    // Place all significant initialization in InitInstance
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CABReturnApp object

CABReturnApp theApp;

/////////////////////////////////////////////////////////////////////////////
// Main Processing for the dll call

Now Add your code to the dll. Note the name of the entry point: dllEntry.


extern "C" __declspec(dllexport) int dllEntry (LPBHVRCOM lpBhvrCom, LPVOID lpVoid, LPDSD5501 lpDS)
{
// Initialize the pointers and lpDS fields used in the Dialog.

    // First, save the lpDS structure in case the user cancels
DSD5501 ABparms;
memcpy(&ABparms,lpDS,sizeof(ABparms));

    // Create a new Dialog from the ABDialog Class,
    // populate it's variables and Execute it.
   
    ABDialog TheDialog = new ABDialog;
               
    TheDialog.lpBhvrCom=lpBhvrCom;
    TheDialog.lpVoid=lpVoid;
    TheDialog.lpDS=lpDS;

if(TheDialog.DoModal()!=IDOK)
{
    // The user hit Cancel, so restore the passed lpDS
memcpy(lpDS,&ABparms,sizeof(ABparms));
};

    return (TRUE);
}

Now the last thing we need to do is instanciate our Dialog Class in the ClassWizard created framework of our dll.  Again, the new code is blue.  You can see that we've supplied an entry point named "dllEntry".  Look back at the OneWorld BSFN B5501.  That's the name we supplied as the entry point. 

In this example the entry point is used to simply call our new dialog class.  If we didn't need a dialog class we could have just written all the code right into this APP class.  Subroutines and everything could just be added here.  We aren't doing that in this case though. 

So, the short story with the code here is this: 

We save the lpDS in case the user presses 'Cancel'.  If that happens we just pass back whatever was passed in.  We don't want to change any values if the user decides just to leave the dialog. 

  1. The ABDialog (the name of my Dialog Class) is instantiated in TheDialog. 
  2. The parameters are placed in my Dialog Classes variables. 
  3. The Dialog Class is called with DoModal.

You can see that if IDOK is not TRUE then the saved values from step 1 are put back into the lpDS structure.  That's to say, if the user presses Cancel.

You're Done!

That's it, compile your DLL and use BusBuild to compile the OneWorld function (B5501 in this example).  Put your new dll into the BIN32 directory of your OneWorld Client and you're ready.  Call your version of B5501 from a OneWorld application and watch your new Dialog dll display before your eyes.

Distribution

I'm sure you'll want all your users to begin using the new dll with the next package you deploy.  This is simple to insure.  Copy your new dll to the Bin32 directory of your deployment server in the appropriate environment.  When the next package is built the new dll should be included.  I say 'should be' because you should check just in case.  There is a bit more overhead using your own custom objects like this, but I hope you'll agree that the extra effort is worth the extra flexibility you'll enjoy.

 

 

example graphic