11 February 2023

DataFlex - COM Automation vs DLL calling overhead

In DataFlex, there’s a few common ways to share code between applications. One is via SOAP and Webservices, which is generally used externally and over a network. The other is using COM Automation and DLL’s. The latter two not being able to be written in DataFlex but are able to be consumed by.

The focus of this is on the latter, COM Automation and DLL’s. More specifically, how fast can we call C# vs Rust—that is, the overhead of the calls, not how fast the individual languages are.

What is a DLL

A DLL (Dynamic Link Library) is a file that has a set of executable code and data that can be used by one or more programs simultaneously. It is a shared library that is loaded into memory at runtime when the program needs to use its functionality. The program can then call functions and access resources from the DLL as if they were part of the program itself.

DLLs are commonly used in Windows for a variety of purposes, including providing system-level functionality, supporting graphics and multimedia, and providing functionality for applications that are generally done at a lower level.

What is COM?

COM Automation (Component Object Model Automation) is a technology used to enable software components to communicate and exchange data with each other across different programming languages, platforms, and applications. It is a mechanism that allows applications to automate or control other applications using a common language interface.

COM Automation is widely used in Microsoft Windows operating systems and is supported by many programming languages, including C++, Visual Basic, and .NET languages such as C# and VB.NET. It allows developers to create reusable software components that can be easily integrated into other applications or systems, reducing development time and improving overall software quality.

Create the DataFlex project

To get started we will create a DataFlex workspace ComVsDLL. I will be using DataFlex 2023 Alpha 2 for my testing. We will create a basic project as we don’t need any database connectivity.

This is located at C:\Dataflex Projects\ComVsDLL.

Setting up the COM example

The following was tested in Visual Studio 2022

Create a new C# Class Library

Create Visual Studio Class Library

Make sure you choose the correct one, Class Library (.NET Framework).

I will call mine HelloCom

Register COM properties

We will change some of the project properties for COM integration.

Project -> HelloCom Properties.

Application -> Assembly Information

Check “Make assembly COM-Visible”

Visual Studio Assembly Information

Build -> Output Path

The Output build path for simplicity to the the DataFlex folder.

C:\Dataflex Projects\ComVsDLL\programs\

While here, click “Register for COM Interop”.

Project Settings Output

Make the functions COM Visible.

The following is the source code I used.

You can create GUID’s using Tools -> Create GUID.

Visual Studio Create GUID

Example Sourcecode

using System;
using System.Runtime.InteropServices;

namespace HelloCom
{
    [ComVisible(true)]
    [Guid("EF0E881C-CAAD-4112-BD58-B104D42ED511")]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IServer
    {
        [DispId(1)]
        double ComputeBasicMaths();
    }

    [ComVisible(true)]
    [ProgId("HelloCom")]
    [ClassInterface(ClassInterfaceType.None)]
    [Guid("30081443-52A7-4489-9120-D9E97D299560")]
    public class Server : IServer
    {
        double IServer.ComputeBasicMaths()
        {
            return 20.0 * 2;
        }
    }
}

Build

You may need to run as Administrator for it to output to the folder. This should create a

Integrate the COM into DataFlex

Create New Import COM Automation

Click Browse and pick your generated TLB

Import COM Automation Library

This will generate a class like the following

// DataFlex COM proxy classes generated from C:\DataFlex Projects\ComVsDLL\Programs\HelloCom.tlb
Use FlexCom20.pkg

// CLSID: {EF0E881C-CAAD-4112-BD58-B104D42ED511}
Class cComIServer is a Mixin

    Function ComComputeBasicMaths Returns Real
        Handle hDispatchDriver
        Real retVal
        Get phDispatchDriver to hDispatchDriver
        Get InvokeComMethod of hDispatchDriver 1 OLE_VT_R8 to retVal
        Function_Return retVal
    End_Function
End_Class

// CoClass
// ProgID: HelloCom
// CLSID: {30081443-52A7-4489-9120-D9E97D299560}
Class cComServer is a cComAutomationObject
    Import_Class_Protocol cComIServer

    Procedure Construct_Object
        Forward Send Construct_Object
        Set psProgID to "{30081443-52A7-4489-9120-D9E97D299560}"
        Set peAutoCreate to acNoAutoCreate
    End_Procedure
End_Class

To use the COM the following code sample

Use HelloCommReal.pkg

Object oHelloCom is a cComServer
End_Object

Send CreateComObject of oHelloCom

Real rTest
Get ComComputeBasicMaths of oHelloCom to rTest

Create the Rust example

Make the Cargo.toml a cdylib

[package]
name = "hello"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ['cdylib']

Then add a file for the computing basic maths

#[no_mangle]
pub extern fn computeBasicMaths() -> f32 {
    return 20.0*2.0;
}

Run cargo build

Copy target\debug\hello.dll to the programs directory of the Dataflex project

Example to call the Rust library from DataFlex

External_Function _Basic_Maths "computeBasicMaths" hello.dll Returns Float

Send Info_Box (_Basic_Maths()) "Output"

Setup the benchmark Code

The following tests the calls 10,000,000 times then benchmarks the call time of each.

Use Windows.pkg
Use cHtmlHelp.pkg
Use cApplication.pkg
Use cConnection.pkg

External_Function _Basic_Maths "computeBasicMaths" hello.dll Returns Float

Use HelloCom.pkg

Object oHelloCom is a cComServer
End_Object

Send CreateComObject of oHelloCom

Procedure BenchmarkCom
    Real rtest
    Integer iCount iMillseconds iSeconds
    DateTime dtStart dtFinish
    TimeSpan tsTotal

    Move (CurrentDateTime()) to dtStart
    For iCount from 0 to 10000000
        Get ComComputeBasicMaths of oHelloCom to rTest
    Loop
    Move (CurrentDateTime()) to dtFinish

    Move (dtFinish - dtStart) to tsTotal

    Move (SpanTotalMilliseconds(tsTotal)) to iMillseconds
    Move (SpanTotalSeconds(tsTotal)) to iSeconds

    Showln (SFormat("MilliSeconds %1", iMillseconds))
    Showln (SFormat("Seconds %1", iSeconds))
End_Procedure

Procedure BenchmarkDF
    Real rtest
    Integer iCount iMillseconds iSeconds
    DateTime dtStart dtFinish
    TimeSpan tsTotal

    Showln "DataFlex Version"

    Move (CurrentDateTime()) to dtStart
    For iCount from 0 to 10000000
        Add (20.0 * 2) to rtest
    Loop

    Move (CurrentDateTime()) to dtFinish

    Move (dtFinish - dtStart) to tsTotal

    Move (SpanTotalMilliseconds(tsTotal)) to iMillseconds
    Move (SpanTotalSeconds(tsTotal)) to iSeconds

    Showln (SFormat("MilliSeconds %1", iMillseconds))
    Showln (SFormat("Seconds %1", iSeconds))
End_Procedure

Procedure BenchmarkRust
    Real rtest
    Integer iCount iMillseconds iSeconds
    DateTime dtStart dtFinish
    TimeSpan tsTotal

    Showln "Rust Version"

    Move (CurrentDateTime()) to dtStart
    For iCount from 0 to 10000000
        Move (_Basic_Maths()) to rtest
    Loop
    Move (CurrentDateTime()) to dtFinish

    Move (dtFinish - dtStart) to tsTotal

    Move (SpanTotalMilliseconds(tsTotal)) to iMillseconds
    Move (SpanTotalSeconds(tsTotal)) to iSeconds

    Showln (SFormat("MilliSeconds %1", iMillseconds))
    Showln (SFormat("Seconds %1", iSeconds))
End_Procedure

Send BenchmarkCom
Send BenchmarkDF
Send BenchmarkRust

Send Info_Box "Hello World" ""

And the results are …

COM Automation

Milliseconds: 19,865 Seconds: 19

Rust

Miliseconds: 5,792 Seconds: 5

Dataflex

Milliseconds: 2,698 Seconds: 2

What should I use?

If you are doing many calls, constantly, then using a .DLL is important. If you are just doing a single call in a web request, then offloading it to C# and back, then the overhead is minimal. Noting that it tooks 19,965 milliseconds to call the COM 10 million times. Which is 1.996 microseconds.

tags: rust - dataflex - dll - csharp - dotnet - benchmarking