我想要一个干净的方法来增加StringBuilder()的大小,因为本地代码需要填充,下面的回调方法似乎很干净,但不知怎的,我们得到了缓冲区的副本而不是实际的缓冲区 - 我我对解释和解决方案感兴趣(最好坚持回调类型分配,因为只要它可以工作就会很好和干净。)
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");
}
答案 0 :(得分:5)
我有一个关于为什么会发生这种情况的理论。我怀疑StringBuilder
的编组涉及复制数据,将其传递给P / Invoke调用,然后复制回StringBuilder
。 I couldn't actually verify this虽然。{/ p>
唯一替代方法是首先要求StringBuilder
展平(它在内部是char[]
的链接列表),并char[]
固定,即使这样只能用于编组指针到Unicode-chars字符串,而不能用于ANSI或COM字符串。
因此,当您传递StringBuilder
作为参数时,.NET有一个明显的地方可以复制任何更改:在P / Invoke返回之后。
当您传递一个返回StringBuilder
的委托时,情况并非如此。在这种情况下,.NET需要创建一个包装器,将int => StringBuilder
函数转换为int => char*
函数。这个包装器将创建char*
缓冲区并填充它,但显然无法复制任何更改。在接受委托的函数返回后,它也无法执行此操作:它还为时尚早!
事实上,没有明显的地方可以进行反向复制。
所以我的猜测就是发生这种情况:当编组StringBuilder
- 返回委托时,.NET只能执行单向转换,因此您所做的任何更改都不会反映在{{1 }}。这比完全无法组织这样的代表要好一些。
至于解决方案:我建议首先询问本机代码需要多大的缓冲区,然后在第二次调用中传递适当大小的缓冲区。或者,如果您需要更好的性能,请猜测足够大的缓冲区,但允许本机方法进行通信,以确保需要更多空间。这样,大多数调用只涉及一个P / Invoke转换。
这可以包含在一个更方便的功能中,您可以从托管世界调用而无需担心缓冲区。
答案 1 :(得分: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);