Delphi - 如何将“类型”作为参数传递

4
我想知道是否可以将一个声明的类型(在本例中是记录)传递给我的函数。如果不是因为 SizeOf() 函数可以接受类型作为参数,我甚至不会提出这个问题。
我正在将代码从 C 翻译成 Delphi,并希望尽可能保持与原始代码的接近。C 程序声明了 PushArray 和 PushStruct 作为宏。由于 Delphi 没有宏支持,我正在尝试将它们转换为函数。
我已经搜索过一些信息,似乎可以使用泛型类型。例如,function PushStruct<T>(Arena : Pmemory_arena; dtype : <T>) ,但你只能在面向对象的应用程序中使用它。
function PushSize_(Arena : Pmemory_arena; Size : memory_index) : pointer;
begin
    Assert((Arena^.Used + Size) <= Arena^.Size);
    Result := Arena^.Base + Arena^.Used;
    Arena^.Used := Arena^.Used + Size;
end;

function PushStruct(Arena : Pmemory_arena; dtype : ?) : pointer;
begin 
    result := PushSize_(Arena, sizeof(dtype));
end;

function PushArray(Arena : Pmemory_arena; Count: uint32; dtype : ?) : pointer;
begin
    result := PushSize_(Arena, (Count)*sizeof(dtype))
end;

这是原始的C代码:

#define PushStruct(Arena, type) (type *)PushSize_(Arena, sizeof(type))
#define PushArray(Arena, Count, type) (type *)PushSize_(Arena, (Count)*sizeof(type))
void *
PushSize_(memory_arena *Arena, memory_index Size)
{
    Assert((Arena->Used + Size) <= Arena->Size);
    void *Result = Arena->Base + Arena->Used;
    Arena->Used += Size;

    return(Result);
}

这种东西让我毛骨悚然。你在翻译的整个程序中,不论是什么工作,都应该考虑重新用面向对象的方式实现,而不是试图直接翻译你的 C 代码。如果没有必要,为什么还要继续做所有这些巫术指针计算呢?你真的喜欢 C 让你在如此少的代码行中自食恶果的方式吗?也许你甚至不需要完全翻译这整个东西,只需使用不同的模式、不同的数据结构、使用真正的数据类型或对象,而不是低级内存缓冲区黑客技巧来音译即可。 - Warren P
4
亲爱的沃伦, 我正在翻译一份来自教育资源的代码,该资源特别教授低级voodoo内存缓冲区访问。作者表示,尽管他知道如何使用高级语言,但这种低级操作是一门逐渐消亡的艺术形式,他正在教授它。之后我们甚至会编写一些汇编代码。所以尽管我知道你的意思是好的,但在这种情况下不适用。 - Maudrid
好的。很高兴你把你的能力用于善良而不是邪恶。 - Warren P
3个回答

5

C 代码中没有向函数传递类型信息。预处理器正在扩展宏并计算大小。您可以从函数的原型看到:

void *PushSize_(memory_arena *Arena, memory_index Size)

由于Delphi中没有宏,因此您无法进行直接翻译。个人而言,如果是我,我不会尝试完全匹配C代码。我会传递大小并让调用者使用SizeOf。我认为这并不是一个可怕的负担。这仍然让您拥有非常接近字面翻译的东西 - 您所缺少的只是方便的宏。

如果您想要使用泛型,可以这样做,但需要使用静态方法。例如:

type
  TMyClass = class
    class function PushSize(Arena: Pmemory_arena; Size: memory_index): Pointer; static;
    class function PushStruct<T>(Arena: Pmemory_arena): Pointer; static;
  end;
....
class function TMyClass.PushSize(Arena: Pmemory_arena; Size: memory_index): Pointer;
begin
  Result := ....;
end;

class function TMyClass.PushStruct<T>(Arena: Pmemory_arena): Pointer;
begin
  Result := PushSize(Arena, SizeOf(T));
end;

如果您想返回一个类型化的指针,它会像这样:

type
  TMyClass<T> = class
    type P = ^ T;
    class function PushSize(Arena: Pmemory_arena; Size: memory_index): Pointer; static;
    class function PushStruct(Arena: Pmemory_arena): P; static;
  end;
....
class function TMyClass<T>.PushSize(Arena: Pmemory_arena; Size: memory_index): Pointer;
begin
  Result := ....;
end;

class function TMyClass<T>.PushStruct(Arena: Pmemory_arena): P;
begin
  Result := PushSize(Arena, SizeOf(T));
end;

显然,你可以在TMyClass的位置上使用另一个名称!
我并不认为泛型在这里很合适,因为我猜想你想要尽可能直接地进行翻译。在这种情况下,我不会选择使用泛型。

你可能是对的。你有没有想过 sizeOf 是如何工作的?由于我在 System.pas 中找不到实际的声明,我很好奇它是如何声明的。 - Maudrid
这是一个编译器内置函数,因此没有函数源代码,因为它不会在运行时被评估。编译器知道类型、大小并且可以在编译时评估函数。因此,SizeOf(T) 是一个常量。 - David Heffernan

2
您可以通过声明自己所需的每个记录类型的Push*函数来“扩展宏”。
type
  // just guessing here...
  PMemoryArena = ^TMemoryArena;
  TMemoryArena = record
    Base: Pointer;
    Used: Cardinal;
    Size: Cardinal;
  end;
  TMemoryIndex = Cardinal;
  // 1st example record type
  PMyRecord1 = ^TMyRecord1;
  TMyRecord1 = record
    I1: Integer;
    I2: Integer;
  end;
  // 2nd example record type
  PMyRecord2 = ^TMyRecord2;
  TMyRecord2 = record
    D1: Double;
    D2: Double;
  end;

function PushSize_(Arena: PMemoryArena; Size: TMemoryIndex): Pointer; inline;
begin
  Assert(Arena^.Used + Size <= Arena^.Size);
  Result := Pointer(NativeUInt(Arena^.Base) + Arena^.Used);
  Inc(Arena^.Used, Size);
end;

function PushMyRecord1(Arena: PMemoryArena): PMyRecord1;
begin
  Result := PMyRecord1(PushSize_(Arena, SizeOf(TMyRecord1)));
end;

function PushMyRecord2(Arena: PMemoryArena): PMyRecord2;
begin
  Result := PMyRecord2(PushSize_(Arena, SizeOf(TMyRecord2)));
end;

我已经考虑过了。最好使用函数重载,这样您就可以继续使用相同的函数名称。 - Maudrid
好主意,但是您需要使用带有输出参数的过程,因为重载函数可能仅通过返回类型不同而不会发生冲突(这将无法编译)。因此,您可以编写以下类似的过程:procedure PushStruct(Arena: PMemoryArena; out Struct: PMyRecord1); overload; procedure PushStruct(Arena: PMemoryArena; out Struct: PMyRecord2); overload; 等。 - Ondrej Kelle
是的,这就是我最终实现它的方式。 - Maudrid

0

这似乎有点不必要。

为什么不,例如

function PushStruct(Arena : Pmemory_arena) : pointer;
begin 
    result := PushSize_(Arena, sizeof( Arena ));
end;

好的,如果你仔细看,询问者想要使用不同大小的值进行调用。此外,你知道sizeof(Arena)只是一个指针的大小吗? - David Heffernan
是的,David - 是我的错。 - Dsm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接