从 C# 将 Delphi 集传递给外部 Delphi 函数
我正在尝试从 C# 调用外部 Delphi 函数,该函数采用 Delphi 集作为参数:
Delphi 代码
type
tStatus = (sIn, sOut, sAbsent, sSick);
tStatusSet = set of tStatus;
function LoadEmployees(tStatusSet aStatusSet): tEmpList;
我需要将 C# 枚举值数组(即来自 tStatus
的元素)编组为一种格式Delphi 将读取为 tStatusSet
类型:
C# 代码
tStatusSet lStatusSet = ConvertToDelphiSet(sIn, sOut);
tEmpList lEmpList = LoadEmployees(aStatusSet);
ConvertToDelphiSet
理想情况下应该是一个通用解决方案,能够处理任何枚举。我们将其定义为:
int ConvertToDelphiSet<T>(params T[] aArgs) {
int lResult = 0;
foreach (T lItem in aArgs)
{
int lValue = lItem.ToInt32();
lValue = (int)Math.Pow(2, lValue);
lResult |= lValue;
}
但这并没有返回正确的值(例如,传递所有四个 tStatus 值会导致 Delphi 只看到集合中的第三个值)。
是否有关于 Delphi 如何在内部表示集合的文档?它是所有值的简单位字段吗?有没有更强大的方法来实现这一目标?这是面向未来的证明还是我依赖可能会改变的未记录的内部功能?
I'm trying to call an external Delphi function from C# which takes a Delphi set as a parameter:
Delphi code
type
tStatus = (sIn, sOut, sAbsent, sSick);
tStatusSet = set of tStatus;
function LoadEmployees(tStatusSet aStatusSet): tEmpList;
I need to marshall a C# array of enum values (that is elements from tStatus
) into a format that Delphi will read as a tStatusSet
type:
C# code
tStatusSet lStatusSet = ConvertToDelphiSet(sIn, sOut);
tEmpList lEmpList = LoadEmployees(aStatusSet);
ConvertToDelphiSet
should ideally be a generic solution, able to cope with any enum. We have it defined as:
int ConvertToDelphiSet<T>(params T[] aArgs) {
int lResult = 0;
foreach (T lItem in aArgs)
{
int lValue = lItem.ToInt32();
lValue = (int)Math.Pow(2, lValue);
lResult |= lValue;
}
But this isn't returning the correct value (for example passing it all four tStatus
values results in Delphi only seeing the third value in the set).
Is there documentation on how Delphi internally represents a set? Is it a simple bit-field of all the values? Is there a more robust way of achieving this? Is this future proof or am I relying on undocumented internal features likely to change?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我相信它是一个位字段,但可能不是您所说的“简单”。
Delphi 集最多可以有 256 个元素,或者值最多为 255 个元素,这不一定是同一件事,因为枚举成员可以分配特定值并且不一定是连续的,例如:
导致 TSet 具有可能的最大值大小为 256 位,尽管它只有 3 个可能的成员。 (请注意,成员“a”的值为 0,而不是 1!)
如果您对集合类型或该类型的变量使用 sizeof() 函数,您可以看到这一点,这将指示该类型的存储空间有多少字节。类型或变量占用。
您设计的任何依赖于 Delphi 集类型的内部存储的机制都将是脆弱的,并且需要您在 C# 和 Delphi 中定义匹配的枚举类型,但不能轻松/可靠地识别为不同步。
如果这是一个实际问题,那么我建议您将值作为枚举成员名称数组传递,并使用 Delphi 中的 RTTI 通过将枚举名称转换为相应的枚举值,并根据需要将它们添加到您的集合中(如果指定了无效的枚举成员名称,-1 将作为 GetEnumValue 的枚举值返回):
GetEnumValue() 和相应的GetEnumName() 函数是TypInfo 单元的一部分。
通过这种方式,您将能够检测和处理 C# 代码指定了 Delphi 代码无法识别的枚举值(按名称)的情况(当然反之亦然),这种情况可能不太可靠当对任意 n 字节存储块中的匿名位进行假设时。
当然,您仍然必须确保 C# 和 Delphi 代码对枚举成员使用相同的或易于映射的名称。
一种折衷方案可能是将值作为简单的字节数组传递 = 枚举的每个成员都有一个底层字节大小的序数值,因此只要 C# 中的枚举成员与 Delphi 中的枚举成员具有相同的底层序数值您可以根据需要简单地转换它们,例如,使用非常粗略的伪代码,您应该能够很容易地适应:
给出:
I believe it is a bit-field, but probably not what you would call "simple".
A Delphi set may have up to 256 elements, or elements with values up to 255, which may not necessarily be the same thing since enum members can be assigned specific values and are not necessarily contiguous, e.g :
Results in a TSet with a possible maximum size of 256 bits even tho it has only 3 possible members. (and note that the member "a" has the value 0, NOT 1!)
You can see this if you use the sizeof() function on your set type or a variable of that type, which will indicate how many bytes of storage that type or variable occupies.
Any mechanism you devise that relies on the internal storage of a Delphi set type is going to be fragile and will require that you have enum types defined in both C# and Delphi that match but which cannot easily/reliably be identified as having fallen out of synch.
If that is a practical concern then I'd suggest that you instead pass your values as an array of enum member names and use RTTI in Delphi to rebuild the set on the Delphi side by converting the enum names to the corresponding enum values and adding them to your set as required (if an invalid enum member name is specified -1 is returned as the enum value from GetEnumValue):
GetEnumValue() and the corresponding GetEnumName() functions are part of the TypInfo unit.
In this way you will be able to detect and handle a situation where the C# code has specified an enum value (by name) that the Delphi code does not recognise (or vice versa of course), in a way that is perhaps not so reliable when making assumptions about anonymous bits in an arbitrary n-byte chunk of storage.
You do of course have to still ensure that both your C# and Delphi code are using the same, or easily mapped, names for the enum members.
A compromise might be to pass the values as a simple array of bytes = each member of an enum has an underlying byte-sized ordinal value, so as long as your enum members in C# have the same underlying ordinal value as your enum members in Delphi you can simply cast them as required, e.g, in very rough pseudocode that you should be able to adapt quite easily:
given:
Delphi 集的格式当然有文档记录,但是当您使用不同编程语言和开发环境不常见的 DLL 函数参数和函数结果数据类型时,您就会陷入困境。坚持使用 Windows API 中使用的类型,就不会出现此类问题。
这对于不同的 Delphi 版本也同样适用。当您使用字符串或对象时,您会受到以相同方式实现它们并在内部使用相同内存管理器的编译器的支配。
对于您的示例,使用
DWORD
并将集合中的每个元素编码为具有不同的 2 次幂的常量。The format of Delphi sets is of course documented, but you are painting yourself into corners when you use data types for DLL function parameters and function results that are not common to different programming languages and development environments. Stick to the types used in the Windows API, and you won't have such problems.
This does also hold true for different Delphi versions. When you use strings or objects you are at the mercy of the compiler implementing them in the same way, and using the same memory manager internally.
For your example, use a
DWORD
and encode each element of the set as a constant with a distinct power of 2.理想情况下,您永远不应该将涉及接口的集合暴露给其他语言,因为大多数语言没有这个概念,但从技术上讲,您想要的都是可能的。
正如您所说,集合是 Delphi 自动为您管理的简单位域,但它比这更棘手,因为集合变量的大小取决于集合元素的数量。集合最多可以有 256 个元素,这使得集合变量可以在内存中保存 1 到 32 个字节。
考虑到这一点,您可以对字节值进行一些修改,并将其传递给 Delphi 调用,因为您的集合只能包含 4 个元素。
Ideally you should never expose set involving interfaces to other languages as most languages don't have the notion but technically what you want is possible.
As you say sets are simple bitfields Delphi automatically manages for you but it's trickier than that because the size of a set variable depends on the number of set elements. Sets can have up to 256 elements and this makes it possible that a set variable can hold between 1 to 32 bytes in memory.
This in mind, you could do some bit fiddling on a byte value and pass it to a Delphi call as your set can only contain 4 elements.