When using ftlib some issues may arise, in this wiki I address some of them.
This wiki is intended for fischertechnik users that are comfortable with programming on a PC in Borland C++ builder, Visual C++ or Visual C#.
The ftlib package was provided to make it possible to control a RoboInterface (RI) from a PC with conventional programming languages (i.e. not RoboPro). The main use of the library is to use the RI in online mode and control the outputs from the PC and read the inputs from the RI into the PC. Other uses comprise: interfacing with e.g. RoboPro by means of messages and reading and writing RI memory to interface with a user program running on the RI.
The ftlib is available for download from the fischertechnik site, the current version is V1.70a. The download contains a.o. the library, documentation and examples. A wrapper encapsulating most of the ftlib is available on
http://www.ftcomputing.de.
The API as made available in the header files is written in C and the examples are in Visual C++.
The use with Visual C++ is relatively easy although the examples are quite startling. The ftlib API is pretty well documented and including the lib is as easy as including ftlib.h in your source files and adding any of the four static libraries to your project.
Things become more complicated when using the ftlib.dll as a library. There is an example that shows how to do this but that example uses only one source file that actually uses the ftlib. It is quite possible to confine all calls to the ftlib to a single source file but we do not always like this restriction. The restriction is caused by the fact that the header file ftlibdllfunc.h not only contains declarations but also definitions (this is considered bad practice and now it is haunting us). The header contains the following definitions:
The instance handle of the DLL;
Function pointers for each DLL entry;
LoadFtLibDll and UnloadFtLibDll functions.
The presence of these definitions makes that this include file can be included in the project precisely once. Inclusion in a further source file will make the linker complain about multiple definitions of a symbol. On the other hand, not including the header will make the compiler complain about undeclared symbols. A solution seems to be to include ftlibdllfunc.h in one source file and the regular ftlib.h in all other source files. Alas, this does not work either, the linker sees the definition in ftlibdllfunc.h and the declaration in ftlib.h as distinct. This is caused by the fact that the DLL entry points are defined as function pointers [type (*func)();] whereas the declarations are for functions [ type func();]. The difference is an extra level of indirection that often goes unnoticed because C accepts both (*funcptr)() and funcptr() to call a function indirectly. For the file including the ftlibdllfunc.h the compiler will generate indirect calls and for the file including ftlib.h the compiler will generate direct calls, the linker subsequently cannot handle this and issues error messages.
The solution to the problem is to adapt the ftlibdllfunc.h header in such a way that it includes the definitions in one module only (e.g. with a preprocessor symbol defined in precisely one source file) and contains the proper declarations only for every other source file.
//ftlibdllfunc.h
#ifdef LOAD
#define EXTERN
HINSTANCE hFtLibDLL;
#else
#define EXTERN extern
#endif
typedef DWORD (__stdcall *DLL_GetLibVersion) (void);
.
.
EXTERN DLL_GetLibVersion GetLibVersion;
.
.
#ifdef LOAD
DWORD LoadFtLibDll(void)
.
.
#endif
The effect is that symbols that we don’t need in other modules are not declared/defined and those that we do need are declared as extern. So this modified ftlibfuncdll.h can be included in every source file and only in the source file that loads the library shall be preceded by
#define LOAD
The other include file ftlibdll.h contains only declarations and must be included in every source file.
This problem is not specific to Visual C++, we encounter the same problem in Borland C++ Builder. In Builder we must use the DLL because we cannot use the statically linked library. The libraries provided by fischertechnik are in COFF format but Borland requires them to be in OMF format. The utility coff2omf does not do a proper conversion because it is intended for import libraries (libraries that just provide entrypoints to DLL functions). The static ftlib libraries are not of this kind.
We can however try to create an import library from the ftlib.dll with the Borland provided IMPLIB utility. This works fine but the functions have to be declared as extern “C”.
extern “C” {
#include “ftlib.h”
}
Of course the newly created import library has to be added to the project and the ftlib.dll has to be in the path or in the directory of the executable.
The .Net framework and C# are becoming increasingly popular. Using ftlib requires a different approach because the ftlib.h header file cannot be used. Linking the ftlib.dll is however very easy, an import library is not required nor are calls to LoadLibrary and GetProcAddress.
An ftlib function is simply declared as for example:
[DllImport("ftlib.dll")]
public static extern UInt32 GetLibVersion();
This declaration is placed inside the class where we want to use it. The main difference with the original declaration is the return type UInt32 which is the C# equivalent of DWORD.
With some simple substitutions we can call any function in ftlib.dll.
Ftlib C#
FT_HANDLE IntPtr
LPCSTRING string
DWORD UInt32
DWORD* out UInt32
USHORT* out UInt16
UCHAR byte
The trouble starts only when we pass or return pointers to structures. The most important structures are FT_TRANSFER_AREA, NOTIFICATION_EVENTS and SMESSAGE.
Each structure has its own peculiarities. FT_TRANSFER_AREA consists of relatively simple types that can easily be translated but it also has some arrays. C# arrays are objects and the array declaration in a struct will be translated into a reference to the actual array object on the heap. The way to circumvent this is to declare the struct as ‘unsafe’ which allows us to use ‘fixed’ arrays which follow syntax and semantics similar to C.
Furthermore the struct has to be declared with a pack size of 1 (which apparently is not the default).
[StructLayout(LayoutKind.Sequential, Size = 0x200, Pack = 1)]
public unsafe struct FT_TRANSFER_AREA //unsafe because of fixed buffers
{
public UInt32 E; //0
UInt64 rsvd1; //4
// more members
public fixed byte MPWM[32]; //50
// etc...
}
The SMESSAGE is actually a C union, this can be simulated in the following way:
[StructLayout(LayoutKind.Explicit, Size = 6, Pack = 1)]
public unsafe struct SMESSAGE
{
[FieldOffset(0)]
public byte ucHwId;
[FieldOffset(1)]
public byte ucSubId;
[FieldOffset(2)]
public UInt16 uiMsgId;
[FieldOffset(4)]
public UInt16 uiMsg;
[FieldOffset(0)]
public fixed byte aucMsg[6];
[FieldOffset(2)]
public UInt32 dw;
}
The FieldOffset attribute places each member at the appropriate place.
The NOTIFICATION_EVENTS structure is more complicated because it comprises mostly pointers. Two of these pointers are pointers to functions. The C# most natural equivalent is the delegate. Unfortunately structs (which are value types) are sometimes more restrictive than classes (which are object types). In this case I decided to use a class, the handles are substituted by IntPtr’s and the function pointers by delegates.
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void callback_t(IntPtr context);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void msgCallback_t(ref SMESSAGE msg);
[StructLayout(LayoutKind.Sequential)]
public class NOTIFICATION_EVENTS
{
// Callback-Procedure
public callback_t NotificationCallback; // Pointer to the Callback-Procedure
// public IntPtr Context; // Pointer to the Context for the Callback-Procedure
public object Context; // Pointer to the Context for the Callback-Procedure
// for SetEvent()
public System.IntPtr hEvent;
// for posting a Message (PostMessage() with wParam & lParam = 0)
public System.IntPtr WindowsHandle; // handle to the destination window
public UInt32 Message; // message ID
// Callback-Procedure for Messaging
public msgCallback_t CallbackMessage; // Pointer to the Callback-Procedure for Receiving Messages
}
This all looks pretty straight-forward but we have to be aware that we make calls from a managed environment (where memory is managed by a garbage collector(GC)) into an unmanaged environment (where we or ftlib are responsible for the memory management).
To complicate matters, ftlib stores pointers into managed memory and makes calls back into managed memory. The big issue here is that the GC may deallocate chunks of memory or move around pieces of memory without asking or even telling us. The danger is that we pass e.g. a pointer to a struct to ftlib and by the time ftlib decides to use that pointer the actual struct may have been moved. Within C# this is not a problem because the GC updates all references accordingly but the GC has no jurisdiction over unmanaged memory. The so called interop services help us a great deal however. Two special interface elements are important, thunks and GCHandles. These are objects that live in both worlds, they stay at a fixed address but contain references that are updated by the GC. Another solution is to pin certain managed objects to a fixed place in memory, which although possible is quite restrictive.
A thunk is implicitly generated and makes sure that ftlib can call the delegate that was passed to ftlib. We only have to make sure that the delegate (which is an object by itself) stays alive. This is most easily accomplished by declaring it static or in an object with the same lifetime as the connection to the interface device. For data pointers like the Context pointer we need to explicitly allocate a GCHandle (that is, if we need a Context pointer because we can do without). A GCHandle has methods which give a pointer that can be passed to ftlib and vice versa. It also has members which refer to the original managed object.
The callback delegates can elegantly be used in combination with events. Other objects simply subscribe to the event and they get called when an interface device sends an update. It is however still the case that these events are called in the context of the callback thread, so they must be brief and cannot easily interact with the GUI. For display purposes it is therefore much more convenient to use the windows handle and message. For the handle we best use the handle of the main window which can be obtained by:
Ne.WindowsHandle = Process.GetCurrentProcess().MainWindowHandle;
Ne.Message = WM_USER+2000;
WM_USER is a windows constant (equal to 1024) and the value of message must be above this. The messages can be caught in the main window (Form) of the application like this:
// Override WndProc to handle new messages
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_USER + 2000: //notification that the TA was updated by the ft interface
// handle your message
label4.Text = "Hello! " + ((Form2.localdata)(open_intf_form.gch.Target)).i.ToString();
break;
// handle other messages
default:
base.WndProc(ref m);
break;
}
}