Sample: Windows Forms Replacing Dialog System Dialogs

The Customer + .NET WinForm sample CustomerWinForm.sln takes the original Customer sample from Dialog System and replaces the Orders dialog with a more modern Windows Forms Orders form. The main Customer application remains largely unchanged, so that the application becomes a native code main program calling .NET code as a COM object to handle the Windows Forms Orders form.

The following diagram shows the native code on the left, with Dialog System handling the screenset. The .NET COBOL code is on the right, with the new Windows Forms form. The image also shows the native code interfacing with a COM Callable Wrapper (CCW) containing the .NET COBOL code, and shows the data block being passed and returned.

The numbers in the above diagram highlight some key points in the process and are explained below:

  1. The native COBOL application calls Dialog System in the traditional way.
  2. Instead of calling the now redundant dialog, the dialog returns control to the application, using RETC.
  3. The native COBOL invokes the .NET COBOL code interface, IFormsFactory, using standard native OO COBOL syntax.

    A copy of the data block is created for the .NET classes to use.

    An entry point is created in the native code, ready for calling back from the .NET COBOL code.

  4. The .NET COBOL code library, OrderFormsLibrary is registered for COM Interop. This ensures that a COM Callable Wrapper (CCW) is created, which provides an interface for the native code to use.
  5. The .NET COBOL code class, FormsFactory, instantiates and displays the form, as a modal dialog, handling the events as needed.
  6. The .NET COBOL code calls back to the entry point previously created in the native COBOL, passing back a pointer to the updated data block.

Customer Project in Native COBOL

The solution has two projects: one native and one .NET COBOL. The native project, Customer, contains the original sources with some changes to handle the Orders dialog differently and to interact with the .NET COBOL code project.

The native project, Customer, contains:

  • Properties - these define how the project is built. For example, it is built as a Windows application called Customer, stored in .\bin\x86\Debug.
  • Customer.cbl - original source code, plus some additional code to interact with the .NET COBOL project, which handles the new Windows Forms Orders form.
  • Customer.cpb - original unchanged copybook generated from the Dialog System data block.
  • Customer.gs - original screenset, which now handles the Orders button by returning control to the customer.cbl, rather than displaying the Orders dialog
  • Cust.ism - the original unchanged data file.

To debug the native project, make sure the Customer project is set as the startup project. To do this, right-click the project and click Set as StartUp Project.

OrderFormsLibrary Project in .NET COBOL

The .NET COBOL project, OrderFormsLibrary, contains the new Windows Forms Orders form. It also contains the supporting code to pass the customer data block between .NET COBOL and native code. It is registered and exposed as a COM object.

The .NET COBOL project, OrderFormsLibrary, contains:

  • Properties - these define how the project is built. Notice that the project is registered as a COM object, thereby exposing it as a COM object and enabling the native project to access it. See Project > Properties > COBOL > Advanced.
  • References - all the required .NET classes.
  • Calendar.cbl - implements a calendar editor column for use in the data grid.
  • Link to Customer.cpb - the same copybook is used and understood by the native and .NET COBOL projects. The copybook itself remains in the native project and a link to it is now added in the .NET COBOL project.
  • FormsFactory.cbl - implements the CreateOrderForm() method and creates a delegate for the callback function, which is used to get hold of the customer data block.
  • IFormsFactory.cbl - defines the CreateOrderForm() method, which provides the entry point into .NET COBOL code from native code.
  • OrderForm.cbl [Design] - this is the order form, drawn with the designer.
  • OrderForm.cbl [Code] - this contains the code to handle the form and you can edit this and add your own code.

To debug the .NET COBOL project, make sure the OrderFormsLibrary project is set as the startup project.

Customer.cbl in Native COBOL

Customer.cbl, contains the original code for the business logic and for interaction with the Dialog System screenset. Customer.cbl also contains some additional code to interact with the .NET COBOL project and to display the Windows Forms Order form. Customer.cbl contains additional code to do the following:

  1. Set the following Compiler directives, using $SET:

      $SET ans85 mfoo ooctrl(+P) case
    • ANS85 remains set as before
    • MFOO enables support for the Micro Focus native OO syntax
    • OOCTRL(+P) enables the run-time system to map COBOL data types to COM data types
    • CASE prevents external symbols (such as Program-ID and names of called programs) being converted to upper case
  2. Map the COM class, OrderFormsLibrary.FormsFactory, to an OO COBOL class name, OrderFormFact , in the CLASS-CONTROL paragraph, where the class is in the COM domain, $OLE$, of the Micro Focus native OO class library:

      class-control.  
      OrderFormFact is class"$OLE$OrderFormsLibrary.FormsFactory".
  3. Evaluate the flag to determine if the Orders button has been pressed and respond accordingly:

      WHEN customer-orders-flg-true
      PERFORM Show-Orders-Form
  4. Create an instance of the COM object, the new .NET order forms library, which implements a Windows Forms version of the Orders dialog that originally existed in customer.gs:

      invoke OrderFormFact "New" Returning formsLibrary
  5. Set a procedure pointer that the .NET code can use to call the native code:

      set  pptr  to  entry  "Customer_Callback"
  6. Get a pointer to the data block, to be used in the callback:

      Customer-Callback SECTION.
          entry "Customer_Callback" stdcall.
          exit program returning address of CUSTOMER-DATA-BLOCK.
    
  7. Invoke and display the new Orders form:

      invoke formsLibrary "CreateOrderForm" using
          by value pptr-val

Customer.GS, the Dialog System Screenset

The screenset, customer.gs no longer displays the original Orders dialog, but instead it passes control back to the native COBOL program. The instructions for the Orders button in the Main-Window dialog have changed as follows:

  SET-FLAG ORDERS-FLG(1)
  RETC
  * REFRESH-OBJECT DIALOG-BOX
  * SET-FOCUS DIALOG-BOX

The instructions now set the ORDERS-FLG to indicate that the Orders button has been pressed, so that the native COBOL program can evaluate the flag and respond appropriately. Control is returned to the native COBOL program. The redundant lines are commented out, so that the old Orders dialog is no longer displayed.

IFormsFactory.cbl in .NET COBOL

IFormsFactory.cbl defines an interface to create a Windows Forms Order form. IFormsFactory.cbl has code to do the following:

  1. Declare the interface using the InteropServices classes from the .NET Framework. In addition, establish the COM object support for late binding, by specifying ComInterfaceType:: InterfaceIsDual. Alternatively, you could specify ComInterfaceType::InterfaceIsIDispatch (but not ComInterfaceType::InterfaceIsIUnknown):

      interface-id OrderFormsLibrary.IFormsFactory
          attribute System.Runtime.InteropServices.InterfaceType 
            (type
            System.Runtime.InteropServices.ComInterfaceType::InterfaceIsDual)

    Note, the 'Register For COM Interop' property exposes everything as a dual interface, by default.

  2. Define the CreateOrderForm() method. This is the definition of the entry point into .NET COBOL code from native code:

      method-id CreateOrderForm.
      procedure division using by value callback as binary-long.
      end method.

FormsFactory.cbl in .NET COBOL

FormsFactory.cbl creates an instance of the Orders form, when the native program asks for it. FormsFactory.cbl has code to do the following:

  1. Declare the class, which implements the IFormsFactory interface:

      class-id OrderFormsLibrary.FormsFactory implements
          type OrderFormsLibrary.IFormsFactory.
  2. Expose the class to COM, by making it visible:

      attribute ComVisible(true)
  3. Specify our interface IFormsFactory as the default interface to expose to COM:

      attribute ComDefaultInterface
          (type of OrderFormsLibrary.IFormsFactory)
  4. Set the ComVisible attribute off by default at the assembly level. This means the types we expose must be explicitly marked as ComVisible(true), which gives us a more well-defined type library for clients to use:
      assembly-attributes.
      attribute ComVisible(false).
  5. Get a delegate for the callback function and use it to get hold of the customer data block. The GetDelegateForFunctionPointer() converts an unmanaged function pointer to a delegate:

      set  pptr to new System.IntPtr(callback)
      set  custCallback to type  
          System.Runtime.InteropServices.Marshal::GetDelegateForFunctionPointer
          (pptr, type of CustomerCallback) 
          as type  CustomerCallback
  6. Declare the delegate for the callback function:

      delegate-id CustomerCallback.
      procedure division returning pCustomerDataBlock as type IntPtr.
      end delegate.
  7. Create the Orders form and show it as a modal dialog box:

      set form to new OrderFormsLibrary.OrderForm(custCallback)
      invoke form::ShowDialog()

OrderForm.cbl [Design] in .NET COBOL

The order form is drawn with the form designer. See the Windows Forms tutorials.

OrderForm.cbl [Code] in .NET COBOL

OrderForm.cbl [Code] is a partial class, also called OrderFormsLibrary.OrderForm and this contains the code to handle the Windows Forms Orders form.

OrderForm.cbl [Code] has the following methods:

New()

Call back to native code to retrieve customer data block. We store the pointer in _pCustomerDataBlock.:

  set  _pCustomerDataBlock  to  callback::Invoke()
OrderForm_Load()

Unpack the data block to a copy in local storage, which can then be accessed in the same way as in native code. The Marshal::Copy() method copies the pointer _pCustomerDataBlock into pData:

  invoke type Marshal::Copy
  (_pCustomerDataBlock, pData, 0, length  of  CUSTOMER-DATA-BLOCK)
  set CUSTOMER-DATA-BLOCK to pData
PopulateOrder()

Initialize the Orders form using the orders information in the CUSTOMER-DATA-BLOCK, with a PERFORM block and statements such as:

  perform varying row thru OrdersGridView::Rows
  move CUSTOMER-ORD-NO(array-ind) to  
      row::Cells::get_Item("OrderNo")::Value
  move CUSTOMER-ORD-DATE(array-ind) to dt1
  ...
OrderForm_FormClosing()

Handle the form closing event and move the locally held Dialog System data block back to the caller. The Marshal::Copy() method copies the pointer pData into _pCustomerDataBlock:

  set pData to CUSTOMER-DATA-BLOCK
  invoke type Marshal::Copy
  (pData, 0, _pCustomerDataBlock, LENGTH OF CUSTOMER-DATA-BLOCK)
OK_Click()

Handle the OK button click event.

Delete_Click()

Handle the Delete button click event.

OrderForm.Designer.cbl in .NET COBOL

This is the code behind the Windows Forms that defines the form and is generated from the design. See the Windows Forms tutorials.