2012-02-21 15 views
2

我想一個乾淨的方式增加,因爲需要通過本機代碼人口的StringBuilder()的大小,回調方法如下似乎乾淨,但不知何故我們得到了一個緩衝區的副本,而不是實際的緩衝區 - 我對解釋和解決方案感興趣(最好堅持回調類型分配,因爲如果只有它可以工作,它會很好,很乾淨)。我怎樣才能返回一個StringBuilder或其他字符串緩衝區從PInvoke的本地回調

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

namespace csharpapp 
{ 
    internal class Program 
    { 
     private static void Main(string[] args) 
     { 
      var buffer = new StringBuilder(12); 
      // straightforward, we can write to the buffer but unfortunately 
      // cannot adjust its size to whatever is required 
      Native.works(buffer, buffer.Capacity); 
      Console.WriteLine(buffer); 

      // try to allocate the size of the buffer in a callback - but now 
      // it seems only a copy of the buffer is passed to native code 
      Native.foo(size => 
          { 
           buffer.Capacity = size; 
           buffer.Replace("works", "callback"); 
           return buffer; 
          }); 
      string s = buffer.ToString(); 
      Console.WriteLine(s); 
     } 
    } 

    internal class Native 
    { 
     public delegate StringBuilder AllocateBufferDelegate(int bufsize); 
     [DllImport("w32.dll", CharSet = CharSet.Ansi)] 
     public static extern long foo(AllocateBufferDelegate callback); 
     [DllImport("w32.dll", CharSet = CharSet.Ansi)] 
     public static extern void works(StringBuilder buf, int bufsize); 
    } 
} 

本地頭

#ifdef W32_EXPORTS 
#define W32_API __declspec(dllexport) 
#else 
#define W32_API __declspec(dllimport) 
#endif 

typedef char*(__stdcall *FnAllocStringBuilder)(int); 
extern "C" W32_API long foo(FnAllocStringBuilder fpAllocate); 
extern "C" W32_API void works(char *buf, int bufsize); 

本地代碼

#include "stdafx.h" 
#include "w32.h" 
#include <stdlib.h> 

extern "C" W32_API long foo(FnAllocStringBuilder fpAllocate) 
{ 
    char *src = "foo  X"; 
    int len = strlen(src) + 1; 

    char *buf = fpAllocate(len); 
    return strcpy_s(buf,len,src); 
} 

extern "C" W32_API void works(char *buf, int bufsize) 
{ 
    strcpy_s(buf,bufsize,"works"); 
} 
+1

嘗試標誌着'StringBuilder'參數作爲'[輸入,輸出]'在P /調用簽名。 – Polynomial 2012-02-21 13:46:46

+1

作爲參數傳遞的StringBuilder工作正常 - 它是從函數指針/委託返回的一個問題。 – dice 2012-02-21 13:50:24

+0

有點令人驚訝的是,返回的'StringBuffer'顯然與傳入的參數不同。這可能只是它的方式,你必須解決它。 +1雖然。 – 2012-02-21 15:26:07

回答

5

我對爲什麼出現這種情況的理論。我懷疑編號StringBuilder涉及複製數據,將它傳遞給P/Invoke調用,然後複製回StringBuilderI couldn't actually verify this雖然。

到這一點的唯一的替代將需要StringBuilder首先被壓平(它在內部是char[]的鏈表)和char[]釘扎,並且甚至那麼這將僅用於編組到指針到Unicode工作-chars字符串,但不適用於ANSI或COM字符串。

因此,當您傳遞StringBuilder作爲參數時,.NET有一個顯而易見的地方可以將任何更改複製回來:在P/Invoke返回之後。

同樣不是當你通過一個委託返回StringBuilder真正的。在這種情況下,.NET需要創建一個將int => StringBuilder函數轉換爲int => char*函數的包裝器。這個包裝將創建char*緩衝區並填充它,但顯然不能複製任何更改。 需要代表返回的功能後也不能這樣做:現在還爲時過早!

事實上,在可能發生反向複製的地方沒有明顯的地方。

所以我的猜測是這發生了什麼:編組StringBuilder返回代表時,.NET只能執行單向轉換,因此您所做的任何更改都不會反映在StringBuilder中。這比完全無法組織這些代表要好一些。


至於解決辦法:我會建議先詢問本地代碼緩衝區多大必須,然後通過適當大小的緩衝區的第二個電話。或者,如果您需要更好的性能,請猜測足夠大的緩衝區,但允許本地方法傳達需要更多空間。這種方式大多數調用只涉及一次P/Invoke轉換。

這可以被包裝成一個,你可以只從管理世界調用,而不必擔心緩衝區更方便的功能。

+0

聽起來很可能 - 我以爲我過去做過這樣的事情,但我想我一定是用過C++/Cli或者在UNICODE環境中用char []來做的。 – dice 2012-02-21 16:47:34

+1

您可以驗證是否真的發生了複製:在非託管代碼的循環中寫入緩衝區,並且從另一個線程的循環中的StringBuilder中讀取。是的,看起來實際上發生了什麼:在本地函數被調用之前,'StringBuilder'被複制到本地緩衝區,然後在它返回之後被複制回來。 – svick 2012-02-21 16:48:23

1

除了由romkyns提供的輸入我將分享的最小變化的解決方案,我想出了。如果有人使用這個,請小心你的編碼!

主要修改是:

private static void Main(string[] args) 
    {    
     byte[] bytes = null; 
     var gcHandle = new GCHandle(); 

     Native.foo(size => 
         { 
          bytes = new byte[size]; 
          gcHandle = GCHandle.Alloc(bytes,GCHandleType.Pinned); 
          return gcHandle.AddrOfPinnedObject(); 
         }); 

     if(gcHandle.IsAllocated) 
      gcHandle.Free(); 

     string s = ASCIIEncoding.ASCII.GetString(bytes); 

     Console.WriteLine(s); 
    } 

與委託簽名量變到質變到:

public delegate IntPtr AllocateBufferDelegate(int bufsize); 
相關問題