Problem:
The customer was moving both COBOL and C source programs from a Unix envirornment using OCDS to Windows XP running Net Express. Under Unix they created shared object libraries for their C programs and then they called these programs from COBOL main programs.
They did not have to use a call-convention in their COBOL programs and the C entry points were found without a problem.
After the customer moved the source code to Windows they decided to compile and link their C programs using Microsoft C/C 6.0 to create a single Dynamic Link Library (.DLL) containing all of the C programs.
When they compiled the COBOL programs and attempted to call the C entry points within the .DLL they were getting RTS 173 "Program not Found" errors on the call.
The customer did not understand the different call-conventions that can be used within a Windows program and they evidently did not know what C compiler or linker options to use in order to make the C entry points visible to COBOL programs.
They also had problems understanding how to locate and load a .DLL from their COBOL main programs so that they could call the C programs correctly.
The customer had used Visual Basic previously and he remembered that all he had to do in order to call a DLL was to add a reference to it within the VB project.
I then explained the differences between calling a COM in-process server .DLL from Visual Basic and calling a standard system .DLL from COBOL.
Resolution:
Multiple language applications can be a tricky subject under Windows.
The developer must understand what the individual compiler and linker tools are doing behind-the-scenes in order to determine the correct directives and call-conventions that need to be used in order for COBOL and C to cooexist.
The Net Express Help has an entire chapter devoted to mixed-language programming and it covers the C language extensively.
The following information contains a more detailed explanation of how to call C .DLLs from COBOL under Windows.
Notes: Assuming that the following items are true.
1. A COBOL main program is being linked as Dynamic Shared into an .EXE program.
2. One or more C programs is being compiled and linked into a Dynamic Link Library.
We do not know the C compiler being used but we will assume that it is Microsoft C/C 6.0.
We will also assume that the two programs "csub1.c" and "csub2.c" are being compiled into objects and are being packaged into the program "csubprog.dll".
3. In the support case log the customer wrote that the following code was successful:
Special-Names.
Call-Convention 65 is C-CALL.
...
01 PP usage is Procedure-Pointer value NULL.
...
SET PP to entry 'CJUTIL'.
CALL C-CALL 'atextest' USING TEST-FLAG
TEST-LENGTH
TEST-DATA.
I do not think that the calling convention of 65 would make this work unless the C programs are being specified with the Pascal call-convention.
The call-conventions decimal value of 65 means that bit 0 is turned on and bit 1 is turned off.
This is the exact opposite of how most Windows 32-bit programs are called.
These values cause the specified parameters to be passed on the stack from left to right instead of right to left (Windows standard) and the calling program will clean the parameters from the stack instead of the called program (again Windows standard).
This call-convention of Pascal was used frequently in the development of 16-bit Windows programs but it is hardly ever used in 32-bit Windows programs.
In 32-bit Windows, C programs are usually compiled with either the "C" calling convention (default in Net Express) or the WinAPI calling convention when making function calls within the Operating System libraries.
If these same COBOL source programs succesfully called these same C programs under Unix without any source code change then they should not have to be modified in order to run with Net Express under Windows.
If the calling is not working then it is due to one or more of the directives being used by the C compiler or Linker being set in error and it is generating the entry point names using an invalid call-convention. The directives being used for creating the C .DLLs need to be changed so that the C function names in the .DLL are generated with either the standard "C" call-convention of "_cdecl" or the Windows API call-convention of "_stdcall" or "APIENTRY".
In a Net Express program, the default convention is the "_cdecl" call-convention which is 0 (same as Unix) which means that the entry point names are case sensitive and will be generated as defined with a leading "_" character.
The WinAPI call-convention which is not used under Unix, causes the entry point names to be decorated as follows:
If the entry point name was "csub2" and it used 2 parameters in the call, then the C compiler would generate the name as "_csub2@8".
The name is case sensitive, a leading "_" is added and a suffix of @nn is added where nn is the number of parameters passed multiplied by 4.
When you specifiy a call-convention in Net Express you are simply telling the compiler to generate the names used in the call statements so that they will match the name of the entry point compiled into the .DLL.
This does not mean that you have to modify all of your call statements in your COBOL programs.
You can do a couple of things.
1. Use the compiler directive DEFAULTCALLS"nn" where nn would specify the default call-convention to use for all of your call statements.
So if your C programs are all compiled with the Pascal call-convention then you could use
DEFAULTCALLS"65" to automatically generate the Pascal call convention.
2. If you have the source code to your C modules and you can control how they are being
compiled by the C compiler then you should change the call-convention of your 32-bit C
programs to match either the C or WinAPI call-conventions. You can do this by using the
correct C compiler directive or you can manually change the entry point names by specifying
one of the following call-conventions:
You can change your entry points in a C program manually to conform to the C call convention
(Net Express default) like:
extern "C" __declspec(dllexport) int __cdecl csub2(int param1 int param2)
or you can change the entry point to the WinAPI call-convention using the following:
int APIENTRY csub2( int param1 int param 2)
Standard Windows Dynamic Link Libraries should contain an entry point called "dllmain" just like a standard C main program contains an entry point called "main".
This entry point name, if it exists, is called first by default when a .DLL is loaded.
In a COBOL program this would be whenever you:
1. Used a procedure-pointer to load the .DLL.
01 proc-point procedure-pointer.
set proc-point to entry "CJUTIL"
2. Used a standard COBOL call statement using the physical name of the .DLL file on disk:
call "CJUTIL"
This call statement would load the file CJUTIL.DLL and make it's entry points known to the calling program.
If CJUTIL.DLL contained the entry points for csub1 and csub2 then you could call these entry points only after the first CALL of the DLL had been done.
But if the entry points that you need to call are different than the main entry point name of the DLL file then you have to use the proc-pointer method.
You cannot call the entry-point of any program in the DLL unless the entry point is the same as the name of the .DLL or the .DLL was loaded previously.
Actually I believe that in the sample code that once you load the entry points by using the proc-pointer method then your COBOL program can then call the entry points using their undecorated names and without using a call-convention so the CALL-CONVENTION 65 example may just be a fluke.
You can test this easily by removing the call-convention name from the call like the following:
01 PP usage is Procedure-Pointer value NULL.
...
SET PP to entry 'CJUTIL'.
CALL 'atextest' USING TEST-FLAG
TEST-LENGTH
TEST-DATA.
to see if the entry points are found without the call-convention?
The methods that I have discussed so far are all used when calling the C entry points dynamically, which means that the entry points are not known until run-time when the .DLL gets loaded. The call-convention used for dynamically calling a C function specified as "_cdecl" in the .DLL is 0 so this is also the default if no call-convention is specified at all.
The call-convention used for dynamically calling a C function specified as WinAPI or APIENTRY would be 66 (instead of 65!)
There is another method that can be used which is called Static calling.
When the C compiler and linker generate the .DLL that you are calling, you can specify a switch which will tells the linker to generate an import Link Library.
This file would have the same name as the .DLL with an extension of .LIB instead.
These import libraries contain all the information about the visible entry points that exist within the .DLL including the DLL name in which the entry point is found and the names of any additional dependant DLLs that should also be loaded at run-time.
You can link an Import Library to your COBOL program when it is built and then all of the entry point names within the CALL statements would be resolved at link time instead of run-time.
To do this, you can modify the Project Build Settings for your COBOL program on the Link tab under Advanced options you can specify the location and the name of any import libraries that you would like to link to.
For any CALL statements in your program where the entry point name is referenced directly such as CALL "atextest" the linker will attempt to resolve the entry point name within the import library so that it can generate the DLL name where the entry point can be found and store this information along with the CALL statement.
At run-time when your COBMAIN.EXE program is executed it will automatically load all necessary .DLLs which eliminates the need for you to explicitly load the DLL using the set proc-pointer method.
The subprogram will automatically be found within the loaded .DLL.
The call-convention used for statically calling a C function specified as "_cdecl" in a .DLL is 8 which is also referred to as LITLINK and can be turned on using a compiler directive of the same name. The call-convention used for dynamically calling a C function specified as WinAPI or APIENTRY would be 74.
Linking directly to Import Libraries is the preferred method to use when your program makes calls to the functions within the Windows API.
Net Express will automatically reference the main Windows import libraries such as Kernel32.lib, User32.lib and gdi32.lib etc. each time an EXE or .DLL program is rebuilt.
It does this by passing these file names directly to the system linker.
These will resolve the API calls at link time so that the linker can generate the extra information about the name of the containing DLL so the CALL statement will know in which .DLL the OS function actually resides.
Linking to an Import Library is not the same thing as linking to an archive library which contains the actual object code for a specified program.
When you link to an archive library it is the same as if you were linking the object files of the calling and called programs directly together.
Linking to an import library only stores a reference to the .DLL name and the actual format of the entry point name.
This is how programs are generated, linked and loaded in the Windows environment.
The Unix environment is somewhat different.
You do not create .DLL''s in the Unix environment but you can create shared object libraries instead.
If an entry point exists within a shared library and the shared library is not located in the calling programs directory then you still need to set an environment variable so that the Unix program loader knows where to search for the called programs.
There are also a few different types of Dynamic Link Libraries under Windows.
The standard Dynamic Link Libraries that are generated by Net Express and Microsoft C are viewed as extensions of the main calling program because these .DLL's are loaded into the same process and memory space as the calling .EXE program.
Once the .DLL is loaded then all of the programs within the .DLL become subprograms of the main EXE.
The other type of Dynamic Link Library that you had referred to in the support case log as a .DLL that you added as a Reference to a Visual Basic project is called an in-process COM Server or a COM Component.
These COM .DLLs can be created by Net Express also by using the Class and Method Wizards or wrapping your standard .DLL in a COM Wrapper using the Interface Mapping Toolkit utility program of Net Express.
COM, like DLL is a technology which is proprietery to Windows.
COM programs must adhere to a strict set of rules and guidelines that are defined by Microsoft.
For instance, all COM Servers must conform to a number of call interfaces that are defined by COM such as IDispatch or IUnknown.
These Components must also either contain or reference a COM Type Library which is a compiled definition of all the properties, methods and parameters that can be accessed by a calling client program.
It is the Type Library of a COM Server which is registered using the tool REGSVR32.EXE and placed into a known location within the System's registry file.
Whenever a COM client program created with COBOL or VB wishes to call a COM Server they must first obtain a reference to the COM Servers Type Library and then they can use the returned information to create an object instance of the COM Server and access its properties and methods according to the information in the Type Library.
You cannot register a standard Windows Dynamic Link library with the OS so that a calling program can find out the location of the DLL.
The Net Express product does look for its run-time files using this technique when you link your COBOL programs as Shared Dynamic, but the only reason that Net Express can do this is that the product is registered when it is installed so it is aware of the key names and values that are placed into the Windows Registry under the Net Express product key.
This is something that you would have to create yourself in the installation procedure for your own software package.
The standard method of searching for a .DLL file when a program needs to load it is to search the current directory, then the calling programs directory and then search the contents of each directory that is specified in the systems PATH setting.
For more information on different methods of compiling and linking your COBOL programs, please look in the Net Express on-line Help and search under the keywords "cbllink" and which is the utility which compiles and links COBOL programs in Net Express and "call convention". You will find a lot of good information here about dynamic and static linking and also some good info about executables, .DLLs and how they work.


