我正在使用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个字符的内容。是否有其他类型我可以使用而不需要我使用size of string
?
问题第2部分:
我需要仔细检查我是否正确地声明了这个C#struct类,因为它需要完全匹配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部分:
在这种情况下,在记录(或结构内部的结构)中有记录是否安全?很确定它是,但我需要确保。
答案 0 :(得分:5)
我将假设信息是从C#流向Delphi而不是其他方式,主要是因为这样在编写答案时会让生活变得更轻松,而你没有说明其他情况!
在这种情况下,Delphi函数声明应为:
function DoSomething(const MyRecord: TMyRecord): Integer; stdcall;
第一点是你不能指望P / invoke marshaller处理System.Drawing.Color
。将颜色声明为int
并使用ColorTranslator.ToWin32
和ColorTranslator.FromWin32
来处理转化。
PChar
没有什么可担心的。您不需要具有字符串长度的字段,因为由于null终止符,长度隐含在PChar
中。只需将字段声明为C#结构中的string
,即Delphi记录中的PChar
,并让P / invoke marshaller发挥其魔力。不要试图写入Delphi的PChar
内容。那将以泪水结束。如果你想将一个字符串传递给C#代码,那么有一些方法,但我不会在这里解决它们。
内联结构完全没问题。没有什么可担心的。不要使用new
分配它们。只需将它们视为值类型(它们是int
,double
等等。
在适当的时候,您需要添加StructLayout
属性等,用DllImport
声明您的DLL函数,依此类推。
总结一下,我会声明你的结构:
<强>的Delphi 强>
TMyOtherRecord = record
SomeDC: HDC;
SomeOtherInt: Integer;
end;
TMyRecord = record
SomeInteger: Integer;
SomeColor: TColor;
SomeText: PChar;
MyOtherRecord: TMyOtherRecord;
end;
function DoSomething(const MyRecord: TMyRecord): Integer; stdcall;
<强> C#强>
[StructLayout(LayoutKind.Sequential)]
public struct MyOtherRecord
{
public IntPtr SomeDC;
public int SomeOtherInt;
}
[StructLayout(LayoutKind.Sequential)]
public struct MyRecord
{
public int SomeInteger;
public int SomeColor;
public string SomeText;
public MyOtherRecord MyOtherRecord;
}
[DllImport("mydll.dll")]
static extern int DoSomething([In] ref MyRecord MyRecord);
我没有使用string
标记MarshalAs
,因为默认情况下将其编组为LPSTR
,与Delphi 7 PChar
相同。 / p>
我只是在脑海中编译了 ,所以可能会有一些皱纹。
答案 1 :(得分:2)
如果您不想在Delphi端使用PChar,最好的选择是固定长度的char数组。但是,PChar类型专门用于处理这些情况:它是一个C风格的以NULL结尾的字符串。为了清楚地了解C#定义,您可以使用MarshalAs
属性来准确指出您在呼叫站点上期望的“字符串”类型。 struct中字符串的默认值取决于您使用的Framework版本:Compact Framework仅支持Unicode字符串(LPWSTR),否则它将是LPSTR。由于字符串编组有7种不同的选项,我总是指定我想要的那个,即使它是默认的,但我认为在你的情况下它是可选的。
另外,如上所述,C#Color类型与Delphi TColor类型不同。 TColor只是一种奇怪的混合格式的整数,而Color除了RGB颜色之外还有许多其他属性。您有两个选择:定义一个新的C#结构以匹配TColor定义,或者只使用int并手动构建值。以下是the structure of a TColor的更好描述。
最后,对于像struct这样的值类型,实际上并不需要使用new
来实例化它们;如果你只是声明一个struct类型的变量,那么就为你分配了空间。使用new
实例化结构的唯一好处是您的构造函数将运行(您没有),并且所有字段都将初始化为其默认值。如果你计划填写所有领域,那只是你不需要的开销。
总的来说,这是我可能会使用的:
public struct MyOtherRecord
{
public IntPtr SomeDC;
public int SomeOtherInt;
}
public struct MyRecord
{
public int SomeInteger;
public int SomeColor;
[MarshalAs(UnmanagedType.LPSTR)] public string SomeText;
public int SomeTextSize;
public MyOtherRecord OtherRecord;
}
我不确定的是记录对齐。我想我记得读过Delphi“理论上”默认使用8字节对齐,但“在实践中”它根据类型对齐字段;这由$ {A}指令控制。在C#中,除非您使用明确的[StructLayout]
,否则您的字段将根据其大小进行对齐。记录中的所有内容都是整数大小的值,因此您应该是安全的,但如果您看到数据损坏,请检查Delphi和C#结构的“sizeof”值并确保它们是相同的。< / p>
如果没有,您可以使用[StructLayout(LayoutKind.Explicit)]和[FieldOffset]属性来准确指定C#结构的对齐方式。
更新:
感谢@David Heffernan指出Delphi 7中的PChar是LPSTR(在这种情况下,我个人倾向于在Delphi中使用PWideChar,因为.NET CF不支持ANSI,而Windows使用UTF-无论如何都是内部16,但无论哪个都有效。)答案更新以匹配。