重新创建 Delphi `Record` 的 C# `Struct` 版本 - 作为参数传递到 DLL
我正在 Delphi 中构建一个 DLL,它的工作方式需要类似于 Windows API 的工作方式。该 DLL 只有一个导出函数...
function DoSomething(var MyRecord: TMyRecord): Integer; stdcall;
...其中 TMyRecord
= 我需要在 C# 中重新创建的记录。如果我没记错的话,这正是标准 Windows API 的工作原理。该记录还包含对另一种记录类型的引用...
TMyOtherRecord = record
SomeDC: HDC;
SomeOtherInt: Integer;
end;
TMyRecord = record
SomeInteger: Integer;
SomeColor: TColor;
SomeText: PChar;
SomeTextSize: Integer;
MyOtherRecord: TMyOtherRecord;
end;
问题第 1 部分:
我想看看是否可以避免使用 PChar(如果可能的话)。我预计不会传递任何超过 255 个字符的内容。是否有另一种类型我可以使用,而不需要我使用字符串大小
?
问题第 2 部分:
我需要仔细检查我是否正确声明了这个 C# 结构类,因为它需要与 Delphi 中声明的 Record 完美匹配...
public struct MyOtherRecord
{
public IntPtr SomeDC;
public int SomeOtherInt;
}
public struct MyRecord
{
public int SomeInteger;
public Color SomeColor;
public string SomeText;
public int SomeTextSize;
public MyOtherRecord OtherReord = new MyOtherRecord();
}
问题第 3 部分: >
在这种情况下,记录内有记录(或结构内有结构)是否安全?很确定是这样,但我需要确定一下。
I'm building a DLL in Delphi, and it needs to work similar to how the Windows API works. This DLL only has one exported function...
function DoSomething(var MyRecord: TMyRecord): Integer; stdcall;
...where TMyRecord
= my record I will need to re-create in C#. If I'm not mistaken, this is exactly how the standard Windows API works. This record also contains a reference to another record type...
TMyOtherRecord = record
SomeDC: HDC;
SomeOtherInt: Integer;
end;
TMyRecord = record
SomeInteger: Integer;
SomeColor: TColor;
SomeText: PChar;
SomeTextSize: Integer;
MyOtherRecord: TMyOtherRecord;
end;
Question part 1:
I'd like to see if I can avoid using PChar, if at all possible. I don't expect anything over 255 characters to be passed through. Is there another type I can use instead which won't require me to use a size of string
?
Question part 2:
I need to double check that I am declaring this C# struct class correctly, because it needs to perfectly match the Record declared in Delphi...
public struct MyOtherRecord
{
public IntPtr SomeDC;
public int SomeOtherInt;
}
public struct MyRecord
{
public int SomeInteger;
public Color SomeColor;
public string SomeText;
public int SomeTextSize;
public MyOtherRecord OtherReord = new MyOtherRecord();
}
Question part 3:
Is it safe in this case to have a record inside of a record (or struct inside of a struct)? Pretty sure it is, but I need to make sure.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我将假设信息从 C# 流向 Delphi,而不是相反,主要是因为这使编写答案时的工作变得更加轻松,并且您没有另外说明!
在这种情况下,Delphi 函数声明应该是:
第一点是您不能期望 System.Drawing.Color 被 P/invoke 编组器处理。将颜色声明为
int
并使用ColorTranslator.ToWin32
和ColorTranslator.FromWin32
来处理转换。使用
PChar
没有什么可怕的。您不需要具有字符串长度的字段,因为由于空终止符,长度在PChar
中是隐式的。只需在 C# 结构中将字段声明为string
,在 Delphi 记录中将字段声明为PChar
,然后让 P/invoke 编组器发挥其魔力。不要尝试从 Delphi 写入PChar
内容。那将会以泪水结束。如果您想将字符串传递回 C# 代码,那么有一些方法,但我不会在这里讨论它们。拥有内联结构是完全可以的。那里没什么好担心的。不要用
new
分配它们。只需将它们视为值类型(它们就是这样),例如int
、double
等。在适当的时候,您将需要添加
StructLayout
属性等上,使用DllImport
声明您的 DLL 函数,依此类推。总而言之,我会像这样声明您的结构:
Delphi
C#
我没有用
MarshalAs
string > 因为默认情况下会将其编组为LPSTR
,这与 Delphi 7PChar
相同。我只是在脑海中编译了这个,所以可能会有一些问题。
I'm going to assume that the information is flowing from C# to Delphi and not the other way, largely because that makes life a lot easier when writing the answer, and you didn't state otherwise!
In that case the Delphi function declaration should be:
The first point is that you can't expect
System.Drawing.Color
to be handled by the P/invoke marshaller. Declare the color asint
and useColorTranslator.ToWin32
andColorTranslator.FromWin32
to handle the conversion.There's nothing to be afraid of with
PChar
. You don't need a field with the string length since the length is implicit in aPChar
due to the null-terminator. Just declare the field asstring
in the C# struct,PChar
in the Delphi record and let the P/invoke marshaller do its magic. Don't try to write to thePChar
content from Delphi. That will end in tears. If you want to pass a string back to the C# code then there are ways, but I won't address them here.It's perfectly fine to have inline structs. Nothing to worry about there. Don't allocate them with
new
. Just treat them as value types (which they are) likeint
,double
etc.In due course you will need to add
StructLayout
attributes and so on, declare your DLL function withDllImport
and so on.To summarise, I would declare your structs like this:
Delphi
C#
I've not marked the
string
with aMarshalAs
since the default is to marshal it as aLPSTR
which is the same as a Delphi 7PChar
.I've only compiled this in my head so there may be a few wrinkles.
如果您不想在 Delphi 端使用 PChar,那么最好的选择是固定长度的 char 数组。然而,PChar 类型是专门为处理这些情况而构建的:它是 C 风格的 NULL 终止字符串。为了使 C# 定义更加清晰,您可以使用
MarshalAs
属性来准确指示您在调用站点上期望的“字符串”类型。结构内字符串的默认值取决于您使用的框架版本:Compact Framework 仅支持 Unicode 字符串 (LPWSTR),否则将为 LPSTR。由于字符串封送处理有 7 种不同的选项,因此我总是指定我想要的选项,即使它是默认选项,但我认为在您的情况下它是可选的。另外,如上所述,C# Color 类型与 Delphi TColor 类型不同。 TColor 只是一个奇怪的混合格式的整数,而 Color 除了 RGB 颜色之外还有很多附加属性。您有几个选择:定义一个新的 C# 结构来匹配 TColor 定义,或者仅使用 int 并手动构建值。下面是对TColor 结构的更好描述。
最后,对于像 struct 这样的值类型,您实际上不需要使用 new 来实例化它们;如果您只是声明一个结构类型的变量,则会为您分配空间。使用 new 实例化结构的唯一好处是您的构造函数将运行(您没有构造函数),并且所有字段都将初始化为其默认值。如果您打算填写所有字段,那么您就不需要这些开销。
总的来说,这就是我可能会使用的:
我不确定的一件事是记录对齐。我想我记得读过 Delphi “理论上”默认使用 8 字节对齐,但“实际上”它根据字段的类型对齐字段;这是由 ${A} 指令控制的。在 C# 中,除非您使用显式的
[StructLayout]
,否则您的字段将根据其大小进行对齐。记录中的所有内容都是整数大小的值,因此您应该是安全的,但如果您看到数据损坏的情况,请检查 Delphi 和 C# 结构的“sizeof”值并确保它们相同。如果没有,您可以使用 [StructLayout(LayoutKind.Explicit)] 和 [FieldOffset] 属性来精确指定 C# 结构的对齐方式。
更新:
感谢@David Heffernan 指出 Delphi 7 中的 PChar 是 LPSTR(在这种情况下,我个人偏好是在 Delphi 中使用 PWideChar,因为 .NET CF 不支持 ANSI,而 Windows 使用 UTF-16无论如何在内部,但无论哪个有效。)答案已更新以匹配。
If you don't want to use a PChar on the Delphi side, your best bet is a fixed-length char array. However, the PChar type is specifically built to handle these situations: it's a C-style NULL-terminated string. For clarity in your C# definition, you can use the
MarshalAs
attribute to indicate exactly what kind of 'string' you are expecting on the call site. The default for a string inside a struct depends on which version of the Framework you're using: the Compact Framework only supports Unicode strings (LPWSTR), otherwise it will be LPSTR. Since there's 7 different options for string marshaling, I always specify the one I want even if it's the default, but I think in your case it's optional.Also, as noted above, the C# Color type is not the same as the Delphi TColor type. TColor is just an integer in a strange mixed format, while Color has a bunch of additional properties beyond just the RGB colors. You have a couple of options: define a new C# struct to match the TColor definition, or just use int and build the values manually. Here's a better description of the structure of a TColor.
Finally, for value types like struct, you don't actually need to instantiate them with
new
; if you just declare a variable of a struct type the space is allocated for you. The only benefit of usingnew
to instantiate a struct is that your constructor will run (you don't have one), and all the fields will be initialized to their default values. If you plan to fill in all the fields anyway, it's just overhead you don't need.Overall, this is what I would probably use:
One thing I am not sure of is the record alignment. I think I recall reading that Delphi "in theory" used 8-byte alignment by default but that "in practice" it aligned fields based on their type; this is controlled by the ${A} directive. In C#, unless you use an explicit
[StructLayout]
your fields will get alignment based on their size. Everything in your records is an integer-sized value, so you ought to be safe, but if you see what looks like data corruption, check the "sizeof" values for both the Delphi and C# structures and make sure they are the same.If not, you can use [StructLayout(LayoutKind.Explicit)] and [FieldOffset] attributes to exactly specify the alignment of your C# structure.
UPDATE:
Thanks to @David Heffernan for pointing out that PChar in Delphi 7 is LPSTR (in which case, my personal preference would be to use PWideChar in Delphi, since the .NET CF don't support ANSI, and Windows uses UTF-16 internally anyway, but whichever works.) Answer updated to match.