Forum: PC-Programmierung FTDI, DLL, C#, stack imbalance, cdecl und stdcall, DotNet 3.5 und 4.0


von Edgar C. (ec-develo)


Lesenswert?

Hallo zusammen,

ihr kennt doch bestimmt die Szene aus Malcom mittendrin, wo der Vater 
die Glühbirne auswechseln soll und zum Schlus das Auto repariert...?

watch?v=epMzvVGI_s4

Eigentlich hänge ich ja gerade mit vier Füßen in der 
FPGA-Programmierung, aber dabei brauche ich unter anderem einen 
JTAG-Anschluss... also den FTDI-Chip benutzen, der eh daran hängt... und 
dazu ein bisschen C# programmieren...

Tage später hänge ich nun an dieser Baustelle:

Das Code-Beispiel 
http://www.ftdichip.com/Support/SoftwareExamples/MPSSE/FTCJTAG/CSharp/FT2232CJTAGCsharpTestApp_200.zip

definiert u.a.
1
[DllImport("ftcjtag.dll", EntryPoint = "JTAG_GetDllVersion", CallingConvention = CallingConvention.Cdecl)]
2
static extern uint GetDllVersion(byte[] pDllVersion, uint buufferSize);

Man beachte die Aufrufkonvention ".Cdecl" am Schluss der ersten Zeile

Ein Aufruf, kompiliert für DotNnet 3.5 funktioniert klaglos. Die Version 
der DLL wird fehlerfrei mit 2.0 zurückgeliefert.

Das selbe Stück Code für DotNet 4.0 kompiliert schmeißt beim Debuggen 
einen pinvokestackimbalance-error!

Tante Google meint dazu, dass
a) die Aufrufkonventionen meines Codes und die der DLL nicht 
übereinstimmen
b) DotNet 3.5 die Fehler bisher defaultmäßig berücksichtigt hat
c) DotNet 4.0 das defaultmäßig nicht mehr tut, jedoch dazu überredet 
werden kann

Nun möchte ich ja keinesfalls unsauber herumschlampen und habe deshalb 
weiter nachgeforscht.

In den Header Dateien von FTDI sind die ganzen Funktionen, die mittels 
DLL bereit gestellt werden (z.B. FTD2XX.H) als

FTD2XX_API
FT_STATUS WINAPI FT_GetDriverVersion(
    FT_HANDLE ftHandle,
  LPDWORD lpdwVersion
  );
deklariert.

Das "WINAPI" brachte mich auf die Idee, die Aufrufkonvention von .Cdecl 
auf .StdCall zu ändern und schon funktionierte alles klaglos für DotNet 
3.5 und auch 4.0.
1
[DllImport("ftcjtag.dll", EntryPoint = "JTAG_GetDllVersion", CallingConvention = CallingConvention.StdCall)]
2
static extern uint GetDllVersion(byte[] pDllVersion, uint buufferSize);

Tja, eigentlich könnte ich ja jetzt fröhlich weitercodieren. Aber als 
der Korinthenkacker, der ich nun mal bin, lässt mir das Ganze keine 
Ruhe.

Ich habe auch schon Versuche mit DumpBin.exe auf der DLL gemacht, um 
hinter die tatsächlich verwendete Aufrufkonvention zu kommen, aber auch 
das brachte mir nichts Aussagekräftiges.

Der Code, den ich oben zitiere, stammt samt und sonders von FTDI. 
Sollten die nicht wissen, wie ihre Aufrufkonventionen sind?

Ich hoffe nun auf das Urteil eines Gurus, der mir bestätigen kann, dass 
meine Änderung der Aufrufkonvention auf StdCall die richtige Lösung ist. 
;-)

Schönen Sonntag noch!

von ms (Gast)


Lesenswert?


von Bernd K. (prof7bit)


Lesenswert?

Edgar C. schrieb:
> Der Code, den ich oben zitiere, stammt samt und sonders von FTDI.
> Sollten die nicht wissen, wie ihre Aufrufkonventionen sind?

Was verbirgt sich denn hinter den Makros in dem Codeschnipsel, expandier 
die doch mal um zu sehen was da wirklich steht. Stdcall ist nicht 
unüblich unter Windows, um nicht zu sagen es ist der default.

von Bernd K. (prof7bit)


Lesenswert?

Edgar C. schrieb:
> Ich habe auch schon Versuche mit DumpBin.exe auf der DLL gemacht, um
> hinter die tatsächlich verwendete Aufrufkonvention zu kommen, aber auch
> das brachte mir nichts Aussagekräftiges.

Du musst es disassemblieren und schauen wie die Parameter übergeben 
werden und wie der Stack benutzt wird und von wem er aufgeräumt wird.

von ms (Gast)


Lesenswert?

Hallo,

FTDI have provided a managed .NET wrapper class for the FTD2XX DLL on 
the Windows platform.


[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate FT_STATUS tFT_GetDriverVersion(IntPtr ftHandle, ref 
UInt32 lpdwDriverVersion);



ms

von Stefan (Gast)


Lesenswert?


von Edgar C. (ec-develo)


Lesenswert?

Erst mal vielen Dank an alle!

Die Links, die ihr mir geschickt habt, waren meiner Browserhistorie der 
letzten beiden Tage bereits bekannt, trotzdem habe ich noch einmal genau 
nachgelesen.

So langsam komme ich zu der Überzeugung, dass StdCall der richtige 
Aufruf ist und FTDI sich bei ihrem Beispiel-Code schlicht vertan hat.

Letzte Klarheit würde wohl nur ein Blick in die Sourcen geben (die aber 
leider nicht verfügbar sind), da WINAPI und StdCall zwar meist, aber 
eben nicht zwangsläufig zusammen gehören. So verstehe ich es jedenfalls.

Oder eben der disassemblierte Code. Dabei habe ich leider gerade eine 
Bauchlandung erlitten. Hier ein Schnipsel:

1
    29:             ftStatus = JTAG_GetDllVersion(byteDllVersion, MAX_NUM_DLL_VERSION_CHARS);
2
01223938 8B 4D BC             mov         ecx,dword ptr [ebp-44h]  
3
0122393B BA 0A 00 00 00       mov         edx,0Ah  
4
01223940 E8 E3 F4 FF FF       call        01222E28  
5
01223945 89 85 60 FF FF FF    mov         dword ptr [ebp-0A0h],eax  
6
0122394B 8B 85 60 FF FF FF    mov         eax,dword ptr [ebp-0A0h]  
7
01223951 89 45 C0             mov         dword ptr [ebp-40h],eax  
8
    30:             DLLVersion = Encoding.UTF8.GetString(byteDllVersion).TrimEnd('\0');

Interessant wäre also, was sich hinter dem call 01222E28 verbirgt.

Tja, leider macht Visual Studio an dieser Stelle einfach kein 'Step 
Into', sondern nur ein 'Step Over'. Und wenn man zu der entsprechenden 
Zieladresse hochscrollt findet man Gibberish. Ich schätze mal, dass der 
Disassembler da aus dem Tritt kommt:
1
01222E25 C1 B2 6F B8 E8 08 8B sal         dword ptr [edx+8E8B86Fh],8Bh  
2
01222E2C 05 89 ED E9 DC       add         eax,0DCE9ED89h  
3
01222E31 12 00                adc         al,byte ptr [eax]  
4
01222E33 00 B8 18 09 8B 05    add         byte ptr [eax+58B0918h],bh  
5
01222E39 89 ED                mov         ebp,ebp


Ich denke, ich werde es jetzt langsam dabei bewenden lassen und nicht 
noch tiefer einsteigen, sonst gibt das dieses Jahr nichts mehr mit dem 
Wechsel der Glühbirne ;-)

Vielleicht nerve ich FTDIs Support ja noch mit der Frage nach dem 
richtigen Aufruf. Bisher waren die zwar immer langsam, aber doch 
hilfsbereit.

Euch noch einen schönen Rest-Ersten-Advent!

von Bernd K. (prof7bit)


Lesenswert?

Edgar C. schrieb:
> Letzte Klarheit würde wohl nur ein Blick in die Sourcen geben (die aber
> leider nicht verfügbar sind), da WINAPI und StdCall zwar meist, aber
> eben nicht zwangsläufig zusammen gehören.

Wozu brauchst Du deren Sourcen, Du hast doch bereits die Header von 
FTDI, da steht alles drin was der Compiler wissen müsste um korrekten 
Code für den Aufruf der Funktionen zu erzeugen. Du brauchst auch nicht 
rumzurätseln was WINAPI hier bedeutet, Du schaust stattdessen einfach 
nach wie dieses Makro und die anderen Makros expandieren würden, dann 
hast Du Gewissheit.

: Bearbeitet durch User
von Edgar C. (ec-develo)


Lesenswert?

Hallo Bernd,

ich glaube du hast mich jetzt in die richtige Richtung geschubst.

In die Header-Dateien hatte ich bisher nur per Texteditor reingeschaut, 
weil ich sie in meinem eigenen Programm ja gar nicht benutze. C# kennt 
ja eigentlich keine Header?

Nur zur Referenz hier mein eigener Code, den ich z.T. aus dem Beispiel 
von FTDI extrahiert habe. (Ich bitte um Nachsicht, falls ich da gegen 
irgendwelche Regeln der Kunst verstoßen sollte, denn ich stamme noch aus 
den Borland-Plain-C-Zeiten)

1
using System;
2
using System.Runtime.InteropServices;
3
using System.Text;
4
5
namespace ArtyRVTap
6
{
7
    partial class Program
8
    {
9
10
        private const uint FTC_SUCCESS = 0;
11
        private const uint MAX_NUM_DLL_VERSION_CHARS = 10;
12
13
14
        // [DllImport("ftcjtag.dll", CallingConvention = CallingConvention.Cdecl)]
15
        [DllImport("ftcjtag.dll", CallingConvention = CallingConvention.StdCall)]
16
        static extern uint JTAG_GetDllVersion(byte[] pDllVersion, uint bufferSize);
17
18
        static void Main(string[] args)
19
        {
20
21
            uint ftStatus = FTC_SUCCESS;
22
            byte[] byteDllVersion = new byte[MAX_NUM_DLL_VERSION_CHARS];
23
            string DLLVersion;
24
25
            Console.Write("*** Checking for 'ftcjtag.dll'... ");
26
27
            ftStatus = JTAG_GetDllVersion(byteDllVersion, MAX_NUM_DLL_VERSION_CHARS);
28
29
            DLLVersion = Encoding.UTF8.GetString(byteDllVersion).TrimEnd('\0');
30
            Console.WriteLine("Found version : " + DLLVersion);
31
32
        }
33
    }
34
}


ABER!!! Natürlich gibt es den Code für die JTAG-DLL von FTDI, sogar als 
vollständiges Projekt zum Herunterladen! (Im Gegensatz zu dem Quellcode 
für die D2XX.DLL, den ich die ganze Zeit im Kopf hatte)

Ich habe das Projekt also eingeladen und siehe da:

1
extern "C" __declspec(dllexport)
2
FTC_STATUS WINAPI JTAG_GetDllVersion(LPSTR lpDllVersionBuffer, DWORD dwBufferSize)
3
{
4
  return pFT2232hMpsseJtag->JTAG_GetDllVersion(lpDllVersionBuffer, dwBufferSize);
5
}

Und wenn man die Maus auf WINAPI setzt steht da:
#define WINAPI __stdcall

Damit dürfte das wohl geklärt sein.

Vielen Dank für den Schubser :-)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.