2013-02-03 60 views
7

我寫了一個C++ DLL,現在我需要從託管應用程序調用本地函數。C#的封裝原生DLL

導出的本地函數出現這樣的:

extern "C" __declspec(dllexport) 
bool NativeMethod(char *param1, char *param2, char *result); 

所以,從C#我會調用該函數傳遞2個輸入參數,可以1個輸出參數,顯然我會讀取返回布爾值。

我試圖以很多方式包裝所有這些,但總是得到一個PInvokeStackImbalance異常。 我知道調用本機函數的唯一方法是在.NET函數聲明中應用CallingConvention = CallingConvention.Cdecl)。然而,這樣我就無法讀取輸出參數(它始終是空字符串),並且返回值始終爲真。

+1

具有u試圖進口什麼? –

+0

我試過類似:[DllImport(「MyDll」,EntryPoint =「NativeMethod」)] public static extern Boolean NativeMethod(string param1,string param2,string param3); 此外與出param3或StringBuilder或MarshalAs(UnmanagedType.LPStr)] – bit

+0

您意識到,此輸出參數將無法正常工作? C#字符串是**不可變的**。 – antonijn

回答

12

首先,我會調整你的本地函數的原型。

由於此函數具有C接口,因此您應該使用C類型作爲布爾值,而不是C++類型,如bool。您可能想要使用Win32的BOOL類型。

而且,因爲它是目前,你的作用是容易緩衝區溢出:最好是添加另一個參數指定目的地result字符串緩衝區的最大尺寸。

還要注意的是普遍調用的DLL導出純C接口函數慣例(如大量的Win32 API函數)是__stdcall(不__cdecl)。我也會使用它。

最後,由於前兩個參數是輸入字符串,您可能希望使用const來清除並強制執行const正確性。

所以,我會做導出的原生功能,這樣的原型:

extern "C" __declspec(dllexport) 
BOOL __stdcall NativeFunction(
    const char *in1, 
    const char *in2, 
    char *result, 
    int resultMaxSize); 

然後,在C#的一面,你可以使用下面的P/Invoke:

[DllImport(
     "NativeDll.dll", 
     CharSet = CharSet.Ansi, 
     CallingConvention = CallingConvention.StdCall)] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    static extern bool NativeFunction(
     string in1, 
     string in2, 
     StringBuilder result, 
     int resultMaxSize); 

請注意,對於輸出字符串,使用StringBuilder

還要注意CharSet = CharSet.Ansi用於編組的C#的Unicode UTF-16字符串ANSI(注意一個事實,即轉換爲有損 - 如果你想要一個無損轉換,只需使用在C wchar_t*串++方面也是如此)。

我做了一個簡單的C測試++本地DLL:

// NativeDll.cpp 

#include <string.h> 
#include <windows.h> 

extern "C" __declspec(dllexport) 
BOOL __stdcall NativeFunction(
    const char *in1, 
    const char *in2, 
    char *result, 
    int resultMaxSize) 
{ 
    // Parameter check 
    if (in1 == nullptr 
     || in2 == nullptr 
     || result == nullptr 
     || resultMaxSize <= 0) 
     return FALSE; 

    // result = in1 + in2 
    strcpy_s(result, resultMaxSize, in1); 
    strcat_s(result, resultMaxSize, in2); 

    // All right 
    return TRUE; 
} 

而且它是由下面的C#控制檯應用程序代碼調用成功:

using System; 
using System.Runtime.InteropServices; 
using System.Text; 

namespace CSharpClient 
{ 
    class Program 
    { 
     [DllImport(
      "NativeDll.dll", 
      CharSet = CharSet.Ansi, 
      CallingConvention = CallingConvention.StdCall)] 
     [return: MarshalAs(UnmanagedType.Bool)] 
     static extern bool NativeFunction(
      string in1, 
      string in2, 
      StringBuilder result, 
      int resultMaxSize); 

     static void Main(string[] args) 
     { 
      var result = new StringBuilder(200); 
      if (! NativeFunction("Hello", " world!", result, result.Capacity)) 
      { 
       Console.WriteLine("Error."); 
       return; 
      } 

      Console.WriteLine(result.ToString()); 
     } 
    } 
} 
+0

完美。現在一切正常。非常感謝你!! – bit

+0

@bit:不客氣。 –

1

爲什麼使用注意事項使用的DllImport Net代碼編組如以下

[DllImport(@"C:\TestLib.dll")] 
     public static extern void ProtectDocument(
      out [MarshalAs(UnmanagedType.LPStr)]string validToDate); 

,然後你可以調用函數的局部功能爲以下

string x=string.empty; 
ProtectDocument(out x); 
+0

DLL導入正是我正在使用,但我得到上面提到的異常。另外,我怎麼能聲明我想用作「out」類型的第三個參數? – bit

+0

你可以在變量 –

0
[DllImport("MyDll.dll", EntryPoint = "NativeMethod", CallingConvention = CallingConvention.Cdecl)] 
    static extern bool NativeMethod(
     [MarshalAs(UnmanagedType.LPStr)]string param1, 
     [MarshalAs(UnmanagedType.LPStr)]string param2, 
     [MarshalAs(UnmanagedType.LPStr)]string param3); 

替換爲LPStr如果您使用寬字符,請使用LPWStr

0
[DllImport("MyLibrary.dll", EntryPoint = "NativeMethod")] 
public static unsafe extern bool NativeMethod(
    [MarshalAs(UnmanagedType.LPStr)] string param1, 
    [MarshalAs(UnmanagedType.LPStr)] string param2, 
    [MarshalAs(UnmanagedType.LPStr)] char *param3); 

輸出參數必須是char *,因爲C#字符串是不可變的。你叫這樣的方法(在不安全的情況下):

char[] output = new char[100]; 
fixed (char *param = &output[0]) 
{ 
    NativeMethod("blahblah", "blahblah", param); 
} 

除非輸出參數不是字符串,但只有一個字符,在這種情況下,你可以這樣做:

[DllImport("MyLibrary.dll", EntryPoint = "NativeMethod")] 
public static unsafe extern bool NativeMethod(
    [MarshalAs(UnmanagedType.LPStr)] string param1, 
    [MarshalAs(UnmanagedType.LPStr)] string param2, 
    out char param3); 

並且你可以使用它像這樣:

char output; 
NativeMethod("blahblah", "blahblah", out output); 
+0

的前面使用對不起,但我仍然得到例外。 – bit

+0

@bit現在怎麼樣? – antonijn

+0

@什麼是例外? –

3

就能爲您節省了大量的P/Invoke的頭痛,如果你只是使用COM互操作來代替。放在一個COM接口中的方法和更改簽名遵循COM約定:

interface ISomeInterface : IUnknown 
{ 
    HRESULT NativeMethod([in] BSTR bstrParam1, [in] BSTR bstrParam2, 
         [out] BSTR* pbstrParam3, [out, retval] VARIANT_BOOL* pvbResult); 
} 

我改變的char *BSTR布爾VARIANT_BOOL,因爲這些是使用COM類型字符串和布爾分別。此外,所有COM方法必須返回HRESULT。如果您想要「實際」返回值,則必須將其添加爲最後的參數,並且還將其標記爲retval屬性。

然後,參照COM組件從C#項目添加,也不必去猜測如何使用C#類型匹配C++類型,你會得到一個直觀的C#簽名:

bool NativeMethod(string bstrParam1, string bstrParam2, out string pbstrParam3) 

(這就是它如何出現在對象瀏覽器中。)

+0

我同意你COM給C#客戶提供了方便和簡單(事實上,我給你的答案+1投票)。但是,與僅從本機DLL導出純C接口函數相比,使用ATL在C++中構建COM服務器確實具有學習曲線。如果OP可以用C++構建COM組件,那麼我同意這是一個很好的建議。但是如果他不能,可能最好是爲本地C接口DLL編寫適當的P/Invoke。 –

+0

+1對於在同一臺機器上CLR和本機代碼之間的認真工作,COM是要走的路。但如果你只是想打電話給一件事,那就太過分了。 –