Warning: this is once again a post of the type: What Joachim just found out. Maybe there is nothing new for you to learn in it. But maybe there is and it saves you the time I just invested in finding it.
So here’s the problem:
I have a DLL call that is defined like this:
struct* myCall ();
And I wanted to call this function from VA Smalltalk using futureCallWith: . The nice thing about futureCallWith: is that it does call the library asynchronously and doesn’t block the whole VM. But this is a wide area that is best covered by someone who knows much better than me. If you need more info on that topic, I suggest watching Marten’s mini-series of videos covering external calls on Smalltalk Inspect.
So the first thing you need to do is define a PlatformFunction Instance that describes the call, so that you can call out to the world.
Here’s what I did to define the PlatformFunction:
PlatformFunction callingConvention: 'c' function: 'MyExternalCall' library: 'myDll' parameterTypes: #(void) returnType: #pointer
And this is where the little journey started. No matter what I tried, I always got strange errors when calling the function.
Calling the function using
myFunc futureCallWith: #()
failed with ACOPRIMITIVEFAILED (1007). So I tried lots of variants, like passin nil, an Array with nil, sending futureCall without paramaters and maybe even some more stuff.
Then I tried simple synchronous calls.
The variants where the same as above, the error messages were PrimitiveFailed’s due to invalid parameter at index 1. What??? Which parameter??? I tried to tell the damn thing that there is no parameter for issuing the simplest possible DLL call you could ever imagine and this ….ing thing would tell me about problems with my first parameter?
So maybe there was a problem with how I had defined the function rather than in the way I called it. So back to the PlatformFunction definition. Being a programmer, I was now close to my waterloo: I had to admit it might be time to look at the documentation (oh gosh, how ashamed I am to write about this…). And here is what it says:
The supported parameter types for the C language are:
- none, void
this is not a valid parameter type. If used as the return type, the value nil is returned.
So the first and most important thing is clear: it’s definitely my fault, because the definition was wrong.
I tried to find out how to define a function without parameters in the documentation, but couldn’t (I knew it, I knew it, reading docs is not for real programmers 😉 ). So I browsed all references to PlatformFunction and found one that looked like this:
PlatformFunction callingConvention: 'c' function: 'MyExternalCall' library: 'myDll' parameterTypes: #() returnType: #pointer
And what can I say? The function now works like a charm.
So here is what you can learn from this post:
If you want to call an external function with no parameters, no matter if synchronously or asynchronously, use #() as its parameter type. Do not use #(void), it is only for return types.
Being a programmer, I cannot stop here, because I have to prove it’s not only my fault 😉
So here is a little thing that I also found on my journey: the class PlatformFunction does have a class Variable named InvalidParameterTypes which holds the values ‘void’ and ‘none’. And this variable is also used to signal an error when you define a PlatformFunction with one of these two parameter types. But only if you use the method PlatformFunction>>#parameterTypeAt:put:, which, interestingly, has no senders in my image.
So I think I found a bug in VA Smalltalk that leads to the fact that a very helpful check for valid parameter types is not used in PlatformFunction class>>#callingConvention:function:library:parameterTypes:returnType:.
I’ll report it to Instantiations so that this can be improved for the next releases. It would have saved me a little time if VA just told me the definition is wrong