2012-02-26 8 views
3

この質問はpreviousに基づいて、それはちょうどFYIですされています。私はそれが働いて得ることができました呼び出しオブジェクトメソッドASMを使用 - パート2

、しかし、私は私にははっきりしていない何かを見つけたので、誰もが次の動作を説明することができれば、それは素晴らしいだろう。

私は次のクラスがあります。

type 
    TMyObj = class 
    published 
    procedure testex(const s: string; const i: integer); 
    end; 

procedure TMyObj.testex(const s: string; const i: integer); 
begin 
    ShowMessage(s + IntToStr(i)); 
end; 

と次の2つの手順:作業バージョンをテストするために

procedure CallObjMethWorking(AMethod: TMethod; const AStrValue: string; const AIntValue: Integer); 
begin 
    asm 
    PUSH DWORD PTR AIntValue; 
    PUSH DWORD PTR AStrValue; 
    CALL AMethod.Code; 
    end; 
end; 

procedure CallObjMethNOTWorking(AInstance, ACode: Pointer; const AStrValue: string; const AIntValue: Integer); 
begin 
    asm 
    MOV EAX, AInstance; 
    PUSH DWORD PTR AIntValue; 
    PUSH DWORD PTR AStrValue; 
    CALL ACode; 
    end; 
end; 

を、一つは次のようにコールする必要があります。

procedure ...; 
var 
    LObj: TMyObj; 
    LMethod: TMethod; 
    LStrVal: string; 
    LIntVal: Integer; 
begin 
    LObj := TMyObj.Create; 
    try 
    LMethod.Data := Pointer(LObj); 
    LMethod.Code := LObj.MethodAddress('testex'); 

    LStrVal := 'The year is:' + sLineBreak; 
    LIntVal := 2012; 

    CallObjMethWorking(LMethod, LStrVal, LIntVal); 
    finally 
    LObj.Free; 
    end; // tryf 
end; 

とテストするためにNOTイオン:

procedure ...; 
var 
    LObj: TMyObj; 
    LCode: Pointer; 
    LData: Pointer; 
    LStrVal: string; 
    LIntVal: Integer; 
begin 
    LObj := TMyObj.Create; 
    try 
    LData := Pointer(LObj); 
    LCode := LObj.MethodAddress('testex'); 

    LStrVal := 'The year is:' + sLineBreak; 
    LIntVal := 2012; 

    CallObjMethNOTWorking(LData, LCode, LStrVal, LIntVal); 
    finally 
    LObj.Free; 
    end; // tryf 
end; 

そして最後の質問:なぜ作業をCallObjMethNOTWorkingないされ、CallObjMethWorkingがありながら?私は、コンパイラがTMethodをどのように処理するかについて特別なことがあると推測しています...しかし、アセンブリの知識は限られているので、理解できません。

誰かが私にこのことを説明できるかどうか非常に感謝します、ありがとう!

+0

3つのパラメータがレジスタに渡されます。スタックがどこにあるのか分かりません。あなたは、デルファイ言語ガイドのプログラムコントロールセクションを読んだことがありますか? –

+0

@David、私はちょうど、それは私がPUSH EAX、MOV EDX、DWORD PTR AStrValueと呼ぶと思われます。 MOV ECX、DWORD PTR AIntValue; ACodeを呼び出します。 (実行後の)例外を発生させることを除いては、完全に動作します...文字列はすでに参照渡しと値渡しの整数になっていますが、まだ取得できません。 – ComputerSaysNo

+0

なぜあなたは押していますかEAX?なぜメソッドを呼び出すためにasmを使用する必要がありますか? –

答えて

4

Delphi Win32のデフォルトの呼び出し規約は "register"です。最初のパラメータはEAX、EDXでは2番目、ECXでは3番目のパラメータが渡されます。スタックは、4つ以上のパラメータがある場合、または4バイトを超える値の型が渡された場合にのみ使用されますが、これはあなたの例では当てはまりません。

最初のCallObjMethWorkingプロシージャは、が既ににあり、CallObjMethWorkingが呼び出されたときにEDXにaStrValue、ECXにaIntValueが設定されているため機能します。しかし、2つのプッシュ命令をクリーンアップしていないので、プロシージャが戻るときに悪いことが起こります。

コードは次のようになります。この場合、stdcallディレクティブはオプションですが、実際にメソッドを呼び出す前に他の目的でレジスタを使用するため、パラメータが失われていないことを確認するために、このような用途に使用することをお勧めします。

procedure CallObjMeth(AInstance, ACode: Pointer; const AStrValue: string; const AIntValue: Integer); stdcall; 
asm 
    MOV EAX, AInstance; 
    MOV EDX, DWORD PTR AStrValue; 
    MOV ECX DWORD PTR AIntValue; 
    CALL ACode; 
end; 
+0

Henrickさん、ありがとう、ちょうどテストして、あなたは正しいです、 "プッシュ命令のクリーニング"についてもっと具体的にお考えですか? – ComputerSaysNo

+0

ほとんどの場合、各PUSH命令はPOP命令と一致する必要があります。呼び出し規約によってメソッドを呼び出すときなど、いくつかの例外があります。呼び出し規約によって、呼び出されたメソッドをコールバックに残してスタックをクリーンアップしますが、これはその1つではありません。 –

+0

ありがとうございます! – ComputerSaysNo

5

Henrickヘルストロームは、彼のanswerで正しいです、と私はあなたの質問は、Delphi 2010でタグ付けし、これだけのWin32に関係していることに気づきます。しかし、あなたはWin64の(デルファイ> = XE2)に前進する場合、状況はどのようになるか見て興味があるかもしれませんので、私はHenrickのコードに例のWin64バージョンを追加しました:いくつかの説明があります

procedure CallObjMeth(AInstance, ACode: Pointer; const AStrValue: string; const AIntValue: Integer); stdcall; 
asm 
{$IFDEF CPU386} 
    MOV EAX, AInstance; 
    MOV EDX, DWORD PTR AStrValue; 
    MOV ECX, DWORD PTR AIntValue; 
    {$IFDEF MACOS} 
    //On MacOSX32 ESP = #######Ch here  
    SUB ESP, 0Ch 
    {$ENDIF}  
    CALL ACode; 
    {$IFDEF MACOS} 
    ADD ESP, 0Ch // restoring stack 
    {$ENDIF}  
{$ENDIF} 
{$IFDEF CPUX64}{$IFDEF WIN64} // <- see comments 
    .NOFRAME //Disable stack frame generation 
    //MOV RCX, AInstance {RCX} //<- not necessary because AInstance already is in RCX 
    MOV R10, ACode {RDX} 
    MOV RDX, AStrValue {R8} 
    MOV R8D, AIntValue {R9D} 
    SUB RSP, 28h //Set up stack shadow space and align stack: 4*8 bytes for 4 params + 8 bytes bytes for alignment 
    {$IFNDEF DO_NOT_TEST_STACK_ALIGNMENT} 
    MOVDQA XMM5, [RSP] //Ensure that RSP is aligned to DQWORD boundary -> exception otherwise 
    {$ENDIF} 
    CALL R10 //ACode 
    ADD RSP, 28h //Restore stack 
{$ENDIF}{$ENDIF} 
end; 

を作る注:

1)ASMステートメント:デルファイXE2 x64のではパスカルとASMコードのいかなる混合が存在しないので、アセンブリコードを書くための唯一の方法は、単一asm..endブロックから構成されているルーチンであります、no begin..end。 32ビットasmコードの周囲のbegin..endも効果があることに注意してください。具体的には、スタックフレームの生成を強制し、コンパイラに関数パラメータのローカルコピーを作成させます。 (あなたが最初の場所でアセンブリを使用してに頼る場合は、コンパイラはそれをしたくない場合がありますでしょう。)

2)大会呼び出す:Win64の上で を、単一の呼び出し規約があります。 registerstdcallのようなものは無意味です。それは全く同じです、Microsoft's Win64 calling convention。これは、本質的にこれです:パラメータはRCXRDXR8及びR9レジスタ(及び/又はXMM0-XMM4に渡され、RAX/XMM0の値を返すより大きい64ビット値を参照して渡す機能が使用できる呼び出さ

RAX, RCX, RDX, R8-R11, ST(0)-ST(7), XMM0-XMM5, YMM0-YMM5, YMM6H-YMM15H、そして、。どこに適切なRBX, RSI, RDI, RBP, R12-R15, XMM6-XMM15を保持しなければならない、と呼ばれる機能が期待される状態にCPUを復元するCLD/EMMS/VZEROUPPER指示を発行する必要があります。

3)アライメントとシャドウ空間 Importantl各関数はスタック上に独自のシャドースペースを持ちます。これは、パラメータがなくても、呼び出された関数が実際にそれに触れるかどうかにかかわらず、スタックスペースの少なくとも4つのQWORDパラメータです。さらに、各関数呼び出しのサイト(各CALLステートメント)では、RSPは16バイト境界になると予想されます(MacOSX32ではESPと同じ、btw。)。 これは、sub rsp, ##; call $$; add rsp, ##のようなものにつながります。##は、関数が呼び出される(QWORD)パラメータと、アライメントのためのオプションの8バイト(RSP)の合計になります。 CALLサイトでのアラインメントが(CALLはリターンアドレスをスタックに置くため)になるので、誰もがRSPと混乱していないと仮定すると、それはそうであると予想できます。

この例では、RSPのアライメントをテストするためにSSE2 MOVDQA命令が使用されています。 (XMM5は宛先レジスタとして使用されます。これは自由に変更できますが、関数パラメータデータを含むことはできないためです)。ここ

4)仮定 コードは、コンパイラがRSPを変更するためのコードを挿入しないことを前提としています。これが真実でないかもしれない状況があるかもしれないので、この仮定をすることに注意してください。 Win64の中

5)例外処理例外処理は少し複雑であり、適切に(例えば、上記のコードはこれを実行しない)コンパイラによって行われるべきです。コンパイラがこれを可能にするためには、理想的には、新しいBASMディレクティブ/疑似命令.PARAMS.PUSHNV、および.SAVENVAllen Bauer hereのように使用する必要があります。正しい(間違った)状況が与えられれば、そうでなければ悪いことが起こる可能性があります。

+0

+1 x64(RCX = EAX(?))のインスタンスをEAXに移動しなければならないx86について、もう少し深い説明をいただき、ありがとうございます.AInstanceは最初のパラメータであるため、既にRCXに入っていますか? – ComputerSaysNo

+0

@Dorin - それがRCXにある理由は、それが最初のパラメータであることです – PhiS

+0

現在、人生は意味を持ってくれてありがとう! (:ASMで{$ IFDEF CPUX64}に注意してください – ComputerSaysNo

関連する問題