externe Funktionsdeklaration in MASM



  • Einen wunderschönen guten Tag,

    ich habe ein Problem^^. Und zwar muss ich mich - notgedrungen - in MASM einarbeiten. Ich habe dort (beispielhaft) folgende Art der Deklaration von externen Funktionen gefunden

    EXTERN _Proc1:PROC
    EXTERN _Proc2:BYTE
    EXTERN _Proc3:DWORD
    

    So, jetzt die große Frage:
    Was soll denn bitte schön ":BYTE" oder ":DWORD" heißen? Sind das die Rückgabetypen oder was?

    Es wäre wirklich super, wenn mir jemand diese Frage beantworten könnte. Da ich allerdings noch mehr Dinge hätte, wäre ein Buchtipp (explizit für MASM) nicht schlecht. Ich habe von "Power Programming with Microsoft Macro Assembler" von Microsoft Press gehört, kann das Buch aber weder bei Amazon, Libri, noch bei Microsoft Press selbst finden ...
    Hat da vielleicht jemand Rat für mich?



  • _Proc1 ist als externe Prozedur deklariert, _Proc2 und _Proc3 als Variablen vom type BYTE und DWORD (widersinnig zum Namen). An sich sollte man die flexiblere Anweisung EXTERNDEF für Variablen und PROTO für Funktionen benutzen. (EXTERN setz voraus, das die Variable/Prozedur auf jeden Fall in irgendeinem der verlinkten Module mit PUBLIC deklariert werden).

    Zum Thema Bücher: google mal nach “ MASM Programmer's Guid“.
    Wenn du weitere Fragen hast, kann ich dir nur das masmforum empfehlen (hat auch eine Suchfunktion ;)): http://www.masm32.com/board/index.php



  • x86-64 schrieb:

    _Proc1 ist als externe Prozedur deklariert, _Proc2 und _Proc3 als Variablen vom type BYTE und DWORD (widersinnig zum Namen).

    _Proc1 war mir auch klar. Aber die Sachen die in meinem Code zum Teil als "BYTE" oder "DWORD" markiert waren, sind definitiv auch Funktionen.

    Inwiefern ist denn "PROTO" flexibler als "EXT(E)RN"? Ich will ihm doch nur mitteilen, dass ich da eine Funktion habe, die in einem anderen Modul definiert worden ist und die ich per Linker später einbinden werde.

    EDIT:
    Ich hab jetzt mal was versucht und bekomm allerdings den Compiler-Error "syntax error : PROTO" ...

    .686P
    .XMM
    .model FLAT, PASCAL
    
    .code
    PROTO  @@DynArraySetLength:NEAR
    [...]
    


  • FrEEzE2046 schrieb:

    Inwiefern ist denn "PROTO" flexibler als "EXT(E)RN"? Ich will ihm doch nur mitteilen, dass ich da eine Funktion habe, die in einem anderen Modul definiert worden ist und die ich per Linker später einbinden werde.

    Mit PROTO/EXTERNDEF kann man in allen Modulen benutzen, egal ob die Funktion im gleichen Modul deklariert wird oder nicht -> man kann z.B. die gleiche include-file für alle Module verwenden - das würde mit EXTERN/PUBLIC nicht gehen. In deinem fall macht das aber kein unterschied.

    FrEEzE2046 schrieb:

    Aber die Sachen die in meinem Code zum Teil als "BYTE" oder "DWORD" markiert waren, sind definitiv auch Funktionen.

    Wie werden die aufgerufen? : call PROC ptr _Proc2

    PROTO benutzt man so:

    FunktionsName PROTO [C,STDCALL,..] [VarName]:TYPE,...
    

    (Eckige Klammern = optional)

    PS: near brauchst du nicht - wir bewegen uns immer im gleichen Segment 😉



  • Sry, aber was meinst du mit "Funktionsname" und dann nochmal "Var Name"?
    Hab's jetzt so gemacht:

    @@DynArraySetLength PROTO [PASCAL]
    

    Jetzt kommt: "Syntax error : [". Ohne die geht's aber.



  • sry, war etwas verwirrend:
    Am Beispiel MessageBox (WINAPI)

    MessageBoxA PROTO STDCALL hWnd:DWORD,lpText:DWORD,lpTitel:DWORD,dwFlag:DWORD

    mit proto kann man also auch die Funktionsparameter angeben -> dies braucht man wenn man den Äußerst konvertiblen invoke-Macro benutzen will. Hier mal ein Beispiel mit mehreren Möglichkeiten einen Funktionsprototypen an zu geben:

    .686p                
    .model flat, stdcall 	; stdcall als default-calling convention vereinbart
    option casemap :none 	; groß/klein Schreibung unterscheiden
    .mmx
    .xmm
    
    includelib User32.Lib 	; findet man z.B. im Windows SDK oder, im MASM32-package (absolutes Muss für masm :))
    
    ; 1 
    MessageBoxA PROTO STDCALL hWnd:DWORD,lpText:DWORd,lpTitel:DWORD,dwFlags:DWORD
    
    ; 2
    ;MessageBoxA PROTO :DWORD,:DWORD,:DWORD,:DWORD
    
    ; 3
    ;EXTERNDEF MessageBoxA@16:PROC
    
    ; 4
    ;PR4 typedef PROTO :DWORD,:DWORD,:DWORD,:DWORD
    ;EXTERNDEF MessageBoxA:PR4
    
    .data
    	t1 db "hallo Welt",0
    	t2 db "bla",0	
    .code
    start:
    
    	; für 1,2 und 4
    	invoke MessageBoxA,0,OFFSET t1,OFFSET t2,0
    
    	; nur für 3
    	;push 0
    	;push OFFSET t2
    	;push OFFSET t1
    	;push 0
    	;call MessageBoxA@16
    
    	ret
    end start
    

    Zeig doch mal deinen Programmcode - das macht es vieleicht einfacher dir zu helfen ...



  • x86-64 schrieb:

    Zeig doch mal deinen Programmcode - das macht es vieleicht einfacher dir zu helfen ...

    Das würde ich ja machen; bringt aber nur dann was, wenn du Delphi kannst.
    Ich habe noch gar keinen richtigen Programmcode (wie gesagt, ich will dass ja alles erst noch umstellen).

    Ich habe aber zum Test versucht die Procedure SetLength aufzurufen (dass ist eine Delphi (Pascal) Routine).
    Warum brauche ich dass? Weil ich zum Teil arrays zurückliefern muss und dass ist in Delphi immer ein ziemliches gemurkse.

    Hier mal mein (extra ganz stupider) Code:

    TITLE DelphiTest.asm
    
    .686P
    .XMM
    .model FLAT, PASCAL
    
    .code
    _DynArraySetLength PROTO PASCAL
    public SetLen
    
    ; procedure SetLen(var B : array of Byte; val : Byte);
    SetLen PROC
      call  _DynArraySetLength		; parameter liegen korrekt
    SetLen ENDP
    
    END
    
    unit TestUnit;
    
    interface
    
    uses
    	SysUtils;
    
      procedure SetLen(var B : array of Byte; val : Byte);
    
    implementation
    
      procedure SetLen(var B : array of Byte; val : Byte); external; {$L DelphiTest.obj}
    
    end.
    

    Das führt zu der Meldung:
    Ungenügende Forward- oder External-Deklaration von _DynArraySetLength

    Das Problem ist mir natürlich klar, fragt sich nur warum der Linker es nicht finden kann. SetLength(ruft im Endeffekt DynArraySetLength auf) ist deklariert in der System.pas, die man aber gar nicht direkt einbinden kann ... ziemlich nervig.



  • mhh.. (Borland) Delphi arbeitet doch mit fastcall !?.



  • masm unterstützt fastcall nicht, jwasm(masm clon) aber schon.
    Die Deklaration müsste dann so aussehen:
    SetLen PROTO FASTCALL bla:ptr BYTE,val:BYTE

    Ein andere Möglichkeit wäre es, wenn du die calling convention in delphie zu z.B. sdtcall änderst.



  • x86-64 schrieb:

    masm unterstützt fastcall nicht, jwasm(masm clon) aber schon.
    Die Deklaration müsste dann so aussehen:
    SetLen PROTO FASTCALL bla:ptr BYTE,val:BYTE

    Ein andere Möglichkeit wäre es, wenn du die calling convention in delphie zu z.B. sdtcall änderst.

    Hallo und Danke für eure Antworten.

    1. Delphi hat nicht mehr Pascal als Standardaufrufkonvention, sondern Register. Dies entspricht am ehesten dem, was in C/C++ der fastcall ist. Die Parameter werden in Reihenfolge ins EAX, EDX und ECX Register gelegt.

    2. Ich kann die Aufrufkonvention in Delphi sogar in CDECL ändern; aber natürlich nicht von den bereits bestehenden Funktionen (wie eben SetLength). Die ist weiterhin Register.

    Ich vermute jetzt mal, dass die Meldung nicht wirklich daraufhin deutet, dass er nur differierende Calling Conventions gefunden hat, aber in Delphi weiß man ja nie 😉
    Ich schaue es mir mal an.



  • So,

    ich hab jetzt einfach mal versucht mit meinem eigenen (zunächst simplen) Code-Implementationen weiter zu machen.

    Ich glaube mir reicht das Hilfe-Dokument allein nicht aus; dass hat ja stellenweiße nicht mehr viel mit Assembler zu tun, was da gemacht wird.

    Wie genau implementiere ich jezt eine Funktion? Angenommen ich habe eine Funktion IntCmp, die aus einem C-Programm aufgerufen werden soll. Ich bin da jetzt folgendermaßen drangegangen:

    TITLE Compare.asm
    
    .686P
    .XMM
    .MODEL FLAT, C
    
    PUBLIC IntCmp:PROC
    
    .CODE
    _TEXT    SEGMENT
    _lho$    = 8
    _rho$    = 12
    _dwSize$ = 16
    IntCmpEx PROC
    	push  edi
    	push  esi
    	push  ebx
    
    	xor   ebx, ebx
    
        test  ecx, 0xFFFFFFFC
        lea   esi, DWORD PTR [eax+ecx-1]
        lea   edi, DWORD PTR [edx+ecx-1]
        jz    Bytes
    
        sub     esi, 3
        sub     edi, 3
    
      DWORDs:
        mov   eax, DWORD PTR [esi]
        mov   edx, DWORD PTR [edi]
    
        cmp   eax, edx
        jnz   Exit
    
        sub   esi, 4
        sub   edi, 4
        sub   ecx, 4
    
        test  ecx, 0xFFFFFFFC
        jnz   DWORDs
        test  ecx, ecx
        jz    Exit
    
        add   esi, 3
        add   edi, 3
    
      Bytes:
        movzx eax, BYTE PTR [esi]
        movzx edx, BYTE PTR [edi]
    
        dec   edi
        dec   esi
    
        cmp   eax, edx
        jnz   Exit
    
        dec   ecx
        jnz   Bytes
    
      Exit:
        seta  bl
        sbb   ebx, 0
        mov   eax, ebx
    
        pop   ebx
        pop   esi
        pop   edi
    IntCmpEx ENDP
    _TEXT    ENDS
    
    END
    

    Bekomme aber folgende Fehler:

    (8):  Error A2008 : Syntax Error : :
    (23): Error A2206 : Missing operator in expression
    (42): Error A2206 : Missing operator in expression
    

    23 und 42 sind die Zeilen mit der Hexzahl. Wie muss ich denn Hexzahlen in MASM angeben? Mit einem nachgestellten 'h' mag er es auch nicht (unbekannter Bezeichner).

    Was ich auch noch gesehen habe ist folgende Art der Funktionsdeklaration:

    myproc  PROC FAR C PUBLIC <callcount> USES di si,
            argcount:WORD,
            arg2:VARARG
    

    was muss ich da denn alles angeben und vorallem: Was gibt denn USES an? Alle verwendeten Register?



  • Zu USES: Nach uses folgen Register, die automatisch beim Eintritt in die Prozedur auf den stack gepusht werden sollen. Überall wo RET oder LEAVE steht, werden dann die Argument wieder vom stack geholt (pop). Für eine normale Funktionsdefinition reicht:
    Name proc Parameter1:DWORD,Parameter2:DWORD ...
    DWORD = nur Beispielhaft, man könnte auch byte,word,real4,real8... verwenden.
    Der Prototyp dazu sähe gleich aus, bis auf das man 'proc' durch 'proto' ersetzt.

    ; die von masm erstellte object file (*.obj) muüsste man dem c-linker mitteilen
    .686P
    .XMM
    .MODEL FLAT, C
    
    IntCmpEx PROTO      ;hat keine Parameter?
    
    .CODE               ; zwischen den Segment wechselt man mit .code .data .data? .const
    start:              ; 
        _lho$    = 8
        _rho$    = 12
        _dwSize$ = 16
    
    ; Allgemeiner Hinweis: 
    ; Masm verfügt über hochsprachen Konstrukte wie .if/.eleif/.ele/.endif   .while/.endw   .repeat/.until   .break   .continue 
    ; Bsp: .if eax >= edx && ecx != MyDwordVar
    ;           ...
    ;      .endif
    ; Vergleichsoperatorn: '!=', '<=' , '>=' , '==' ,'<' , '>' 
    ; Sonstige:  Negieren       : '!'
    ;            Bitverknüpfung : 'a&b' = A AND B
    ;                           : 'a|b' ? A OR B
    ; - Mehrere Bedingungen verknüpft man mit '&&' und '||' 
    ; - Klammern kann man auch: ((a!=b) || !(b>=c)) ...
    ; - Flags können getestet werden: ZERO?,CARRY?,OVERFLOW? ...
    ;   Bsp: .if !ZERO?  ; wenn zero-flag nicht gesetzt
    ; - Es können Register, Speicheroperanten und Konstanten verglichen werden, aber nie 2 Speicheroperanten miteinaner
    ; - die Operanten die verglichen werden, müssen natürlich gleich groß sein
    ; - u.v.m. ;)
    
    IntCmpEx PROC       ; hat keine Parameter ?
        push  edi
        push  esi
        push  ebx
    
        xor   ebx, ebx
    
        test  ecx, 0FFFFFFFCh               ; Zahlen dürfen nie mit Buchstaben anfangen -> 0 davor falls nötig
                                            ; hex-dezimale Zahlen haben das "h" Suffix, (binär: 'y' oder 'b')
        lea   esi, DWORD PTR [eax+ecx-1]
        lea   edi, DWORD PTR [edx+ecx-1]
        jz    Bytes
    
        sub     esi, 3
        sub     edi, 3
    
      DWORDs:
        mov   eax, DWORD PTR [esi]
        mov   edx, DWORD PTR [edi]
    
        cmp   eax, edx
        jnz   Exit
    
        sub   esi, 4
        sub   edi, 4
        sub   ecx, 4
    
        test  ecx, 0FFFFFFFCh
        jnz   DWORDs
        test  ecx, ecx
        jz    Exit
    
        add   esi, 3
        add   edi, 3
    
      Bytes:
        movzx eax, BYTE PTR [esi]
        movzx edx, BYTE PTR [edi]
    
        dec   edi
        dec   esi
    
        cmp   eax, edx
        jnz   Exit
    
        dec   ecx
        jnz   Bytes
    
      Exit:
        seta  bl
        sbb   ebx, 0
        mov   eax, ebx
    
        pop   ebx
        pop   esi
        pop   edi
    
        ret ; ohne RET wird das nichts ;)
    IntCmpEx ENDP
    
    END start
    


  • Problematisch ist bei mir gerade eher, dass ich nicht in der Lage bin die entstandene Objektdatei im Visual Studio zu linken; mit GCC geht's.

    Hab das Ganze so gemacht:

    #pragma comment(linker, "/include:Compare.obj")
    
    #define LPCVOID	const void*
    
    extern int IntCmp(LPCVOID lho, LPCVOID rho, LPCVOID lpSize);
    
    int main(int argc, char** argv, char** env)
    {
    	int i1 = 7, i2 = 9, res = 0;
    
    	res = IntCmp(&i1, &i2, sizeof(i1));
    
    	return 0;
    }
    

    Er sagt immer

    unresolved external symbol '_IntCmp'
    

    Zudem bekomme ich es nicht hin zwei Funktionen zu deklarieren.

    TITLE SimpleTest.asm
    
    .686P
    .XMM
    .MODEL FLAT
    
    Proc1 PROTO
    Proc2 PROTO
    
    .CODE
    
    Proc1 PROC
    	; ;
    Proc1 ENDP
    
    Proc2 PROC
    	; ;
    Proc2 ENDP
    
    END
    

    MASM hat damit kein Problem. So lange ich nur Proc1 hier deklariere hat Delphi nichts dagegen (in Delphi sind beide Funktionen als extern deklariert und das Obj-File gelinkt). Wenn beide drin sind, dann sagt er bei beiden ungenügende external Deklaration bzw. "Falsche globale Symboldefinition".

    Was mache ich falsch?



  • Vielleicht fehlt da einfach ein extern "C":

    ...
    extern "C"
    {
        extern int IntCmp(LPCVOID lho, LPCVOID rho, LPCVOID lpSize);
    }
    ...
    


  • Kennst du dich vielleicht auch mit Delphi aus?
    Habe ein Objekt-File, dass ich mit MASM folgendermaßen kompiliert habe:

    ml /c /omf "source.asm"

    Das ganze habe ich dann per "{$LINK "Pfad\source.obj"} in Delphi gelinkt und bekomme aber immer die Meldung: "Falsches Dateiformat".



  • das omf-Format von Delphi weicht etwas vom Standard ab ... Im Manual zu Agner Fog's ObjConverter steht etwas dazu.
    Aber warum schreibst du nicht einfach eine Dll in masm? - ist doch am einfachsten.



  • dll schrieb:

    das omf-Format von Delphi weicht etwas vom Standard ab ... Im Manual zu Agner Fog's ObjConverter steht etwas dazu.
    Aber warum schreibst du nicht einfach eine Dll in masm? - ist doch am einfachsten.

    Jo, hab ich jetzt auch gemacht. Es ist wirklich nur rum gemurkse, wenn man mit Delphi OMF-Dateien arbeiten muss. Entweder bin ich zu blöd, ... auf jeden Fall geht es mal und mal geht es nicht ... Irgendwie scheint es, wie du schon sagtes, sich nicht wirklich an den Standard zu halten.


Anmelden zum Antworten