重新创建Delphi`Record`的C#`Struct`版本 - 作为参数传入DLL

时间:2011-11-28 19:49:42

标签: c# delphi pinvoke delphi-7

我正在使用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部分:

在这种情况下,在记录(或结构内部的结构)中有记录是否安全?很确定它是,但我需要确保。

2 个答案:

答案 0 :(得分:5)

我将假设信息是从C#流向Delphi而不是其他方式,主要是因为这样在编写答案时会让生活变得更轻松,而你没有说明其他情况!

在这种情况下,Delphi函数声明应为:

function DoSomething(const MyRecord: TMyRecord): Integer; stdcall;

第一点是你不能指望P / invoke marshaller处理System.Drawing.Color。将颜色声明为int并使用ColorTranslator.ToWin32ColorTranslator.FromWin32来处理转化。


PChar没有什么可担心的。您不需要具有字符串长度的字段,因为由于null终止符,长度隐含在PChar中。只需将字段声明为C#结构中的string,即Delphi记录中的PChar,并让P / invoke marshaller发挥其魔力。不要试图写入Delphi的PChar内容。那将以泪水结束。如果你想将一个字符串传递给C#代码,那么有一些方法,但我不会在这里解决它们。


内联结构完全没问题。没有什么可担心的。不要使用new分配它们。只需将它们视为值类型(它们是intdouble等等。


在适当的时候,您需要添加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,但无论哪个都有效。)答案更新以匹配。