2011-11-28 68 views
3

我在Delphi中構建一個DLL,它需要類似於Windows API的工作原理。此DLL只有一個導出函數...重新創建C#Delphi結構``Record``作爲參數傳遞到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#結構類,因爲它需要完全匹配的記錄在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(); 
} 

問題第3部分:

在這種情況下,在記錄(或結構體內的結構體)內部有記錄是否安全?很確定,但我需要確保。

+2

1.使用'System.Drawing.Color'不起作用。您需要使用整數類型並創建轉換函數。 2.我看不出'PChar'有什麼問題。當然你也不需要通過這個長度 - 它是零終止的。如果你真的不喜歡'PChar',那麼使用char'的內聯'array [0..255]。 3.不要在你的struct聲明中使用'new MyOtherRecord'。這只是一種內聯值類型。我不回答,因爲有太多問題需要回答。我寧願一次做一個。 –

+2

擴展我最後的評論,這裏有關於'System.Drawing.Color'的問題。在p/invoke結構中有很多關於字符串的說法。等等。我只是沒有精力在一個答案中涵蓋所有角度。我懷疑CharSet,CallingConvention,DllImport等會有後續的跟進。也許別人會接受這個接力棒。抱歉。根據Stack Overflow慣例,你應該一次提出一個問題,當回答一個問題時應該涵蓋所有提出的問題。 –

+1

哦,繼續吧,我會去的,但我不想在提交我的答案後進行大量修改!任何後續行動都可能成爲下一個問題! ;-) –

回答

4

我打算假設信息是從C#流向德爾福的,而不是其他方式,主要是因爲這使得寫作答案時生活變得輕鬆許多,而且您沒有另行說明!

在這種情況下,德爾福函數聲明應該是:

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

的第一點是,你不能指望System.Drawing.Color到由P處理/調用編組。聲明顏色爲int並使用ColorTranslator.ToWin32ColorTranslator.FromWin32來處理轉換。


沒有什麼值得與PChar一起擔心的。由於空終止符,因爲長度隱含在PChar中,所以不需要具有字符串長度的字段。只需在Delphi記錄中的C#struct PChar中聲明該字段爲string,並讓P/invoke編組完成它的魔法。不要嘗試寫入Delphi的PChar內容。那將以淚結束。如果你想傳遞一個字符串回到C#代碼,那麼有辦法,但我不會在這裏解決它們。


內聯結構完全沒問題。沒有什麼可擔心的。不要用new分配它們。只要將它們視爲價值類型(它們是),如int,double等。


在適當的時候,你將需要添加StructLayout屬性等,有DllImport等申報您的DLL功能。


總之,我將宣佈你的結構是這樣的:

德爾福

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相同。

我只編譯了這個在我的腦海裏所以可能會有一些皺紋。

+0

啊,只需單擊一下,爲PChar使用相應的「字符串大小」的唯一原因就是我將數據從DLL傳遞迴C#。我不記得爲什麼我使用'var'開頭,沒有biggie切換到'const'。謝謝,這足以讓這個項目沒有任何進一步的問題。 –

2

如果你不想在德爾福方面使用PChar,你最好的選擇是一個固定長度的字符數組。但是,PChar類型專門用於處理這些情況:它是一種C風格的以NULL結尾的字符串。爲了清楚您的C#定義,您可以使用MarshalAs屬性來準確指出您在呼叫站點上期望的字符串類型。結構中字符串的默認值取決於您使用的是哪個版本的Framework:Compact Framework僅支持Unicode字符串(LPWSTR),否則將爲LPSTR。由於字符串編組有7種不同的選項,我總是指定一個我想要的,即使它是默認的,但我認爲在你的情況下它是可選的。

另外,如上所述,C#顏色類型與Delphi TColor類型不同。 TColor只是一個奇怪的混合格式的整數,而Color除了RGB顏色之外還有一堆附加屬性。您有幾個選擇:定義一個新的C#結構以匹配TColor定義,或者只使用int並手動構建值。這裏有更好的描述the structure of a TColor

最後,對於像struct這樣的值類型,你實際上並不需要用new來實例化它們;如果你只是聲明一個結構類型的變量,那麼空間就是爲你分配的。使用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; 
} 

有一件事我不知道的是記錄對齊。我想我應該回想一下,德爾福「理論上」默認使用8字節對齊方式,但是「實際上」是基於它們的類型對齊字段;這由$ {A}指令控制。在C#中,除非您使用明確的[StructLayout],否則您的字段將根據其大小進行對齊。記錄中的所有內容都是整數大小的值,因此您應該保證安全,但是如果看到數據損壞的情況,請檢查Delphi和C#結構的「sizeof」值,並確保它們相同。

如果不是,您可以使用[StructLayout(LayoutKind.Explicit)]和[FieldOffset]屬性來精確指定C#結構的對齊方式。

UPDATE:

感謝@大衛赫弗南爲指出PChar類型在Delphi 7是LPSTR(在這種情況下,我個人的偏好是使用PWideChar在Delphi中,因爲.NET CF不支持ANSI和Windows在內部使用UTF-16,但無論哪一個都適用。)答案更新以匹配。

+1

根據我的經驗,Delphi默認結構對齊與C#marshaller兼容。 Jerry使用的是Delphi 7,他的'PChar'是ANSI,因此它應該是'LPSTR'。否則,我們似乎基本上是一致的! –

+0

謝謝你們對這一點 - 字符串類型對我來說是瘋狂的。 –

+1

你說的CF是真的,但請記住delphi不會在CF上運行。我也會使用Unicode和現代的delphi,但這是另一回事。 –