如何正确发送长存活缓冲区从C#到C ++ [英] How to correctly send long-live buffer from C# to C++

查看:136
本文介绍了如何正确发送长存活缓冲区从C#到C ++的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

任务 - 提供用于从C ++ DLL注销并从C#使用它的缓冲区



我做了什么(错了)
C ++ pseudocode:

  wchar_t * _out; 
int _outsize;
externC__declspec(dllexport)void setOut(wchar_t * out,int outsize){
_out = out;
_outsize = outsize;
}
void logOut(wstring& message){
const wchar_t * m = message.c_str();
wcscat_s(_out,_outsize,m);
}
externC__declspec(dllexport)void worker(){
logOut(Lstart);
doWork();
logOut(Lfinish);
}

我在C#中做了什么(通常用于outstring)

  [DllImport(LibraryName,CallingConvention = CallingConvention.StdCall,CharSet = CharSet.Unicode)] 
extern public static void setOut([MarshalAs UnmanagedType.LPWStr)] StringBuilder buf,int size);

[DllImport(LibraryName,CallingConvention = CallingConvention.StdCall)]
extern public static void worker();

public void Run(){
const int bufsize = 1024;
var buf = new StringBuilder(bufsize);
setOut(buf,bufsize);
doWork();
Assert.True(buf.ToString()。Contains(start));
}

它失败了 - 有两个变体 - 有时候过程中止,有时缓冲区是空的。在调试期间我看到这是我的伟大的错误 - setOut中的wchar_t *参数是有效的指针只有在第一次调用 setOut (所以如果我会在其中写东西 - 我会捕获调用者中的数据)。但之后,在第二次调用发生之前( worker())指针变得无效。



StringBuilder被编组为单个PInvoke循环的指针临时。



那么,是否可以提供某种长命的CLI?
(我明白,我可以使用文件或映射文件或tcp套接字,但它不是很好,问题是如果可能与StringBuilder或char []或byte [])?


< div分配一个单独的临时缓冲区(这是 out 指向)然后将其内容复制到StringBuilder中函数退出。



由于此行为 out 指针只能用于通过将字符串复制到该指针例如通过在相同或嵌套的函数内调用 wcscat_s(out,outsize,m)



要修复代码,请考虑更改 worker()签名以通过 c $ c>参数直接指向该函数:

  void worker(wchar_t * out,size_t outsize)
{
setOut(out,outsize);
....
setOut(NULL,0);
}

另一个[不那么优雅]选项是在代码中明确地编组: p>

  [DllImport(LibraryName,CallingConvention = CallingConvention.StdCall)] 
extern public static void setOut(IntPtr buf,int size) ;


char [] buffer = new char [bufferSize];

//指定大小(以字节为单位留出空间)\ 0
IntPtr outPtr = Marshal.AllocHGlobal((bufferSize + 1)* sizeof(char)
try
{
setOut(outPtr,bufferSize);
worker();
setOut(IntPtr.Zero,0);
Marshal.Copy(outPtr,buffer,0,bufferSize);
}
finally
{
Marshal.FreeHGlobal(outPtr);
}

//使用缓冲区。


Task - supply buffer to use for log out from C++ DLL and use it from C#

What I did (and it's wrong) C++ pseudocode:

wchar_t* _out;
int _outsize;
extern "C" __declspec(dllexport) void setOut(wchar_t* out, int outsize){
  _out = out;
  _outsize = outsize;
}
void logOut(wstring& message){
    const wchar_t* m = message.c_str();
    wcscat_s(_out,_outsize,m);
}
extern "C" __declspec(dllexport) void worker() {
    logOut(L"start");
    doWork();
    logOut(L"finish");
}

What I did in C# (it's usual for outstring)

[DllImport(LibraryName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
extern public static void setOut([MarshalAs(UnmanagedType.LPWStr)]StringBuilder buf, int size);

[DllImport(LibraryName, CallingConvention = CallingConvention.StdCall)]
extern public static void worker();

public void Run(){
    const int bufsize = 1024;
    var buf = new StringBuilder(bufsize);
    setOut(buf, bufsize);
    doWork();
    Assert.True(buf.ToString().Contains("start"));
}

It fail - where are two variants - sometimes process aborted, sometimes buffer is empty.

During debug I saw that it's my great error - wchar_t* argument in setOut was valid pointer only during first call to setOut (so if i would write something in it - i will catch data in caller). But after it, before second call occured (worker()) pointer became invalid.

So as i can see StringBuilder marshalled as pointer temporary for single PInvoke cycle.

So, is it way to provide somehow long-live CLI <-> C++ string buffer? (I understand that i can use files or mapped files or tcp socket but it's not very good, question is if it's possible with StringBuilder or char[] or byte[])?

解决方案

Marshaling allocates a separate temporary buffer (this is what out points to) then copies its content into StringBuilder on the function exit.

Due to this behavior out pointer is only usable for passing back data by copying a string into that pointer e.g by calling wcscat_s(out, outsize,m) from within the same or nested function;

To fix the code consider changing worker() signature to pass out parameter directly to that function:

 void worker(wchar_t* out, size_t outsize) 
 {
     setOut(out, outsize);
     ....
     setOut(NULL, 0);
}

Another [not so elegant] option is to marshal explicitly in code:

  [DllImport(LibraryName, CallingConvention = CallingConvention.StdCall)]
  extern public static void setOut(IntPtr buf, int size);


 char[] buffer = new char[bufferSize];

 // Specify size in bytes leaving room for terminal \0 
 IntPtr outPtr = Marshal.AllocHGlobal((bufferSize + 1) * sizeof(char) );
 try 
 {
    setOut(outPtr, bufferSize);
    worker();
    setOut(IntPtr.Zero, 0);
    Marshal.Copy(outPtr, buffer, 0, bufferSize); 
 }
 finally
 {
    Marshal.FreeHGlobal(outPtr);
 }

 // use buffer.

这篇关于如何正确发送长存活缓冲区从C#到C ++的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆