2011-01-24 68 views
2

在C++ DLL導出函數的定義是如何使用PInvoke從c#導入C++ dll?

int func1(const char* input,char** output) 
{ 
int returnCode = 0; 

std::stringstream xmlInputStream; 
xmlInputStream<< std::string(input); 
std::stringstream xmlOutputStream; 
returnCode = doRequest(xmlInputStream,xmlOutputStream); 
std::string xmlOutputString =xmlOutputStream.str(); 
*output=const_cast<char *> (xmlOutputString.c_str()); 

cout<<xmlOutputString; 

return returnCode ; 
} 

我試圖導入從C#這樣的功能...

================================================================== 

[DllImport("sample.dll")] 
    public static extern int func1(String str1,out String str2); 


string str1="hello"; 
string str2=String.Empty; 

MyClass.func1(str1,out str2); 
Console.writeln(str2); 

==================================================================== 

輸出是垃圾值...

爲什麼如此以及如何從c#中導入這個函數?

+0

你在做什麼func1 w.r.t str2? – 2011-01-24 06:40:52

+0

實際上,在func1中創建另一個char *並將其分配給str2。而這需要從c#程序中讀取......並且我不太熟悉c#。 – user186246 2011-01-24 06:43:34

+0

@ user186246:你最終得到一個地址來查找c_str(),所以你可以自由使用我的方法,創建ByteArray結構並聲明2個參數爲ByteArray * char,那麼你可以得到一個指向該數據的apointer作爲ByteArray .ptr內存指針,並從內存中提取數據 – 2011-01-24 07:30:27

回答

3

這很簡單,嘗試使用自定義編組(特別是如果你想使用char **指針)。

我發現你沒有得到「Memory ass dumb」字符串,因爲doRequest(char *,char **)的一些不正確的實現,以及分配給結果的處理不當;

首先,如果您在非託管進程中分配內存並將其傳遞給託管進程,則需要聲明一些機制來釋放非託管內存。託管GC不知道任何關於這個內存的信息,這將會丟失。其次,您需要爲結果分配內存,並將它們傳遞給非託管進程,因爲原始內存位置可以隨時重寫。

最後,你得到只是剛剛因爲你不是爲結果分配的內存,有效地只傳遞內存指針到第一輸出字符的存儲位置輸入的第一characted,說&數組[0]

這裏是競爭代碼(MS VC++/MS C#),修復所有的問題:

lib.cpp:

#include "stdafx.h" 
#include "lib.h" 
using namespace std; 

int func1(char* input, char** output) 
{ 
    stringstream xmlInputStream, xmlOutputStream; 

    xmlInputStream << string(input);  
    int returnCode = doRequest(&xmlInputStream, &xmlOutputStream); 
    string xmlOutputString = xmlOutputStream.str(); 

    //*output=const_cast<char *> (xmlOutputString.c_str()); 
    long length = sizeof(char) * xmlOutputString.length(); 
    char* src = const_cast<char *>(xmlOutputString.c_str()); 
    char* dst = (char*)malloc(length+1); 
    memcpy_s(dst, length, src, length); 
    dst[length]=0; // 0 byte always ends up given ANSI string 
    *output = dst; 

    //cout << xmlOutputString; 
    return returnCode; 
} 

int func1_cleanup(char* memptr) 
{ 
    free(memptr); 
    return 0; 
} 


int doRequest(stringstream* xmlInputStream, stringstream* xmlOutputStream) 
{ 
    *xmlOutputStream << "Memory ass dumb"; 
    return 0; 
} 

的Program.cs:

using System; 
using System.Runtime.InteropServices; 
namespace test 
{ 
    class Program 
    { 
     [DllImport("lib.dll", EntryPoint = "func1", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 
     public static extern unsafe int func1(char* input, char** data); 

     [DllImport("lib.dll", EntryPoint = "func1_cleanup", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 
     public static extern unsafe int func1_cleanup(char* data); 

     static void Main(string[] args) 
     { 
      string input = "Hello, World!"; 
      string output; 
      int result = func1(input, out output); 
     } 

     private const int S_OK = 0; 

     public static int func1(string input, out string output) 
     { 
      unsafe 
      { 
       output = null; 
       int result = -1; 
       fixed (char* parray1 = &input.ToCharArray()[0]) 
       { 
        // 
        // if you allocating memory in a managed process, you can use this 
        // 
        //char[] array = new char[0xffffff]; 
        //fixed(char* parray = &array[0]) 
        { 
         // 
         // if you allocating memory in unmanaged process do not forget to cleanup the prevously allocated resources 
         // 
         char* array = (char*)0; 
         char** parray2 = &array; 
         result = func1(parray1, parray2); 
         if (result == S_OK) 
         { 
          // 
          // if your C++ code returns the ANSI string, you can skip this extraction code block (it can be useful in Unicode, UTF-8, UTF-7, UTF-32, all C# supported encodings) 
          // 
          //byte* self = (byte*)*((int*)parray2); 
          //byte* ptr = self; 
          //List<byte> bytes = new List<byte>(); 
          //do 
          //{ 
          // bytes.Add(*ptr++); 
          //} 
          //while (*ptr != (byte)0); 
          //output = Encoding.ASCII.GetString(bytes.ToArray()); 
          output = Marshal.PtrToStringAnsi(new IntPtr(*parray2)); 
         } 
         func1_cleanup(array); 
        } 
       } 
       return result; 
      } 
     } 
    } 
} 
1

我認爲這應該工作

[DllImport("sample.dll")] 
public static extern int func1(
    [MarhsalAs(UnmanagedType.LPStr)] String str1, 
    [MarshalAs(UnmanagedType.LPStr)] out String str2); 

MarshalAs特性是互操作控制赫然強大。 (基於更新後與C++代碼)

編輯

也許更多的東西像這應該工作(我沒有VC++這臺電腦建立一個假的C對++ DLL檢查)

[DllImport("sample.dll")] 
public static extern int func1(
    [MarshalAs(UnmanagedType.LPStr)] String str1, 
    [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStr, SizeConst=1), Out] out String[] str2); 

注意我在str2中添加了[Out],我不確定這是好還是必要的,所以請嘗試使用和不使用(再次,對不起,我不能在這裏嘗試)。此外,您可能需要使用SizeParamIndex而不是SizeConst,我不記得 - 如果它需要是SizeParamIndex,則需要更改C++代碼以返回1作爲數組元素的數量。

這將str2作爲String []返回,但是您可以使用該數組的第一個元素來獲取字符串。

也可能有更乾淨的方法做到這一點 - C++中的__deref_out參數並不能很好地轉換爲我記得的C#,但我可能在這裏忘記了一些神祕的東西。