@SparkOut: I perhaps should have added some comments to the above
In terms of the folder structure, then the extension builder requires each extension have its own folder (velleman) in this instance. Then (in dp-11) it requires that a *single* LCB file be present in that folder (velleman.lcb) [ in the next build we've extended this so you can have multiple LCB files, but they have to be related - more on this when when the next build comes out! ]. Then, as we are wanting to include native code blobs (the velleman dll in this case) we need a code folder. As native code blobs are platform/arch specific, you then need a folder for each of those combinations which you want to support. In this case the DLL is very much Windows only, so you only need a 'code/x86-win32' folder - in here you place the DLLs for x86-win32. The 'leaf' names of the DLLs in that folder is what you use in the binding string (K8055D in this case, as the DLL is called K8055D.DLL).
When you bind to C handlers, you need to tell LCB what the 'signature' of the C handler is, as defined in C. The signature of a handler consists of the types of the parameters and the return value type.
In the DLL zip, there is a header file for BCB - which has the needed information in it:
Code: Select all
FUNCTION int __stdcall OpenDevice(int CardAddress);
FUNCTION void __stdcall CloseDevice();
FUNCTION int __stdcall ReadAnalogChannel(int Channel);
FUNCTION void __stdcall ReadAllAnalog(int *Data1, int *Data2);
FUNCTION void __stdcall OutputAnalogChannel(int Channel, int Data);
FUNCTION void __stdcall OutputAllAnalog(int Data1, int Data2);
FUNCTION void __stdcall ClearAnalogChannel(int Channel);
FUNCTION void __stdcall ClearAllAnalog();
FUNCTION void __stdcall SetAnalogChannel(int Channel);
FUNCTION void __stdcall SetAllAnalog();
FUNCTION void __stdcall WriteAllDigital(int Data);
FUNCTION void __stdcall ClearDigitalChannel(int Channel);
FUNCTION void __stdcall ClearAllDigital();
FUNCTION void __stdcall SetDigitalChannel(int Channel);
FUNCTION void __stdcall SetAllDigital();
FUNCTION bool __stdcall ReadDigitalChannel(int Channel);
FUNCTION int __stdcall ReadAllDigital();
FUNCTION int __stdcall ReadCounter(int CounterNr);
FUNCTION void __stdcall ResetCounter(int CounterNr);
FUNCTION void __stdcall SetCounterDebounceTime(int CounterNr, int DebounceTime);
FUNCTION int __stdcall Version();
FUNCTION int __stdcall SearchDevices();
FUNCTION int __stdcall SetCurrentDevice(int CardAddress);
FUNCTION int __stdcall ReadBackDigitalOut();
FUNCTION void __stdcall ReadBackAnalogOut(int *Buffer);
Each of these lines defines the signature of the C handler, so we have to turn them into LCB foreign handler definitions.
The first bit to notice is that all of them have __stdcall before the name. For various reasons of history, Windows has many different ways a C function can be called - typically you'll find two though __stdcall and __cdecl. The latter is the default for C applications, and the default for LCB, the former (__stdcall) convention is the one which is generally used for DLLs (particularly Win APIs) - however it needs to be explicitly stated.
In LCB, the calling convention is specified by appending !<convention> to the binding string - this is why all the bindings in this case have !stdcall at the end.
After establishing that, the next thing to do is to translate the signature as written in C, to LCB. If we take the example you mention - ReadAllDigital - then you can see in the above it is declared in C as:
Code: Select all
FUNCTION int __stdcall ReadAllDigital();
So, the function returns 'int', and take not arguments. This translates to an LCB handler decl as:
Code: Select all
private __safe foreign handler _ReadAllDigital() returns CInt binds to "c:K8055D>ReadAllDigital!stdcall"
So the reason this handler looks different from ReadAllAnalog, is because the C functions are quite different.
The rest of the functions are all pretty much the same - they either return void, int or bool, and take no, 1 or two int arguments. The type to use in LCB for 'bool' in C, is 'CBool' (and as we have seen 'nothing' maps to void).
The final bit of 'difference' in the API is the use of 'int *' in ReadBackAnalogOut and ReadAllAnalog. This is a little more subtle, as C APIs declared as above don't actually specify *what* this means - this requires sifting through the docs generally.
The case of ReadAllAnalog is as specified above - it reads the two analog channels the device has, and returns the values into the two passed parameters (out basically means that the var passed to it is passed 'by reference' - for foreign handlers this is done in a way so that the foreign handler sees a reference to the foreign type which is declared).
The case of ReadBackAnalogOut would, at first glance, appear to require something like:
Code: Select all
private __safe foreign handler _ReadBackAnalogOut(out rBuffer as CInt) returns nothing binds to "c:K8055D>ReadBackAnalogOut!stdcall" -- THIS IS NOT CORRECT!
i.e. Following the same pattern as ReadAllAnalog - you might assume that rBuffer is a CInt out parameter. However, this is not quite the case, checking the docs (and the sample code) - the buffer parameter actually needs to be a pointer to an array of 2 CInts (i.e. int[2]). This is considered an 'aggregate' type in LCB, which we need to define:
Code: Select all
private foreign type CIntArray2 binds to "MCAggregateTypeInfo:EE"
This defines a 'foreign' type which consists of two (C) int values - each char after the : specifies the type of a field in the aggregate. The full list of codes can be found in the LCB language reference guide (
https://github.com/livecode/livecode/bl ... ference.md).
WIth this type defined, we can now define the foreign handler type in a safe way:
Code: Select all
private __safe foreign handler _ReadBackAnalogOut(our rBuffer as CIntArray2) returns nothing binds to "c:K8055D>ReadBackAnalogOut!stdcall"
With this definition we can then define a suitable handler which we can call from LCS:
Code: Select all
public handler valReadBackAnalogOut() returns String
variable tBuffer as CIntArray2
_ReadBackAnalogOut(tBuffer)
/* We have the buffer as a foreign aggregate, but we must convert to a List to access */
variable tIntList as List
put tBuffer into tIntList
return tIntList[1] formatted as string & "," tIntList[2] formatted as string
end handler
This also illustrates how to access an aggregate - a foreign aggregate type in LCB 'looks like' a List - 1 element for each code in the aggregate definition. So tBuffer[1] gives us the first CInt, and tBuffer[2] gives us the second CInt.
Hope this helps - I think the rest of the calls in the DLL are all simple variants of the above. The only 'tricky' ones are the ReadBackAnalogOut and ReadAllAnalog - but I think those should work with the definitions above (obviously I can't actually *test* this, as the DLL won't init for me, not having the board!).
Let me know how you get on though!
P.S. The above has been edited several times - as I said I can't really test this - so it might not quite work, but hopefully it is almost correct and we can find any problems with it easily enough.