Seite 1 von 1
ftMscLib.dll Problem bei ftxStartTransferArea
Verfasst: 01 Feb 2014, 12:36
von Hauke
Guten Tag,
ich Programmiere im Moment einen etwas größeren Roboter. Diesen möchte ich in C# programmieren.
Ich habe die neuste ftMscLib.dll aus dem aktuellen Downloadarchiv von fischertechnik genommen.
Die Funktionen der dll spreche ich über DllImport an. - Das funktioniert soweit auch sehr gut.
Mein Problem ist aber, dass das Programm immer abstürzt. Das geschieht immer dann, wenn die TransferArea über ftxStartTransferArea gestartet wurde und eine Funktion für den online Betrieb ausgeführt wird.
Das heißt:
ftxStartTransferArea(..);
SetFtMotorConfig(...); <- an dieser Stelle stürzt das Programm ohne Fehlermeldung ab.
Jedoch passiert das nicht immer an dieser Stelle! Manchmal wird die Funktion ausgeführt, manchmal nicht. Das macht es mir sehr schwer, das Problem zu finden.
Kennt sich jemand etwas tiefer mit der ftMscLib.dll aus,und kann mir vielleicht Tipps geben?
Hierbei geht es um ein Projekt, beidem mich fischertechnik als Sponsor unterstützt. Dafür noch einmal vielen Dank!
Re: ftMscLib.dll Problem bei ftxStartTransferArea
Verfasst: 01 Feb 2014, 14:56
von vleeuwen
The FtTxservice for MS_Robotics developer studio has been based on the FtMscLib,
see also
http://www.fischertechnik.de/home/downl ... d-122.aspx and
http://www.fischertechnik.de/home/downl ... d-120.aspx.
The C-API's without callback are working fine at C# level, see the examples in the library kit
ftxStartTransferArea(..); SetFtMotorConfig(...);
However all the C api's with callback's are not easy to transform into managed code.
InteropServices does not cover it is this case very well.
For this you need to develop a normal C to C++ managed code wrapper, which is transforming the callbacks into event and delegates.
The next problem is that the FtMscLib does not support C++ class events.
Fischertechnik cover this problem already with the
FtMscLibEx (the extended version of the FtMscLib with events and C++ friendly call backs).
For the moment I am busy with developing a more native .NET version of the FtMscLib.
Suggestion: contact fischertechnik about your problem.
Re: ftMscLib.dll Problem bei ftxStartTransferArea
Verfasst: 01 Feb 2014, 15:25
von Ad2
I once tried to use ftlib.dll from C#.
There were some solvable problems.
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;
}
}
Hope this helps you