2010-06-24 3 views
6

방금 ​​문제를 해결했지만 곧 "문제가되는 것"과 같은 문제가 생길 수 있습니다. 그걸로.델파이 스택 불일치 + COM 마샬링 = 잘못된 마셜링

Delphi는 변수를 스택에 정렬하지 않으며이 동작을 제어하기위한 지시문/옵션이 없습니다. 내 XP SP3의 기본 COM 마샬 러는 레코드를 마샬링 할 때 4 바이트 정렬이 필요합니다. 더욱이, 정렬되지 않은 포인터를 만나면 오류를 반환하지 않습니다. 포인터를 가장 가까운 4 바이트 경계로 반올림하고 그런 식으로 계속합니다.

따라서 참조로 COM 마샬링 된 함수에 스택에 할당 한 레코드를 전달하면 문제가 발생하고 알지도 못합니다.

메모리 관리자가 8 바이트 이상으로 모든 것을 정렬하는 경향이 있으므로 New/Dispose를 사용하여 레코드를 할당하면 문제가 해결 될 수 있지만 신, 불편 함이 있습니다. 불일치 부분과 "trim-down-pointer "부분.

이것이 정말로 이유입니까, 아니면 어딘가 잘못 되었습니까?

업데이트 : 재현하는 방법 (Delphi 2007 for Win32).

uses SysUtils; 

type 
    TRec = packed record 
    a, b, c, d, e: int64; 
    end; 

    TDummy = class 
    protected 
    procedure Proc(param1: integer); 
    end; 

procedure TDummy.Proc(param1: integer); 
var a, b, c: byte; 
    rec: TRec; 
begin 
    a := 5; 
    b := 9; 
    c := 100; 

    rec.a := param1; 
    rec.b := a; 
    rec.c := b; 
    rec.d := c; 
    writeln(IntToHex(integer(@rec), 8)); 
    readln; 
end; 

var Obj: TDummy; 
begin 
    obj := TDummy.Create; 
    try 
    obj.Proc(0); 
    finally 
    FreeAndNil(obj); 
    end; 
end. 

이것은 홀수 결과 주소를 나타내며, 분명히 어떤 것도 정렬되지 않습니다. 그렇지 않으면 "a, b, c : byte"에 더 많은 바이트 변수를 추가하십시오 (함수의 끝 부분에서 일부 작업을 시뮬레이트하는 것을 잊지 마십시오).

COM이있는 부분은 재현하기가 쉽지만 설명하는 것이 더 길다. 샘플 서버라는 새 VCL 응용 프로그램을 만들고 형식 라이브러리에 ISampleObject를 구현하는 COM 개체 SampleObject를 추가합니다. ISampleObject는 유형 라이브러리에서 Ole 자동화로 표시되어 있는지 확인하십시오. 형식 라이브러리를 열고 5 개의 __int64 필드가있는 새 SampleRecord를 선언하십시오. 단일의 SampleRecord * out 파라미터를 가지는 SampleFunction를 ISampleObject에 추가합니다. 반환 고정 값으로 TSampleObject에서 SampleFunction 구현 : 적어도 내가 체크 한

SampleRecord = packed record 
    a: Int64; 
    b: Int64; 
    //... 
end; 

,이 :

function TSampleObject.SampleFunction(out rec: SampleRecord): HResult; 
begin 
    rec.a := 1291; 
    rec.b := 742310; 
    //... 
    Result := S_OK; 
end; 

델파이가 자동으로 생성 된 형식 라이브러리 헤더 코드에 "포장 레코드"로 SampleRecord를 선언하는 방법을 참고 는 Delphi 2010에서 수정되었습니다. 자동으로 생성 된 레코드는 거기에 압축되어 있지 않습니다.

COM 서버를 등록하십시오. 그것을 실행하십시오. 특히 (

uses SysUtils, Windows, ActiveX, SampleServer_TLB; 

procedure TDummy.Proc(param1: integer); 
var a, b, c: byte; 
    rec: SampleRecord; 
    Server: ISampleObject; 
begin 
    a := 5; 
    b := 9; 
    c := 100; 

    rec.a := param1; 
    rec.b := a; 
    rec.c := b; 
    rec.d := c; 
    Server := CoSampleObject.Create; 
    hr := Server.SampleFunction(rec); 
    writeln('@: 'IntToHex(integer(@rec), 8)+', rec.a='+IntToStr(rec.a)); 
    readln; 
end; 

var Obj: TDummy; 
begin 
    CoInitializeEx(nil, COINIT_MULTITHREADED); 
    obj := TDummy.Create; 
    try 
    obj.Proc(0); 
    finally 
    FreeAndNil(obj); 
    CoUninitialize(); 
    end; 
end. 

이 녹화의 주소가 일치하지 않는 경우, 녹화 필드의 값이 잘못된 것을 관찰 :

지금 대신하고있는 경우 Writeln의이 서버를 호출 (샘플 1) 위의 소스를 수정 8 비트, 16 비트 또는 24 비트로 비트 시프트, 때로는 다음 값으로 래핑 됨).

+0

어떤 델파이 버전, 어떤 메모리 관리자입니까? 시도해 볼 수있는 예를 제공해 줄 수 있습니까? –

+0

Delphi 2007, 즉시 사용 가능한 FastMM4. 예제가 추가되었습니다. – himself

+1

이 테스트 케이스를 사용하여 Quality Central에 리포트를 입력하십시오. http://qc.embarcadero.com –

답변

8

스택이 잘못 정렬되는 예가 있습니까? Delphi는 모든 것을 4 바이트 경계에 정렬해야합니다. 내가 생각할 수있는 유일한 경우는 정렬 불일치의 원인이 될 수 있습니다. 호출 체인의 일부가 명시 적으로 스택에 잘못 배치 한 일부 어셈블러 코드 인 경우입니다.

+0

팩 레코드의 필드가 가능하거나 배열의 바이트가 가능합니까? –

+1

그렇다면 스택 저장 장치와 어떤 관련이 있습니까? –

+1

@Allen - 변수 및 코드 정렬 옵션 (특히 16 바이트 경계 정렬 허용)은 매우 바람직합니다. – PhiS