Calling C functions from COBOL

I see in the ACUCOBOL-GT docs that it is possible to call C/C functions from COBOL using the CALL statement.

In Windows, I've managed to do this using pure C code that was compiled with gcc. This was done using the version of gcc that ships with MinGW in release 5.1.1 of the Qt framework.

If I try to call functions in a DLL that was compiled with g or cl.exe (the Visual C compiler), I keep hitting the ON EXCEPTION part of my CALL statement. Obviously I'm missing something.

I'm aware of the name-mangling issue with C compilers, and have tried to work around it by using __declspec(dllimport) and __cdecl in the C function declarations. However, this does not resolve the problem.

Can anyone provide a simple example of both C code and compile/link commands, which would allow C library code to be used from ACUCOBOL? And can this only be done using COM DLLs? (That's the one thing I haven't tried, because I don't have a development environment that supports creating COM DLLs.)

I'm interested in doing this both in Windows and Linux, with the current release of ACUCOBOL-GT.

Thank you in advance for any assistance!

  • Rather than me trying to come up with an artificial example, could you post a simple C file, your compile and link options, and a COBOL program that fails to call the function? I believe I would be able to then modify the source and/or the COBOL program in such a way that COBOL can call the function.

  • Sure. First of all I'll show what works. Here is a C file called mydll.c:

    #include <string.h>

    int multiply_long(long x, long y, long *z)

    {

     *z = x * y;

     return 0;

    }

    int multiply_double(double *x, double *y, double *z)

    {

     *z = (*x) * (*y);

     return 0;

    }

    int get_string(char *s, long len)

    {

     strncpy(s, "Hello, world! This is a string to be copied.", len);

     return 0;

    }

    =====

    Here is a COBOL program called dlltest.cbl:

           IDENTIFICATION DIVISION.

           PROGRAM-ID.  DLLTEST.

           DATA DIVISION.

           WORKING-STORAGE SECTION.

           77  XL   SIGNED-LONG.

           77  YL   SIGNED-LONG.

           77  ZL   SIGNED-LONG.

           77  XD   DOUBLE.

           77  YD   DOUBLE.

           77  ZD   DOUBLE.

           77  STR  PIC X(80).

           77  LEN  SIGNED-LONG.

           PROCEDURE DIVISION.

               CALL "mydll.dll"

               MOVE 2 TO XL

               MOVE 3 TO YL

               CALL "multiply_long"

                 USING BY VALUE XL, YL

                       BY REFERENCE ZL

                 GIVING RETURN-CODE

               END-CALL

               DISPLAY XL "x" YL "=" ZL

               MOVE 1.2 TO XD,

               MOVE 3.4 TO YD

         * DOUBLE data items cannot be passed BY VALUE.

               CALL "multiply_double"

                 USING BY REFERENCE XD, YD, ZD

                 GIVING RETURN-CODE

               END-CALL

               DISPLAY XD "x" YD "=" ZD

               MOVE ZEROS TO STR

               MOVE 13 TO LEN

               CALL "get_string"

                 USING BY REFERENCE STR

                 BY VALUE LEN

                 GIVING RETURN-CODE

               END-CALL

               DISPLAY STR(1:LEN)

               ACCEPT OMITTED

               CANCEL "mydll.dll"

               GOBACK.

    =====

    I tested the code shown above in Windows 7 as follows:

    ACUCOBOL-GT 8.1.3.1 is already installed.

    Install gcc (e.g., the version of gcc that ships in the MinGW flavor of Qt 5.1).

    Set the environment variables MINGW and ACU to point to the relevant bin directories.

    Open a command prompt, and execute:

    %MINGW%\gcc -c mydll.c

    %MINGW%\gcc -shared -o mydll.dll mydll.o

    �U%\ccbl32 dlltest.cbl

    �U%\wrun32 dlltest

    The output is as expected.

    =====

    I will paste in my C example next.

  • There is no need to compile the C code shown above as C . However, my goal is eventually to write a C wrapper that provides a C interface to a library written in C .

    =====

    A first simple-minded attempt to compile the C code as C is:

    %MINGW%\g -c -x c mydll.c

    %MINGW%\g -shared -o mydll.dll mydll.o

    �U%\ccbl32 dlltest.cbl

    �U%\wrun32 dlltest

    =====

    This yields the following error from wrun32:

    ---------------------------

    Error

    ---------------------------

    multiply_long: Program missing or inaccessible

    COBOL error at 000025 in dlltest

    ---------------------------

    OK  

    ---------------------------

  • A second simple-minded attempt in Visual Studio 2013 RC..

    In Visual Studio 2013 RC:

    File > New Project > Visual C > Win32 > Win32 Project.

    As the project name, enter "mydll".

    Click OK > Next, and select DLL as the application type.

    Click Finish.

    Paste the following code into the stub file mydll.cpp.

    By way of example, the implementation of the get_string() function below uses the C string type.

    =====

    // mydll.cpp : Defines the exported functions for the DLL application.

    //

    #include "stdafx.h"

    #include <string>

    #include <cstring>

    int multiply_long(long x, long y, long *z)

    {

    *z = x * y;

    return 0;

    }

    int multiply_double(double *x, double *y, double *z)

    {

    *z = (*x) * (*y);

    return 0;

    }

    int get_string(char *s, long len)

    {

    std::string cpp_str = "Hello, world! This is a string to be copied.";

    char *c_str = new char[cpp_str.length() 1];

    strncpy_s(s, len, c_str, len);

    return 0;

    }

    =====

    Click Project > Build to build the DLL.

    Copy the DLL to the folder containing the COBOL test program.

    Compile and run the COBOL program with:

    �U%\ccbl32 dlltest.cbl

    �U%\wrun32 dlltest

    The same error as before occurs in wrun32:

    ---------------------------

    Error

    ---------------------------

    multiply_long: Program missing or inaccessible

    COBOL error at 000025 in dlltest

    ---------------------------

    OK  

    ---------------------------

  • Next I tried decorating the function signatures with __declspec(dllexport) and __cdecl. The Visual Studio version of the mydll.cpp file then becomes:

    // mydll.cpp : Defines the exported functions for the DLL application.

    //

    #include "stdafx.h"

    #include <string>

    #include <cstring>

    __declspec(dllexport) int __cdecl multiply_long(long x, long y, long *z)

    {

    *z = x * y;

    return 0;

    }

    __declspec(dllexport) int __cdecl multiply_double(double *x, double *y, double *z)

    {

    *z = (*x) * (*y);

    return 0;

    }

    __declspec(dllexport) int __cdecl get_string(char *s, long len)

    {

    std::string cpp_str = "Hello, world! This is a string to be copied.";

    char *c_str = new char[cpp_str.length() 1];

    strncpy_s(s, len, c_str, len);

    return 0;

    }

    =====

    Build the DLL in Visual Studio.

    Copy it to the directory containing the COBOL test program.

    Execute:

    �U%\ccbl32 dlltest.cbl

    �U%\wrun32 dlltest

    The error is the same as before.

    Thank you for your help with this!

  • Verified Answer

    Got it. First, I only teseted this with Visual Studio. I am using VS2012, but the version you use should be the same.

    Visual Studio comes with a utility called DUMPBIN.EXE, which can show you what symbols are exported from a DLL. Since I didn't use gcc, I don't know what it does, but apparently (as on Linux) it exports all functions. So MYDLL.DLL built from gcc (as C) should have the three functions exported.

    When using VS, the __declspec(dllexport) is certainly required. Without it, the functions are not exported, and so not visible. The MYDLL.DLL built with VS (before adding the __declspec(dllexport) shows no functions visible, and so of course the runtime can't find them.

    Adding the __declspec(dllexport), rebuilding, and again running dumpbin shows that the function names are mangled:

    $ dumpbin /exports mydll.dll

    Microsoft (R) COFF/PE Dumper Version 10.00.40219.01

    Copyright (C) Microsoft Corporation.  All rights reserved.

    Dump of file mydll.dll

    File Type: DLL

     Section contains the following exports for MyDll.dll

       00000000 characteristics

       525ED4A3 time date stamp Wed Oct 16 11:02:11 2013

           0.00 version

              1 ordinal base

              3 number of functions

              3 number of names

       ordinal hint RVA      name

             1    0 0001118B ?get_string@@YAHPADJ@Z = @ILT 390(?get_string@@YAHPADJ@Z)

             2    1 00011195 ?multiply_double@@YAHPAN00@Z = @ILT 400(?multiply_double@@YAHPAN00@Z)

             3    2 00011168 ?multiply_long@@YAHJJPAJ@Z = @ILT 355(?multiply_long@@YAHJJPAJ@Z)

     Summary

           1000 .data

           1000 .idata

           3000 .rdata

           1000 .reloc

           1000 .rsrc

           7000 .text

          10000 .textbss

    The __cdecl addition you made does nothing to the names. That just specifies the calling convention (which is important!, but not yet). Instead, you need to make sure the names are not mangled. The way I found to do this is to surround the majority of the file with extern "C":

    extern "C" {

    __declspec(dllexport) int multiply_long(long x, long y, long *z)

    {

       *z = x * y;

       return 0;

    }

    [snip]

       return 0;

    } // end of function get_string

    } // matches extern "C" above

    Now when I build the dll and run the COBOL program, I get an assertion failure because the length you pass to get_string() is too small for the string you are trying to copy to the buffer. But the other two functions show the expected results.

    So I believe this answers your question.

    Note that extern "C"{} should also work with gcc, as this is part of the C standard.

  • It does work with g as well. I had tried using extern "C", but must have made some other error. Your code sample did the trick, which gets me past what had been a total roadblock. Thank you!