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.
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.
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.
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
.
The following was tested in Visual Studio 2022
Create a new C# Class Library
Make sure you choose the correct one, Class Library (.NET Framework).
I will call mine HelloCom
We will change some of the project properties for COM integration.
Project -> HelloCom Properties.
Application -> Assembly Information
Check “Make assembly COM-Visible”
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”.
The following is the source code I used.
You can create GUID’s using Tools -> Create GUID.
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;
}
}
}
You may need to run as Administrator for it to output to the folder. This should create a
Click Browse and pick your generated TLB
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
Use HelloCommReal.pkg
Object oHelloCom is a cComServer
End_Object
Send CreateComObject of oHelloCom
Real rTest
Get ComComputeBasicMaths of oHelloCom to rTest
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
External_Function _Basic_Maths "computeBasicMaths" hello.dll Returns Float
Send Info_Box (_Basic_Maths()) "Output"
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" ""
Milliseconds: 19,865 Seconds: 19
Miliseconds: 5,792 Seconds: 5
Milliseconds: 2,698 Seconds: 2
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