为什么编译器坚持我的函数是内联的呢?

时间:2014-06-18 12:51:27

标签: delphi compiler-errors delphi-xe2

为什么我

  

[DCC错误] ProjectCOWArray.dpr(23):E2426内联函数不能有asm块

program ProjectCOWArray;

{$APPTYPE CONSOLE}

{$R *.res}

type
  PRefCount = ^TRefCount;
  TRefCount = array[0..1] of integer;


  TCOWArray<T> = record
  private
    fData: TArray<T>;
  private
    procedure IncRefCount;  <<-- not inline  
  end;


{ TCOWArray<T> }

procedure TCOWArray<T>.IncRefCount;
asm
  {$if defined(win32)}
  mov eax,fData;
  lock inc dword ptr [eax - 8];
  {$ifend}
  {$if defined(win64)}
  mov rax,fData;
  lock inc dword ptr[rax -12];
  {$ifend}
end;

begin
end.

Delphi XE2没有AtomicIncrement,那么如何解决这个问题呢? 它想保留汇编程序,因为否则我不能使用lock前缀,我不想使用InterlockedIncrement,因为这是一个WinAPI函数,我不希望这种开销。< / p>

3 个答案:

答案 0 :(得分:6)

这是因为泛型功能是在内联引擎之上实现的。适用于内联函数的相同限制适用于泛型函数。编译器编写者还没有采取额外步骤来使特定于泛型而不是内联函数的错误消息。

我认为调用InterlockedIncrement可能是最好的选择,对于没有AtomicIncrement内在函数的Delphi版本。或者,或者,创建自己的AtomicIncrement版本,该版本仅在Delphi版本中定义,不包含它。并且该函数可以用asm编写。嗯,显然必须写在asm中。

{$IFNDEF AtomicFunctionsAvailable}
function AtomicIncrement(var Target: Integer): Integer;
asm
  ....
end;
{$ENDIF}

或者正如@TLama建议的那样,您可以使用TInterlocked单位的System.SyncObjs来提供原子操作。

尽管如此,我认为没有必要以这种方式干涉内部。无论何时写入数组,都要通过调用SetLength(...)来实现写数组的副本。例如,这是写阵列实现的一个非常简单的副本:

unit COWArray;

interface

type
  TCOWArray<T> = record
  private
    FItems: TArray<T>;
    function GetLength: Integer;
    procedure SetLength(Value: Integer);
    function GetItem(Index: Integer): T;
    procedure SetItem(Index: Integer; const Value: T);
  public
    class function New(const Values: array of T): TCOWArray<T>; static;
    property Length: Integer read GetLength write SetLength;
    property Items[Index: Integer]: T read GetItem write SetItem; default;
  end;

implementation

function TCOWArray<T>.GetLength: Integer;
begin
  Result := System.Length(FItems);
end;

procedure TCOWArray<T>.SetLength(Value: Integer);
begin
  System.SetLength(FItems, Value); // SetLength enforces uniqueness
end;

function TCOWArray<T>.GetItem(Index: Integer): T;
begin
  Result := FItems[Index];
end;

procedure TCOWArray<T>.SetItem(Index: Integer; const Value: T);
begin
  System.SetLength(FItems, System.Length(FItems)); // SetLength enforces uniqueness
  FItems[Index] := Value;
end;

class function TCOWArray<T>.New(const Values: array of T): TCOWArray<T>;
var
  i: Integer;
begin
  System.SetLength(Result.FItems, System.Length(Values));
  for i := 0 to high(Values) do
    Result.FItems[i] := Values[i];
end;

end.

答案 1 :(得分:4)

即使你可以调用InterlockedIncrement,你仍然有获得对数组引用计数字段的引用的障碍。但是,你真的不需要它。相反,您可以让编译器为您完成工作。声明另一个数组变量并赋值给它以递增计数。然后将其类型转换为非托管类型并将其设置回空指针。即使变量超出范围,引用计数也将保持不变。

var
  dummy: TArray<T>;
begin
  dummy := fData; // increment reference count
  Pointer(dummy) := nil; // circumvent decrement
end;

此技术可用于递增任何内容的引用计数,包括数组,字符串和接口。

递减只是一行:

begin
  Pointer(dummy) := fData;
  // reference count decrements automatically at scope's end
end;

答案 2 :(得分:-1)

感谢大卫解释所有重要的为什么

一种可能的解决方案是将AtomicIncrement从通用记录中提升到黑客中。

program ProjectCOWArray;

{$APPTYPE CONSOLE}

{$R *.res}

type
  PRefCount = ^TRefCount;
  TRefCount = array[0..1] of integer;

  TCOWArray<T> = record
  private
    fData: TArray<T>;
  private
    procedure IncRefCount;
  end;

{ TCOWArray<T> }

type TAnyOldDynArray = array of integer;

procedure AtomicIncrementDynArrayRefCount(const AnArray: TAnyOldDynArray);
asm
  {$if defined(win32)}
  mov eax,AnArray;
  lock inc dword ptr [eax - 8];
  {$else}
  mov rax,AnArray;
  lock inc dword ptr [rax -12];
  {$ifend}
end;

procedure TCOWArray<T>.IncRefCount;
begin
  AtomicIncrementDynArrayRefCount(TAnyOldDynArray(fData));
end;

begin
end.