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 thename_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 alibname
attribute which is theDLL
(Windows) or shared object (Linux
orUNIX
) name and atype="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 otherRUN
statement. This includes all the parameter processing. The backing functionality is in theControlFlowOps.invoke*()
set of methods. More details can be seen in Control Flow.
- The
- 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) incom.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 uponlibffi
for actual function pointer execution and implementation of the calling convention details.libffi
has its own JNI (native code) component.
- The library will be loaded using platform-specific JNI code (see
- Most of the processing is in Java.
- When called,
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.