MEGMS logo - go to the home page
  1. Home

    1. About Us

    2. Services

    3. MEGS_Log

    4. CoalLog tools

    5. Fortran Tools

      1. FF08Depends

      2. FF08Diff

      3. FF08FixedFormForEver

      4. FF08Obfuscate

      5. Internal test utilities

      6. aniso_varying_string

      7. Direct2D API bindings

      8. Fortran shared pointers

    6. Contact Us

  2. Site map

"Let's model me a mine"

M.E.G.M.S.


Mining & Exploration Geological Modelling Services

Previous topic Parent topic Home topic Next topic

Fortran bindings for Direct2D and related APIs

Source code is available for bindings for the Direct2D API and some other related API's, for use from Intel® Fortran and compilers with similar compiler directive extensions. These bindings allow the Direct2D API to be accessed in a relatively natural way from Fortran. Then again, everything is relative, and natural isn't particularly an adjective I'd choose to use regarding Direct2D.

The level of the API supported is the 1.0 version - associated with the initial release of Windows® 7 (or Windows Vista® with the Platform Update). Bindings have not been provided for aspects of the API associated with later operating system versions.

Incomplete bindings for the DirectWrite API are also provided.

The latest source release (revision 2781 from 2017-02-06) is available under an Apache 2.0 licence, and can be downloaded from www.megms.com.au/download/winapi-bindings.zip.

The source code in the modules that provide the bindings is written in Fortran 2008, plus the Intel Fortran extensions that allow specification of the STDCALL calling convention. These modules do not rely on the existing Intel Fortran supplied bindings (IFWIN etc).

Code using the provided bindings should be able to be compiled without use of extensions, subject to there being some way of providing the basic framework of a GUI program.

The source for a small test/demonstrator program is included in the archive. That program is written in Fortran 2008, plus various Intel Fortran extensions used to provide the basic framework of a GUI program.

Intel Fortran 17.0 beta or later is required to compile the test program. Earlier versions of the compiler may be able to compile the modules in isolation. The /standard-semantics command line option is required.

(Intel Fortran 17.0 beta (and earlier versions) has some issues with the calling of finalizers that may result in Direct2D resource leaks when using this module. That compiler (and earlier versions) also has some issues with overloaded structure constructors, hence the otherwise overloaded structure constructor has been renamed.)

How to use the bindings

Consult the relevant MSDN documentation for details on the API, being mindful of the API level that the bindings support. Only the Fortran specific aspects are discussed below - this is not a Direct2D tutorial.

The underlying Direct2D API uses a "COM-light" approach, where Direct2D resources are represented by reference counted COM objects and are manipulated through a COM interface. A thin interface layer is provided with these Fortran bindings to simplify the interaction with COM.

All COM interfaces are part of an inheritance tree, with the IUnknown interface at the root. The bindings provide a derived type hierarchy, using Fortran 2003 type extension, that mirrors this interface heirarchy.

For example, the ID2D1SolidColorBrush COM interface extends ID2D1Brush, which extends ID2D1Resource which extends IUnknown. Fortran derived types with the same names and the equivalent type extension are provided.

Objects of types in this hierarchy automatically deal with maintenance of the relevant reference counting on the underlying COM object as the Fortran objects are deallocated or otherwise end their lifetime, with some edge case exceptions (do not use the SOURCE= specifier in an ALLOCATE statement to copy the value of these objects!).

Differences between COM interfaces and Fortran derived types result in some differences in how the Fortran objects are used relative to the underlying COM objects that the Fortran objects are managing.

Operations on the Fortran objects that correspond to COM interface method are provided via Fortran wrapper procedures.

Similar argument conventions as for the interface methods are used for the Fortran wrappers of the free standing functions in the API.

The binding also provides named constants and derived types that correspond to the enumerations and structures of the Direct2D API.

For enumerations, a named constant is provided with the same name as the API enumeration that nominates the integer kind to store a value in the enumeration. Enumerator values are then integer constants with the same name as the corresponding API enumerator.

Derived types corresponding to API structures have the same name as the API structure. Default initialized values for many components of the derived types have been provided that either result in the same value as the corresponding initializer in the C++ helper API, or some other reasonable value - for example a default initialized D2D1_SIZE_F object has both width and height components set to zero.

Overloaded structure constructors are provided for some of the derived types that either correspond to the C++ helper API constructors, or that are generally regarded as useful. See the source code for details.

(Due to compiler issues, these overloaded constructors have had to be renamed - with an _IFortBug suffix.)

Derived types, procedures and constants directly relevant to Direct2D can be found in the module named, unsurprisingly, Direct2D. Named constants for well-known colour values (if you are a fan of SVG or similar) can be found in the Direct2DColor module. DirectWrite related stuff is in the DirectWrite module, while some very isolated fragments of the Windows imaging component API (used for loading bitmaps from disk) can be found in the WinImagingComponent module.

Example

A source code fragment illustrating usage (atypical, for the sake of example) is given below. See the Direct2DTest main program for a more complete, compilable and typical example.

  USE COMBase       ! For Exists, Release, QueryInterface.
  USE Direct2D      ! For most other things.
  USE Direct2DColor ! For colour names.
  
  ! Created in code not shown.
  TYPE(ID2D1Factory) :: factory
  TYPE(ID2D1RenderTarget) :: render_target
  
  TYPE(ID2D1SolidColorBrush) :: solid_brush
  TYPE(ID2D1Brush) :: brush_array(2)
  TYPE(ID2D1SolidColorBrush), ALLOCATABLE :: tmp_brush
  CLASS(ID2D1Brush), ALLOCATABLE :: poly_brush
  INTEGER(HRESULT) :: hr
  
  ! Create a solid colour brush.  This is an example of an interface 
  ! method that is described as a function in the MSDN documentation, 
  ! that is a subroutine in the Fortran bindings, because it has two 
  ! outputs.  The interface method may also take an optional 
  ! D2D1_BRUSH_PROPERTIES argument, because that is optional it has been 
  ! moved in the Fortran procedure list to be after the hr argument 
  ! corresponding to the method's result.  It is then ommitted from this 
  ! call, because we just want the default brush properties.
  CALL CreateSolidColorBrush(  &
      render_target,  &
      Silver,  &   ! Color constant can be provided directly - alpha is 1.0.
      solid_brush,  &
      hr )
  ! A negative HRESULT code indicates failure.
  IF (hr < 0) ERROR STOP 'CreateSolidColorBrush failed.'
  
  ! Create another brush.  This one is in an allocatable scalar that we 
  ! need to allocate first.  We are using the renamed overloaded structure 
  ! constructor for D2D1_COLOR_F to specify an alpha other than 1.0.
  ALLOCATE(tmp_brush)
  CALL CreateSolidColorBrush(  &
      render_target,  &
      D2D1_COLOR_F_ifortbug(Green, 0.5_FLOAT),  &    ! Specify alpha.
      tmp_brush,  &
      hr )
  IF (hr < 0) ERROR STOP 'CreateSolidColorBrush failed.'
  
  ! An example of upcasting.  Here we put additional underlying references 
  ! to the brushes in an array of type ID2D1Brush.  Because brush_array is not 
  ! polymorphic (and the array constructor isn't either) it doesn't 
  ! "know" that it is managing an object with a ID2D1SolidColorBrush 
  ! interface.
  brush_array = [ solid_brush%ID2D1Brush, tmp_brush%ID2D1Brush ]
  
  ! To downcast from brush_array, we need to use the QueryInterface 
  ! procedure.  If the object represented by the first element of brush_array 
  ! supports the interface that is represented by the second argument, 
  ! then the second argument will become an additional reference to that 
  ! interface.  We re-use solid_brush for that here.  Because the second 
  ! argument is INTENT(OUT), the reference managed by solid_brush to the 
  ! Silver brush we created above will automatically be released (another 
  ! reference to that brush lives on in the first element of brush_array).
  !
  ! QueryInterace can also be used to upcast or sidecast (COM permits 
  ! the equivalent of multiple inheritance) in the COM interface hierarchy, 
  ! but that is not so relevant for Direct2D.
  CALL QueryInterface(brush_array(2), solid_brush)
  
  ! If brush_array(2) did not support the requested interface, then the 
  ! destination object will not exist.
  IF (.NOT. Exists(solid_brush)) ERROR STOP 'That was rather unexpected.'
  
  ! ID2D1SolidColorBrush extends ID2D1Brush with methods such as SetColor.  
  ! So we will change the colour of the green brush we created above to blue.  
  ! This also changes the alpha back to its default of 1.0.
  CALL SetColor(solid_brush, Blue)
  
  ! We are done with our additional reference to the same brush as 
  ! solid_brush(2), so we Release it.
  CALL Release(solid_brush)
  
  ! If you want to define a polymorphic object with current compiler 
  ! support, you have to MOVE_ALLOC the value into place.  tmp_brush 
  ! will not be allocated after this call (and hence it will no longer 
  ! be managing a brush object).  The dynamic type of poly_brush 
  ! is ID2D1SolidColorBrush.
  CALL MOVE_ALLOC(tmp_brush, poly_brush)
  
  ! Because poly_brush is polymorphic, we can use SELECT TYPE to downcast 
  ! through the Fortran heirarchy.
  SELECT TYPE (poly_brush)
  TYPE IS (ID2D1SolidColorBrush)
    ! We are still operating on the same brush as nominated by brush_array(2).
    CALL SetColor(poly_brush, Gold)
  END SELECT
  
  ! A allocatable object managing an underlying COM object releases the 
  ! underlying object when it is deallocated.
  DEALLOCATE(poly_brush)
  
  ! At this point we still have one reference each to two separate brushes, 
  ! stored in the two elements of brush_array.  If brush_array was an 
  ! unsaved local object, then when it went out of scope both those references 
  ! would automatically be released, and the underlying brush objects would 
  ! be destroyed.
  
  ! The above resources would generally not be created every drawing cycle.
  
  ! Start some drawing.  This is the BeginDraw method of the 
  ! ID2D1RenderTarget interface.
  CALL BeginDraw(render_target)
  
  ! Clear the target to white.
  CALL Clear(render_target, White)
  
  ! Draw a silver rectangle with the default line thickness.  We are 
  ! using the overloaded structure constructor for D2D1_RECT_F.  The 
  ! brush dummy argument of DrawRectangle is declared CLASS(ID2D1Brush), 
  ! so it will take ID2D1Brush Fortran objects, or any object that is 
  ! a type that is an extension of ID2D1Brush.
  CALL DrawRectangle(  &
      render_target,  &
      D2D1_RECT_F_IfortBug(  &
        D2D1_POINT_2F(10.0_FLOAT, 10.0_FLOAT),  &
        D2D1_POINT_2F(90.0_FLOAT, 90.0_FLOAT) ),  &
      brush_array(1) )
  
  ! Draw a gold ellipse with an explicitly provided line thickness.  This is 
  ! using the built-in structure constructor.
  CALL DrawEllipse(  &
      render_target,  &
      D2D1_ELLIPSE(  &
        D2D1_POINT_2F(50.0_FLOAT, 50.0_FLOAT),  &
        40.0_FLOAT,  &
        20.0_FLOAT ),  &
      brush_array(2),  &
      5.0_FLOAT )
  
  ! Complete drawing.  This method has one output, but is still implemented 
  ! as a subroutine, because of the state change of the render target that 
  ! is implied.
  CALL EndDraw(render_target, hr)
  IF (hr == D2DERR_RECREATE_TARGET) THEN
    ! See the MSDN documentation for more information about this - but 
    ! such a return code indicates that the program "lost the device" 
    ! for some reason, so device dependent resources need to be 
    ! released and recreated.  We do the release bit explicitly for our 
    ! remaining references here.
    CALL Release(brush_array)
  END IF

Feedback

The API is reasonably large and complicated, so invariably there will be things that have been missed.

Questions, queries and quibbles can be sent to ff08@megms.com.au.