Project

General

Profile

Native API Calls

Introduction

FWD fully supports the ability to call functions in shared libraries on Linux, and dynamic link libraries (DLLs) on Windows. The minor exception is currently support for the PASCAL calling convention. In order to support the calling of native library calls from Java code at runtime, FWD depends on Portable Foreign Function Interface Library (also known as libffi, https://sourceware.org/libffi/). The FWD JNI library is dependent upon libffi. Internally it loads the library and obtains a function pointer which is called from native code.

The basic 4GL syntax is:

PROCEDURE procname EXTERNAL "so_name/dllname":
DEFINE <type> PARAMETER ...
END PROCEDURE.

RUN procname_defined_via_procedure_external (parameters).

The basic idea:

  • Conversion
    • The PROCEDURE EXTERNAL will convert as definitions in the name_map.xml which is stored in the application .jar file and is used at runtime to map control flow operations (e.g. RUN) to the proper converted Java classes, interfaces and methods. Such mapping entries will have a libname attribute which is the DLL (Windows) or shared object (Linux or UNIX) name and a type="DLL-ENTRY". There is no Java source code emitted for this 4GL code.
    • Any RUN statement that targets a native library call will convert as any other RUN statement. This includes all the parameter processing. The backing functionality is in the ControlFlowOps.invoke*() set of methods. More details can be seen in Control Flow.
  • Runtime
    • When called, ControlFlowOps.invoke*() statement will resolve the target procedure.
    • Native library procedures (those marked type="DLL-ENTRY") will cause:
      • The library will be loaded using platform-specific JNI code (see src/native/library*). This same JNI code will be used to obtain a function pointer.
      • The RUN statement itself will have the core processing (including much of the error handling and parameter processing) in com.goldencode.p2j.library.NativeInvoker and related server-side code.
      • The low level invocation is implemented using the LibraryManager which is usually run on the client side. LibraryManager relies upon libffi for actual function pointer execution and implementation of the calling convention details. libffi has its own JNI (native code) component.
    • Most of the processing is in Java.

As you will see below, there is little, to no, platform difference in the converted code. Full details of the implementation can be found in #1634.

Simple Linux Example

The below procedure sets up and calls the get_current_dir_name function in the libc.so.6 shared library on Linux:

DEFINE VARIABLE ptrToString AS MEMPTR NO-UNDO.
DEFINE OUTPUT PARAMETER chrDirectoryName AS CHARACTER FORMAT "X(256)" NO-UNDO.

PROCEDURE get_current_dir_name EXTERNAL "libc.so.6":    
  DEFINE RETURN PARAMETER ptrToString AS MEMPTR.
END PROCEDURE. 

SET-SIZE(ptrToString) = 256. 

RUN get_current_dir_name (OUTPUT ptrToString). 

IF get-pointer-value(ptrToString) = 0 THEN
   DO:
      ASSIGN chrDirectoryName =  "".
      MESSAGE "Function call failed, not sure why" VIEW-AS ALERT-BOX.
   END.
ELSE
   ASSIGN chrDirectoryName = GET-STRING(ptrToString,1).

SET-SIZE(ptrToString) = 0.

The name_map.xml method mapping for this is:

    <method-mapping jname="getCurrentDirName" libname="libc.so.6" pname="get_current_dir_name" type="DLL-ENTRY">
      <parameter jname="ptrToString_1" mode="RETURN" pname="ptrToString" type="MEMPTR"/>
    </method-mapping>

And the pertinent converted Java:

.
.
.
         ptrToString.setLength(256);
         ControlFlowOps.invokeWithMode("get_current_dir_name", "O", ptrToString);

         if (_isEqual(ptrToString.getPointerValue(), 0))
.
.
.

Simple Windows Example

The below procedure sets up and calls the GetCurrentDirectoryA function in the KERNEL32.DLL Dynamic Link Library on Windows:

DEFINE VARIABLE intBufferSize    AS INTEGER   NO-UNDO INITIAL 256 .
DEFINE VARIABLE ptrToString      AS MEMPTR    NO-UNDO. 
DEFINE VARIABLE intResult        AS INTEGER   NO-UNDO.
DEFINE VARIABLE chrDirectoryName AS CHARACTER FORMAT "X(256)" NO-UNDO.

PROCEDURE GetCurrentDirectoryA EXTERNAL "KERNEL32.DLL":    
  DEFINE INPUT        PARAMETER intBufferSize AS UNSIGNED-LONG.    
  DEFINE INPUT-OUTPUT PARAMETER ptrToString   AS MEMPTR.    
  DEFINE RETURN       PARAMETER intResult     AS UNSIGNED-LONG.
END PROCEDURE. 

SET-SIZE(ptrToString) = 256. 

RUN GetCurrentDirectoryA (INPUT intBufferSize, INPUT-OUTPUT ptrToString, OUTPUT intResult). 

ASSIGN chrDirectoryName = GET-STRING(ptrToString, 1). 

IF intResult = 0 THEN
   MESSAGE "Function call failed, not sure why" VIEW-AS ALERT-BOX.
ELSE IF intResult = LENGTH(chrDirectoryName) THEN       
   MESSAGE "Result = " + chrDirectoryName VIEW-AS ALERT-BOX.
ELSE
   MESSAGE "Buffer size is too small.  Must be at least " + STRING(intResult) VIEW-AS ALERT-BOX. 

SET-SIZE(ptrToString) = 0.

The name_map.xml method mapping for this is:

    <method-mapping jname="getCurrentDirectoryA" libname="KERNEL32.DLL" pname="GetCurrentDirectoryA" type="DLL-ENTRY">
      <parameter jname="intBufferSize_1" mode="INPUT" pname="intBufferSize" type="UNSIGNED-LONG"/>
      <parameter jname="ptrToString_1" mode="INPUT-OUTPUT" pname="ptrToString" type="MEMPTR"/>
      <parameter jname="intResult_1" mode="RETURN" pname="intResult" type="UNSIGNED-LONG"/>
    </method-mapping>

And the pertinent converted Java:

.
.
.
         ptrToString.setLength(256);
         ControlFlowOps.invokeWithMode("GetCurrentDirectoryA", "IUO", intBufferSize, ptrToString, intResult);
         chrDirectoryName.assign(ptrToString.getString(1));
.
.
.

© 2004-2022 Golden Code Development Corporation. ALL RIGHTS RESERVED.