9.1D is, of course, ancient, obsolete and unsupported but even so it does support calling C routines as DLLs. For some detailed examples you might find this presentation on UNIX Shared Libraries helpful. (The gory details vary but the same ideas work for Windows if that's your environment.)
Simple example of the 4GL side:
define variable x as integer no-undo.
define variable c as memptr no-undo.
define variable m as memptr no-undo.
procedure sprintf external "/lib64/libc.so.6":
define input-output parameter fStr as memptr.
define input parameter mask as memptr.
define input parameter arg as double.
define return parameter x as long.
end.
set-size( c ) = 1024.
set-size( m ) = 1024.
put-string( m, 1 ) = "%1.4e".
run sprintf( input-output c, m, 0.0123, output x ).
display get-string( c, 1 ) format “x(20)”.
return.
The main issue for you, as the C programmer, is providing an API and data structure that the 4GL guys will be able to work with easily.
The simplest method is to just do plain old simple parameters (like above). Possibly one record at a time (depending on what your function is doing that may, or may not make sense -- if you need a set of records to work with then you would need to collect them and manage them somehow).
If it is really necessary to create structs and build them with memptrs you're going to have to work closely with the 4GL guys to define an API and agree on what the memptr should look like. I think that is likely to be a lot of work -- I'd try to avoid it and find a way to go with simple parameters if I could.