Journal Articles

CVu Journal Vol 8, #1 - Feb 1996 + Programming Topics
Browse in : All > Journals > CVu > 081 (7)
All > Topics > Programming (877)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: Understanding Windows (8)

Author: Administrator

Date: 03 February 1996 13:15:26 +00:00 or Sat, 03 February 1996 13:15:26 +00:00

Summary: 

Body: 

The portability problem

The nature of Windows programming is such that program design tends to be dominated by the user interface and the demands of the target version of Windows. It is a fact of life that much existing code is very closely tied to the Windows 3.x API. There are all sorts of reasons why code will end up being difficult to port to another environment, not least being the difference in data sizes. Experienced programmers will instinctively avoid the worst of such traps - such as storing a POINT structure in the space taken up by a far pointer in the previous article. In the end any attempt to make the same body of code fully portable between say Windows 3.1 and 95 and NT will fail in that only a subset of features can be supported. For example a 16 bit application will be unable to take advantage of multithreading if simply re-compiled for 32 bits. Since in practice most software is written for one environment and then only ported as an afterthought, the port can be expected to be both difficult and ineffective.

Apart from avoiding the obvious pitfalls awaiting those who make unwarranted assumptions about data sizes, selector tiling, machine architecture (Windows NT isn't restricted to Intel processors), etc., what can the ordinary overworked programmer do?

The library solution

One of the fundamentals of software engineering is that user interface (UI) code should be separated from that which does the real work of the application. It is generally easiest to port code that isn't directly involved with the UI from one platform to another (including non Windows), as well as from one application to another. It is even easier if that code makes no direct API calls at all, for example substituting 'new' and 'delete' for global heap allocation functions.

As much code as possible should be written as 'library' code (at least conceptually) with well thought out interfaces that can be used in any environment. If this is done with care the resultant body of code can simply be re-compiled under any environment. It is a matter of choice whether this is a set of C functions or a true class library - I'd go for the class library every time.

In order to build a finished application it is then necessary to bolt on the platform specific components. These components can be thought of as coming in two flavours, first those for which the library code is a 'client' and secondly those which are clients of the library. The heap management routines (mentioned above), which are linked in behind the scenes, fall into the first category, as do any other substitutable drivers, hardware interfaces or whatever. The user interface code will largely be in the second category, i.e. a client of the library code.

This is best understood with a diagram, so I suggest that the interested reader goes away and draws one.

Hint: Try to make the library domain as large as possible and its interface as simple as possible. Make platform specific components as minimal as possible - that's it, you're getting the idea!

Alternative user interfaces

The inevitable thrust of the preceding articles is concerned with using the Windows API to provide the user interface to your program. Indeed example programs have been almost all UI and little real substance. This API when used with care can be reasonably portable across Windows platforms. The C++ equivalent is to use MFC. as a cleaner interface to the API. This used sensibly gives better portability to other Windows platforms, including simulating some features that aren't fully supported between '95 and NT.

Other class libraries that provide a more abstract view of the UI are available. These promise to enable source code portability across a wider range of platforms, at the cost of only supporting a common subset of features and involving some extra complexity.

In general, class libraries should support the 'document - view' architecture. I recommend that you should make the interface between your own platform independent code and the UI concentrate in one or more specialisations of the document class. As with the discussion above I feel that it is a mistake to allow references to the UI class library to creep into your own library code. Unless you are totally enamoured of the third party library you will probably want to separate them at some point in the future.

One of the above alternatives will suit many developers, depending on their particular needs.

On the other hand, if you are concentrating on Windows targets and wish to integrate your code with applications written in other languages (or macro languages in third party applications) then other possibilities arise.

The simplest is to make your code into one or more DLLs, these export a simple functional interface and being language independent can be used almost anywhere.

Other alternatives include the venerable 'VBX control', the exciting new 16 and 32 bit OLE custom controls and full OLE 2 server applications, all of which require significant extra work to produce.

All of these separate compiled components can be shared across multiple application instances, so they should be considered for use with purely C/C++ applications.

The Visual Basic alternative

I find it difficult to raise any real enthusiasm for this language but with many companies using VB 3.0 and its close relatives - VBA (Visual Basic for Applications) in other Microsoft products, we have to take it seriously.

The main reason for its popularity is that it is very easy to produce a running application without needing either skill or patience. For this reason it is often put forward as an ideal proto-typing tool, frankly I believe this not to be the case, it isn't really difficult to produce an equivalent UI framework in C/C++.

VB applications have a certain indefinable 'look and feel' which I think comes from the 'form' concept, combined with the general availability of icons as a UI element.

Where VB is useful is in providing a 'quick and dirty' test bed for DLLs as well as producing finished applications for vertical markets or bespoke applications where there may be no justification for producing optimal solutions and where maintenance by less skilled programmers is considered desirable.

For all its ease of use, VB is a positive minefield when applied to large projects. It is not an OOP language and gets out of control if you aren't careful for anything over - say - 10,000 lines of code. Having said all of that, it handles databases (Access 1.1) very well and has a quite reasonable exception handling mechanism.

As with all languages it is desirable to separate UI from the rest and VB does this by allowing you to attach code to 'events' within each form module but also allows you to write code in modules which are not associated with forms. Some abstraction is possible in terms of references to VB objects (forms and controls) but sadly less in terms of programmer defined types.

Some Visual Basic specifics

The basic data types correspond more or less with those used in C, there is no concept of unsigned integers and no equivalent of 'char'. VB types include 'Integer'(16 bit signed), 'Long'(32 bit signed), 'Double' and 'String'.

VB functions and subroutines take parameters that default to passing by reference but can be forced to be passed by value. When a DLL function is declared as taking a string parameter passed by value VB has the decency to pass a pointer to a null terminated string!

Strings can be made large enough to accommodate the DLL function writing back to it. This needs to be handled with care. It is advisable to pass the buffer length as a separate parameter.

Simple data structures can be declared as 'types'.

One of the many deficiencies of VB is that it is difficult to create dynamic instances of programmer defined types, large objects have to be created statically, on the stack or as elements of an array (which may grow and shrink). It is also extremely difficult to represent associations between objects due to the difficulty of taking an object's address. The best you may be able to do is to use an index into an array of objects. An example that comes to mind is that maps tend to be implemented by a linear search of an entire array (separate code for each map usually).

Type safety is good for those parameters that are properly declared and which VB understands: A structure reference parameter will be properly checked whereas a pointer masquerading as a 'long' cannot be checked (after all you have just evaded type checking - like casting in C ).

Problems with handling objects

Whether you are using OOP or not you will probably want to create and destroy large data instances within your C/C++ DLL, managing these objects and their associations internally.

The option of allowing the VB code to create the objects and pass references for the DLL to act upon may be appropriate in some circumstances but it does leave responsibility for the object divided uncomfortably. Finally, it doesn't allow for inheritance in C++, you may well want to expose only the base object to VB code when in fact the object could be of a derived class.

The traditional mechanism is to have a set of functions corresponding to constructor (called with the 'new' operator), member functions and a destructor (called with 'delete'). The creation function returns a 'handle' to the object that it has created an instance of (on the heap). This handle is then passed to the other functions, finishing with the destructor (which frees the heap memory after performing cleanup). This scheme works well but there are a number of drawbacks. The handle will be an integer type ('Long' or 'Integer') in VB representing a pointer in C (either 'near' with a known selector or 'far' more usually). There is nothing to stop the VB code from modifying it between uses or calling the functions in the wrong order, such as calling the destructor before a member function or calling a member function before the constructor. Furthermore there is nothing to prevent a handle to some other type of object being passed or even some other number, simply because VB can't differentiate between them.

A safer but slower version uses the handle as an abstract identifier and maps that to a pointer, failing gracefully when the wrong value is passed. This is of course still not proof against accidentally passing a valid ID of the wrong type of object.

Consider another problem, if you are using a handle to the object, you will need to provide a set of access functions to retrieve attribute values. On the other hand if the object is 'owned' by the VB code, there is no privacy enforceable, any VB code that can access the object can modify its members.

A solution (or how I do it)

For each class that you wish to work with, declare a VB type that contains a handle and any other members that may be useful for information purposes. The handle is used by the DLL to store the address of its actual class instance. Each function takes a reference to this object as its first parameter, possibly writing to the members.

The VB code may read the information members that will be updated by the DLL functions. So long as the VB code doesn't actually modify the handle member, the functions can only be called with valid references and the handle only has to be checked for zero by the functions. This relies on the fact that VB initialises all data instances (including automatic ones) to zero before they are used.

Passing a reference to a type understood by VB enforces a lot more type safety than the direct handle mechanism but with the real object being in the hands of the DLL allows for the full C++ class semantics to be applied.

Having additional attributes in the interface structure is optional but useful. I would advocate that these are only written to by the DLL functions, and where the VB code needs to supply new values that these are passed as separate arguments.

Example DLL interface

As an example, imagine an interface to a lift class, the interface structure can be declared in 'C':

struct LiftData { int num_floors; int max_people; int cur_floor; LiftBase *pLift; };

Assuming that we have a class 'LiftBase' define some exportable functions:

#ifdef __cplusplus extern "C" { #endif void __export CALLBACK lift_create(LiftData& ld, int num_floors, int max_people ){ ld.pLift = new LiftBase( num_floors, max_people ); ld.num_floors = num_floors; ld.max_people = max_people; ld.cur_floor = ld.pLift->get_cur_floor(); } void __export CALLBACK lift_destroy(LiftData& ld) { delete ld.pLift; ld.pLift = 0; } void __export CALLBACK lift_request( const LiftData& ld, int floor_num, BOOL in_lift) { if( ld.pLift ) ld.pLift->request( floor_num, in_lift ); } // ... and more 'lift_' procedures #ifdef __cplusplus } // End extern "C" #endif

The above fragments illustrate the technique. The functions correspond with the public interface to the LiftBase class (more or less). If other classes are derived from LiftBase, there would need to be extra creation functions for these types. Assuming that virtual functions (and destructors) are used throughout, the existing 'member' functions remain equally valid. LiftData represents an abstract type - this gives you an OOP interface to the DLL!

This DLL interface is aimed at VB but would be perfectly good for any client language.

Used from Visual Basic

Sadly, VB doesn't understand C header files, nor will it read import libraries. We have to declare the functions in a '.bas' module:

Type LiftData num_floors As Integer max_people As Integer cur_floor As Integer pLift As Long End Type Declare Sub lift_create Lib "lift.dll" (ld As LiftData, ByVal n_f As Integer, ByVal max_p As Integer) Declare Sub lift_destroy Lib "lift.dll" (ld As LiftData) Declare Sub lift_request Lib "lift.dll" (ld As LiftData, ByVal f_num As Integer, ByVal in_lift as integer)

These 'Declare Sub' lines declare the signature of the procedure as well as its DLL name in one go. Notice that all parameters that are passed by value are explicitly declared as such. It's important to get these declarations correct; failure to do so will lead to garbage being passed to the DLL functions!

Finally

This article has rambled over a wider area than purely Windows programming. It's good to keep in mind the fact that most Windows applications do have a wider context. Putting it another way, don't get too wrapped up in the Windows specifics and leave your UI options as open as you can.

The VB - DLL interface ideas are presented as a result of my own experiences, so if you have any further thoughts on them please write in with your own contribution/criticism!

Notes: 

More fields may be available via dynamicdata ..