dyLib API Call errors - how to fix?

Hi All Declare Experts,

Background: I am developing a dyLib and DLL to access an external library (not a plugin). Currently I am testing ‘libSwEpLib.dylib’ on the macOS 13.7.4. The code librrary is written in ‘C’. Xojo version 2024 Release 4.2.

Why I think ‘libSwEpLib.dylib’ is working:

Declare Function swe_get_library_path Lib kLibSWE ( path As CString ) As CString
path = swe_get_library_path(path)

This code correctly returns the internal path.

So far, so good. Now comes a much more complex call and the one I am having trouble with.

The ‘C’ API call into the 'swe_rise_trans’ that looks like this:

ext_def (int32) swe_rise_trans(double tjd_ut, int32 ipl, char *starname, int32 epheflag, int32 rsmi, double *geopos, double atpress, double attemp, double *tret, char *serr);

Here is the code I set up for a test with static values:

Var tjd_ut As Double
Var ipl As Int32
Var starname As String
Var epheflag As Int32
Var rsmi As Int32
Var geopos() As Double
Var datm() As Double
Var atpress As Double
Var attemp As Double
Var tset() As Double
Var serr As String
Var return_code As Int32

tjd_ut = 2435430.594444
ipl = 0
starname=""
epheflag = 258
rsmi=1        ' SE_CALC_RISE=1, SE_CALC_SET=2
//
geopos.Add(-75.16666666667)
geopos.Add(39.95)
geopos.Add(0)
//
datm.Add(1013.25)  // atmospheric pressure
datm.Add(15)  // atmospheric temperature
//
atpress = 0
attemp = 0
serr = ""
return_code = -3

Declare Function swe_rise_trans Lib kLibSWE ( tjd_ut As Double, ipl As Int32, starname As CString, epheflag As Int32,  rsmi As Int32,  ByRef geopos, datm[0] As Double, datm[1] As Double, ByRef tset, serr As CString ) As Int32

return_code = swe_rise_trans(tjd_ut, ipl, starname, epheflag,  rsmi,  geopos, datm(0), datm(1), tset, serr)

Return tset(1)

This generates this compile error:

Location:
SwissEphemeris.calc_rise_and_set Declaration

Issue:
Syntax error
Function calc_rise_and_set(tjd_ut As Double, ipl As Int32, starname As String, epheflag As Int32, rsmi As Int32, ByRef geopos, datm[0] As Double, datm[1] As Double, ByRef tset, serr As String) As Double

Can anyone tell me what I have mismapped or I am simply doing wrong?

I was able to fix a decakre error by the following:

But I have tried what I currently understnd but can’t fix the issue.

Any thoughts?

Thanks,
John…

Your geopos parameter lacks a type:

geopos as Double

Your tset parameter lacks a type:

tset as Double

Your datm parameters are invalid:

datm0 as Double
datm1 as Double

After these corrections, your declare line should look like this:

Declare Function swe_rise_trans Lib kLibSWE ( tjd_ut As Double, ipl As Int32, starname As CString, epheflag As Int32,  rsmi As Int32,   geopos as Double, datm0 As Double, datm1 As Double, ByRef tset as Double, serr As CString ) As Int32

You will then encounter the following error:

Window1.Opening, line 34
Expected (Double, Int32, CString, Int32, Int32, Double, Double, Double, Double, CString), but these arguments are (Double, Int32, String, Int32, Int32, Double(), Double, Double, Double(), String).
return_code = swe_rise_trans(tjd_ut, ipl, starname, epheflag,  rsmi,  geopos, datm(0), datm(1), tset, serr)

This is because you’re attempting to pass arrays to the geopos and tset parameters. You likely want to pass a structure, but there’s not enough information to really help beyond pointing out the issue.

1 Like

Hi Anthony,

Thanks for helping!

I thought I provided a type by assigning As Double?
Var geopos() As Double
Var tset() As Double

Both are arrays of doubles, no? This is what the ‘C’ call is expecting (well actually I guess it’s expecting a pointer to array)

but there’s not enough information to really help beyond pointing out the issue.

I’m coming from the 4th Dimension world and 4D plugins. So how a 4D plugin works and how a dyLib/DLL works is very different.

I thought that by initializing the ‘Vars’ with accurate values, that would help? The values I am sending in the arrays are initialized. So you can see which values are required. But the ‘C’ call does need arrays. That’s why I included the API call to help.

Otherwise my Declare and your declare look almost identical (exceptions: datm0, datm1 where I just pass the value at an array element).

And yes I conitnue to get the same error as I posted.

So…

Thanks,
John…

In Xojo, types are explicit, not inferred. You have to tell the compiler what type a variable or parameter is going to be. You weren’t doing that in the two places I pointed out.

Hi Anthony,

To confirm, please. Aren’t these explicit types:

Var rsmi As Int32
Var geopos() As Double
Var datm() As Double
Var atpress As Double
Var attemp As Double
Var tset() As Double

Or am I missing something?

Appreciate,
John…

I do not regularly use declares, but I’m 99% what Anthony is referring to the values shown in this screenshot.

Example:

“ByRef geopos” in the Parameters list shown in the screenshot doesn’t have a type listed.

HTH,
Anthony (a different Anthony :wink: )

1 Like

(Other) Anthony is more correct. You need to supply types for parameters of function definitions as well. Declares are a function definition. You neglected to declare the types of the parameters. Creating a local variable of the same name you plan to use for the Declare’s parameters does not absolve you of needing to provide a type for those parameters. They are separate entities.

Hi Anthony’s,

Thank you. I’m processing your feedback.

So, declare var types in the method Parameters screen.

Ok, I’ll give that a try.

Appreciate,
John…

No. Your Declare line’s definition was incomplete. I corrected it by adding the type definitions. All you needed to do was add those type definitions to the parameters in the way that I showed you in my first post.

That line:

Declare Function swe_rise_trans Lib kLibSWE ( tjd_ut As Double, ipl As Int32, starname As CString, epheflag As Int32,  rsmi As Int32,   geopos as Double, datm0 As Double, datm1 As Double, ByRef tset as Double, serr As CString ) As Int32

Is basically saying “This function exists in this library, with these parameters”. So, as with a function you define purely for use within Xojo, it needs valid parameter definitions. What you name these parameters does not matter, and you don’t need to declare these parameters elsewhere.

When you call the Declared function, you can pass in any variable with any name as long as its type matches those defined in the parameter list of the Declare line. So you could have a variable of type Double named numberOfTacos that’s defined on Line 1 of the method containing the Declare call that looks like this:

var numberOfTacos as Double = 2.1

And you can pass that to the Declare function as the first parameter:

var result as Integer = swe_rise_trans(numberOfTacos, <other matching parameters here> )

You should familiarize yourself more with declares:
https://documentation.xojo.com/api/language/declare.html
https://documentation.xojo.com/topics/declares/index.html

In addition to the above resources, you can also search the Xojo examples for “declare” by opening the Xojo “New Project” window, selecting “Examples” on the left, then entering your search term in the search field at top of the center pane.

1 Like

Hi Anthony,

Let me apologize since my command of the ‘C’ language is rusty at best. Haven’t had to use it for 10 years. Now…

I did copy the line and it generated the same error. The error is likely due to my ignorance of how to pass in strings and arrays into a ‘C’ dyLib.

I decided to work on a simpler method call with only 2 parameters to see what would happen.

This call sends in an int and string.

Is how this ‘C’ is implemented in API:
#define AS_MAXCH 256 /* used for string declarations, allowing 255 char+\0 */

so strings seem to be predeclared as 256 before use.

This is the signature:

char *CALL_CONV swe_get_planet_name(int ipl, char *s)
int32 ipl
char s[AS_MAXCH];

In Xojo:

Var err, s As CString 
Var ipl As int32

s = initString( 256) // I tried an empty string same result as below.
ipl = 0

Declare Function swe_get_planet_name Lib kLibSWE ( ipl As Integer, byRef s As CString )  As CString

s = swe_get_planet_name(ipl, s)

Return s

The end result is I am getting garbage in the ‘s’ var. If I remove ‘byRef’ from ‘byRef s As CString’ it crashes Xojo.

I’ve read the Declare docs from Xojo but they don’t seem to apply. Or my ignorance is getting in the way of seeing how?

There must be rules for when an array is sent that will return values, for example. Or when the routine expects a pointer to a char, etc.

There must be obvious rules to follow?

OK, enough for now.

Appreciate,
John…

Hi Anthony (G),

I did review those pages. However, they seemed to be more applicable when you are trying to access OS libraries. In my case, it’s an external library that has nothing to do with the OS.

The Declare example projects follow along similarly.

So I’m left to trial and error. Plus tips from the more experienced in the XoJo community.

Hope that makes sense.

Appreciate,
JOhn…

It’s the same concepts and principles, just calling different libraries with different functions.

Hi Anthony (G),

I trust you’re right.

In this case of the Declare docs/examples (only two desktop), the parameters issue was simple. In fact, there was a call to get the PID without any parameter passing. Using a Soft Declare, I think.

Most of the API calls I am making will require many parameters. There’ a lot to understand, especially with passing arrays and strings.

Hope that makes sense,
JOhn…

For each of the parameters in C you need a Xojo equivalent in the declare statement. So:

becomes:

Declare function swe_rise_trans lib kLibSWE (tjd_ut as Double, ipl as Int32, starname as CString, epheflag as Int32, rsmi as Int32, geopos() as Double, atpress as Double, attemp as Double, ByRef tret as Double, serr as CString) as Int32

The names of the parameters in the Declare statement do not need to match the data you intend to pass, but rather the pattern of the parameters in the dll call. In my example I’ve used the C names for clarity.
How it gets passed to the dll is down to the declare syntax
What gets passed is down to the function call.

Char * is a CString, that is a pointer to a string that is terminated with a \h00 value.

When passing an array put the parameter name() after to indicate that you must pass an array

Anything that is passed in and expected to be returned changed should be ByRef before it.

Every parameter is of the form ‘name as type’, for arrays it is ‘name() as type’. if it was a 2 dimensional array ‘name(,) as type’

Xojo never uses name[0] for array elements, it is always name(1). The declare doesn’t care where the parameters come from, so when you are passing datm(0) and datm(1) the declare just needs to know that you are passing two doubles.

The call would then be:

Var tjd_ut As Double
Var ipl As Int32
Var starname As String
Var epheflag As Int32
Var rsmi As Int32
Var geopos() As Double
Var datm() As Double
Var atpress As Double
Var attemp As Double
Var tset() As Double // This will need a size before if can be used. your other arrays are added to later this isn't. It should be "tset(1) as Double" as a minimum as you are returning element 1 from this function
Var serr As String
Var return_code As Int32

tjd_ut = 2435430.594444
ipl = 0
starname=""
epheflag = 258
rsmi=1        ' SE_CALC_RISE=1, SE_CALC_SET=2
//
geopos.Add(-75.16666666667)
geopos.Add(39.95)
geopos.Add(0)
//
datm.Add(1013.25)  // atmospheric pressure
datm.Add(15)  // atmospheric temperature
//
atpress = 0
attemp = 0
serr = ""
return_code = -3

Declare Function swe_rise_trans lib kLibSWE (tjd_ut as Double, ipl as Int32, starname as CString, epheflag as Int32, rsmi as Int32, geopos() as Double, atpress as Double, attemp as Double, ByRef tret as Double, serr as CString) as Int32

return_code = swe_rise_trans(tjd_ut, ipl, starname, epheflag,  rsmi,  geopos, datm(0), datm(1), tset, serr)
// datm(0) is passed to atpress and datm(1) to attemp, but they could just as easily be to separate doubles.

Return tret(1)

Hi Ian,

Thanks for the explanation.

I’ll give it a try.

Appreciate,
John…

No problem. Just one more tip. You can combine the initial value with the Var statement is you wish:

Var tjd_ut As Double
tjd_ut = 2435430.594444

can be:

Var tjd_ut As Double = 2435430.594444

Hi Ian,

Copied the code exactly and tried to run. There are a series of errors.

Error one:

SwissEphemeris.calc_rise_and_set, line 1
You can't pass an array to an external function
Declare function swe_rise_trans lib kLibSWE (tjd_ut as Double, ipl as Int32, starname as CString, epheflag as Int32, rsmi as Int32, geopos() as Double, atpress as Double, attemp as Double, ByRef tret as Double, serr as CString) as Int32

Error two:

SwissEphemeris.calc_rise_and_set, line 35
Parameter "tret" expects type Double, but this is type Double().
return_code = swe_rise_trans(tjd_ut, ipl, starname, epheflag,  rsmi,  geopos, datm(0), datm(1), tset, serr)

Error three:

SwissEphemeris.calc_rise_and_set, line 38
This item does not exist
Return tret(1)

Thoughts?

Thanks,
John…

As I said on the Var tset line you need to size this array, you’ve not added to it and not given it a size in the var statement.

Try

Var tset(1) as Double

Declare would have to be adjusted to pass the array also:

Declare function swe_rise_trans lib kLibSWE (tjd_ut as Double, ipl as Int32, starname as CString, epheflag as Int32, rsmi as Int32, geopos() as Double, atpress as Double, attemp as Double, ByRef tret() as Double, serr as CString) as Int32

However, it would seem you can’t pass an array into a dll. To get around this issue you’re going to have to pass a MemoryBlock in, which complicates things slightly.

Basic principal is to create a memory block and copy the array into it. Pass that to the dll and then copy back the values out of the memory block.

Var mGeopos as New MemoryBlock( 24 ) ' Double is 8 bytes 3 doubles are 3 * 8 = 24.
mGeopos.DoubleValue( 0 ) = Geopos( 0 )
mGeopos.DoubleValue( 8 ) = Geopos( 1 )
mGeopos.DoubleValue( 16 ) = Geopos( 2 )

Var mTret as New MemoryBlock( 16 ) ' assuming two doubles
mTret.DoubleValue( 0 ) = 0
mTret.DoubleValue( 8 ) = 0

Declare function swe_rise_trans lib kLibSWE (tjd_ut as Double, ipl as Int32, starname as CString, epheflag as Int32, rsmi as Int32, geopos as MemoryBlock, atpress as Double, attemp as Double, ByRef tret as MemoryBlock, serr as CString) as Int32

// Call the function, passing the datm(0) and datm(1) is fine as you arent passing an array, just the elements.
return_code = swe_rise_trans(tjd_ut, ipl, starname, epheflag,  rsmi,  mGeopos, datm(0), datm(1), MTset, serr)

// Get the values back
mTret( 0 ) = MemoryBlock.DoubleValue( 0 )
mTret( 1 ) = MemoryBlock.DoubleValue( 8 )

A memory block is just a chunk of memory, which is also true of a C array.
https://documentation.xojo.com/api/language/memoryblock.html

Does that work?

1 Like

Hi Ian,

The ‘Var tset() As Double’ is an array that returns two double values from the call: sunrise and sunset.

I experimented yesterday with MemoryBlock and that seems to be the way that works.

Here’s what ran:

Var geopos As New MemoryBlock(3 * 8)  ' 3 doubles (longitude, latitude, altitude)
geopos.DoubleValue(0) = -75.16666666667
geopos.DoubleValue(8) = 39.95
geopos.DoubleValue(16) = 0

Var tret As New MemoryBlock(2 * 8)  ' Allocate space for two returned double values
Var serr As New MemoryBlock(256)  ' Allocate 256 bytes for error message

Var tjd_ut As Double = 2435430.594444
Var ipl As Int32 =0
Var epheflag As Int32 = 258
Var rsmi As Int32 = 1
Var atpress As Double = 1013.25
Var attemp As Double = 15
Var starname As CString = ""  ' Empty unless you're passing a star name

Declare Function swe_rise_trans Lib kLibSWE (tjd_ut As Double, ipl As Int32, starname As CString, epheflag As Int32, rsmi As Int32, geopos As Ptr, atpress As Double, attemp As Double, tret As Ptr, serr As Ptr) As Int32

Var return_code As Int32
return_code = swe_rise_trans(tjd_ut, ipl, starname, epheflag, rsmi, geopos, atpress, attemp, tret, serr)

Var riseTime As Double = tret.DoubleValue(0)  ' First returned time
Var setTime As Double = tret.DoubleValue(8)   ' Second returned time

Return setTime  ' Return the second value (tset)

Problem:
riseTime seems to return a correct value but setTime seems to always be zero. Since I can’t debug the dyLib I am not sure how else to inspect the returned value.

Ideas?

Appreciate,
John…