Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations SkipVought on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

.NETCOBOL and calls to unmanaged C DLLs 1

Status
Not open for further replies.

MHerbst

Programmer
Oct 1, 2003
3
0
0
DE
Hi!

I am doing my first steps with .NETCOBOL. Now I have the following problem:
I have to call an unmanaged C function from a managed NETCOBOL program. The function has got one parameter that is defined as void* resp. char*. I need to pass COBOL different types of groups (having of course different lengths) to this function, e.g.
01 TEST1
05 F1 PIC S9(9) COMP-5.
05 F2 PIC X(10).
01 TEST2
05 F1 PIC S(9) COMP-5.
05 F2 PIC S(9) COMP-5.
05 F3 PIC X(20).
...
I have made prototype definition with the first group definition and this worked fine. But I can't make a prototype definition for all possible group definitions that I want to pass to my C function. How can I write a prototype with the definition of a variable length parameter?
I have tried to use POINTER and some other dirty tricks but no success. Anyone her with a good idea or solution to my problem?

Martin
 
You can not write a Prototype definition for a variable length parameter. The parameters HAVE TO BE fixed length. If you have multiple paramter formats to pass you need to create a prototype entry for each of the different options, using different names for each prototype, (i.e. CALL FORMAT01 USING BY REFERENCE OPTION01, CALL FORMAT2 USING BY REFERENCE OPTION02...etc.)

Your PInvoke definition would then look something like:
IDENTIFICATION DIVISION.
PROGRAM-ID. CCBIDC IS PROTOTYPE CUSTOM-ATTRIBUTE PInvoke.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
SPECIAL-NAMES.
CUSTOM-ATTRIBUTE PInvoke
CLASS DllImport USING "dbnet.DLL"
PROPERTY PropEntryPoint IS "CCBID"
.
REPOSITORY.
CLASS DllImport AS
"System.Runtime.InteropServices.DllImportAttribute"
PROPERTY PropEntryPoint AS "EntryPoint"
.
DATA DIVISION.
LINKAGE SECTION.
01 BID-STATUS PIC XX.
01 BID-EDIT PIC X(26).
01 BID-LUNO PIC XX.
01 BID-FLAG PIC S9(04).
PROCEDURE DIVISION USING BY REFERENCE BID-STATUS, BID-DIT,
BID-LUNO, BID-FLAG.

END PROGRAM CCBIDC.
*********************************************

Notice the use of the PropEntryPoint? This is how I can create multiple PInvokes that reference the same DLL. Of course, your calls would also have to change to call the new prototype definition.

This is just one way to do this. There may be others, but without more information this is the first method that comes to mind.

Best Wishes!
 
That's what I have feared. But I was hoping to find some kind of "dirty trick" to bypass it.

Martin
 
There is actually a "dirty trick" to accomplish this, but it is not for the faint of heart. It comes with several caveats and I would only recommend it if you feel that you absolutely need it.

The caveats are:

1) You need a "helper" routine written in a different .NET language, such as C#, to help pin the data for the garbage collector and potentially marshal it.

2) This helper routine gets a lot more complicated if there are pointers embedded in the data structures you intend to pass, because COBOL pointers in .NET are not the same kind of pointers you are accustomed to.

3) The following description will use an undocumented function of the COBOL runtime that may not continue to exist in its current form in future versions of the runtime.

4) This solution will not do any of the automatic marshaling of PIC X data from Unicode UTF-8 encoding to ANSI Code Page encoding that would normally be done by the Fujitsu NetCOBOL runtime if you did not provide the SuppressEncodingConversionAttribute attribute. You can perform this conversion manually if you need to. If your PIC X data is all ASCII, then you don't have to worry about this.

The outline of the solution is as follows:

1) You need to create a COBOL pointer in your calling program that points to the data structure you want to pass. For example:

01 MYVARP USAGE POINTER.
...
SET MYVARP TO ADDRESS OF TEST1.

2) You need to convert this COBOL pointer into an unmanaged memory pointer (COBOL pointers in Fujitsu NetCOBOL for .NET are not real memory pointers) that is pinned for the garbage collector. Pinning is required to make sure that the data isn't relocated by the garbage collector during your call to the unmanaged function. To accomplish this, you first need to define an integer redefines of the pointer from (1) in the calling program:

01 MYVARP-INT REDEFINES MYVARP PIC S9(9) COMP-5.

Then you need your helper routine to take this integer and convert it into a pinned unmanaged memory pointer. The following is a C# code snippet that does this:

using Fujitsu.COBOL;
using Fujitsu.COBOL.Runtime.OmeLib;
using System.Runtime.InteropServices;

...

// parm is assumed to be the int parameter that is
// the integer redefines of the COBOL pointer passed
// from the COBOL program
COBOLData srcData = hPointerManager.hPointerToCOBOLData((uint)parm);
GCHandle handle = GCHandle.Alloc(srcData.Block, GCHandleType.Pinned);
int result = (int)handle.AddrOfPinnedObject() + srcData.Offset;
// result now contains the pinned unmanaged memory
// pointer that you can pass to the unmanaged C
// function.

3) You need to call your unmanaged routine with the integer result computed in step 2. This integer result is, in fact, an unmanaged memory pointer. This call can be done inline from the C# code or from your COBOL code using the returned integer result from your C# helper routine.

4) You need to free up the GCHandle that was allocated in step 2 so that you are no longer pinning the data that you used to call your unmanaged routine. Pinning data without unpinning it means that the performance of the garbage collector will gradually degrade. To free the GCHandle, you simply need to call the "Free" method on the handle that was created in step 2. If you called the unmanaged C function directly from the C#, you could free the GCHandle directly after the call. Otherwise, you need to return the handle from the helper routine so that the calling COBOL program can free the handle.

There are a lot of more complicated variations on this solution that are needed in the case of embedded pointers or in the case that you need to perform other custom marshaling, such as character encoding conversions. The more complicated variations involve allocating unmanaged memory using Marshal.AllocCoTaskMem and copying the data from the managed data structure to the unmanaged memory area while performing the necessary marshaling. Doing this inevitably means creating a C# routine with "unsafe" extensions to do the necessary copying.

After wading through this description, you may well decide that it's not worth it...

Good luck!
 
Kadhim is Basim Kadhim, Chief Architect, Languages
Fujitsu Software Corporation, and, of course, he knows much better then us the product...
Any way, thank to Basim for his kind and useful support.

Gianni Spano

 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top