2012-02-17 110 views
1

我需要將一個指針傳遞給一個結構到我的DLL,任何想法我會怎麼做呢?從C#程序調用一個C DLL

在我的C DLL:

typedef struct 
{ 
    int length; 
    unsigned char *value; 
} Sample; 


__declspec(dllexport) void __stdcall helloWorld(Sample *sample); 

在我的C#代碼:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Runtime.InteropServices; 

namespace CSharpConsole 
{ 
    class Program 
    { 
     [StructLayout(LayoutKind.Sequential, Pack = 1)] 
     private struct Sample 
     { 
      public Int32 length; 
// What Should I Declare Here? 
     } 

     [DllImport("C:\\CTestDLL.dll")] 
     private static extern void helloWorld(Sample sample); // How would I make this a pointer? 

     void HelloWorld() 
     { 
      Sample sample = new Sample(); 
      sample .length = 20; 
      // How can I fill up the values of value? 
      helloWorld(sample); // How should I pass it inside here 
      return; 
     } 

     static void Main(string[] args) 
     { 
      Program program = new Program(); 
      program.HelloWorld(); 
     } 
    } 
} 
+0

你想要使用IntPtr(一個平臺安全指針),可能還有一些數據封送。不安全的代碼塊可能是必需的。 – user978122 2012-02-17 05:12:24

回答

1

Default Marshaling for Value Types - 提供了有關編組結構的一些信息。
Calling Win32 DLLs in C# with P/Invoke - 頁面向下一半的位置有一張表格,顯示標準非託管和託管類型之間的類型轉換。

有幾種方式可以傳遞和轉換類型。
正如Michael Edenfield指出的那樣,你通常可以使用ref關鍵字來表示一個參數應該通過基本上是指針的引用傳遞。

但有時候,事情並不合作,特別是當涉及到字符串或複雜的數據類型時,所以這就是IntPtr類型出現的地方。 您有幾個使用IntPtrs的選項。
可以使用創建一個IntPtr到指定大小的非託管內存塊:

IntPtr pointer = Marshal.AllocHGlobal(sizeOfBufferInBytes); 
//Do stuff with the pointer 
Marshal.FreeHGlobal(pointer); //Don't forget to release the memory 

這顯然是一個有點危險,因爲你手動分配非託管內存。
你需要從緩衝區中的數據,整理回託管類型,使用類似Marshal.Copy(),Marshal.PtrToStructure(),Buffer.BlockCopy()等

另外,您可以創建一個託管對象,並在需要時將其固定在內存中,獲取指向它的指針並將其傳遞給您的方法。

MyObject instance = new MyObject(); 
GCHandle gch = GCHandle.Alloc(instance, GCHandleType.Pinned); 
importedMethod(gch.AddrOfPinnedObject()); //AddrOfPinnedObject() gives you an IntPtr 
gch.Free(); //Release the pinned memory so the garbage collector can deal with it 

這避免了手動編組回正確的數據類型的必要性,但是,這並不總是依賴於myObject的類型以及它是否blittable一個選項。

1

這個工作對我來說:

DLL:

typedef struct 
{ 
    int length; 
    unsigned char *value; 
} Sample; 

extern "C" __declspec(dllexport) void __stdcall helloWorld(Sample *sample) 
{ 
    MessageBoxA(NULL, (LPCSTR) sample->value, (LPCSTR) sample->value, 0); 
} 

C#:

class Program 
{ 
    [StructLayout(LayoutKind.Sequential, Pack = 1)] 
    private class Sample 
    { 
     public Int32 length; 
     public String value; 
    } 

    [DllImport("C:\\Users\\Kep\\Documents\\Visual Studio 2010\\Projects\\SODLL\\Debug\\DLL.dll")] 
    private static extern void helloWorld(Sample sample); 

    static void Main(string[] args) 
    { 
     Sample s = new Sample(); 
     s.length = 10; 
     s.value = "Huhu"; 
     helloWorld(s); 
    } 
} 

重要的是要將它標記爲類,而不是C#中的結構。

由此,您還可以使用構造等在C#:

class Program 
{ 
    [StructLayout(LayoutKind.Sequential, Pack = 1)] 
    private class Sample 
    { 
     public Int32 length; 
     public String value; 

     public Sample(String s) 
     { 
      length = s.Length; 
      value = s; 
     } 
    } 

    [DllImport("C:\\Users\\Kep\\Documents\\Visual Studio 2010\\Projects\\SODLL\\Debug\\DLL.dll")] 
    private static extern void helloWorld(Sample sample); 

    static void Main(string[] args) 
    { 
     Sample s = new Sample("Huhu"); 
     helloWorld(s); 
    } 
} 
+0

C#中的值類型可以有構造函數;另外,將值類型更改爲引用類型以獲得互操作指針行爲看起來像是矯枉過正,恕我直言。 – 2012-02-17 05:42:42

+0

其實我想讀的只是一個文件中的字節,任何想法如何?將byte []轉換爲unsigned char *? – shawn 2012-02-17 07:53:11

3

要傳遞的指針值類型爲P /調用功能只是聲明參數作爲refout。這暗示需要參考的參數並傳遞:

[DllImport("C:\\CTestDLL.dll")] 
private static extern void helloWorld(ref Sample sample); 

由於您的結構中有一個數組,你必須照顧正確聲明用於這項工作。我強烈建議,如果可能的話,你把它變成一個固定長度的數組,因爲它會使封送很多很多快樂:

typedef struct 
{ 
    int length; 
    unsigned char value[MAX_LENGTH]; 
} Sample; 

這將成爲:

public struct Sample 
{ 
    int length; 
    [MarshalAs(UnmanagedType.LPArray, SizeConst = MAX_LENGTH)] byte[] value; 
} 

如果是不可能的,那麼運行時無法知道有多少數據要回傳;在這種情況下,你可能不得不求助於你的數據的手動編組:

public struct Sample 
{ 
    int length; 
    IntPtr value; 
} 

var sample = new Sample(); 
helloWorld(ref sample); 

byte[] value = new byte[sample.length]; 
Marshal.Copy(sample.value, value, 0, sample.Length); 

然而,根據另一種回答您的評論,它看起來像你只需要得到字節塊出來的C DLL到C#中。爲此,你根本不需要一個結構,消除它會簡化很多事情。如果你只是想傳入一個數組並將其填入並返回給你,請嘗試如下所示:

(這裏假定你可以控制C和C#代碼庫;如果沒有,那麼ref的建議是的方式來完成你所需要的)

// In C# code: 
[DllImport(@"C:\CTestDll.dll")] 
private static extern void helloWorld(
    int length, 
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] byte[] buffer); 

byte[] buffer = new byte[1024 * 8]; 
helloWorld(1024 * 8, buffer); 


// In C: 
__declspec(dllexport) void __stdcall helloWorld(int, unsigned char *); 

void helloWorld(int cb, unsigned char *buf) 
{ 
    memcpy(buf, DATASRC, cb); 
} 

在C,unsigned char類型,顧名思義,大小相同的C#字節 - 8位,沒有任何跡象。在C#中,char實際上是兩個字節。運行時會自動將unsigned char *「轉換」爲byte[]。 (根本沒有真正的轉換;它們只是同一類型的不同名稱。)

+0

我有一個C結構,其中有一個無符號的char *作爲成員,並且我在C#代碼中使用byte [],爲什麼我得到的值最終錯了?該值是從文件btw中讀取的。 – shawn 2012-02-19 05:06:33

+0

啊,是的,我忘記了在結構內處理數組有點奇怪。讓我更新我的答案。 – 2012-02-19 14:35:02