C++ dll and C# call

本文关键字:call and dll C++ | 更新日期: 2023-09-27 18:12:05

我有一个用c++编写的函数,它调用COM接口的函数它的签名:

BOOL func(LPWSTR strIn, __out LPWSTR strOut)
{
  //initcom
  //do something
  // release pointers
}
在c#:

[DllImport("funcdll.dll")]
static extern bool func(String strIn, ref String strOut);
// use it
for(int i=0;i<10;i++)
{
   if(func(strin, strout))
   {
      //do something with strout
   }
}

我已经在c++控制台应用程序中测试了我的dll,它可以工作,但在c#中它会崩溃并出现未知错误。

C++ dll and C# call

我看到你有三个问题。

  1. 调用约定不匹配。你的c++代码是cdecl, c#代码是stdcall
  2. c++代码使用宽字符串,但c#代码封送ANSI字符串。
  3. 第二个参数不匹配。c#代码假定c++代码返回一个指向C字符串的新指针,然后c#代码用COM分配器将该指针释放。你的c++代码不会这样做。

现在,更详细地处理这些

<<p> 调用约定/strong>

这很容易修复。只需将c++代码更改为stdcall,或将c#代码更改为cdecl。但不要两者都做。我会修改c#代码:

[DllImport("funcdll.dll"), CallingConvention=CallingConvention.Cdecl]
Unicode字符串/ANSI

我假定您想要使用Unicode字符串,因为您已经在c++代码中显式地选择了它们。但是P/invoke默认是封送ANSI字符串。您可以在DllImport中再次更改,如下所示:

[DllImport("funcdll.dll"), CallingConvention=CallingConvention.Cdecl, 
    CharSet=CharSet.Unicode]

从c++返回到c#的字符串

你当前的c++函数声明是这样的:

BOOL func(LPWSTR strIn, __out LPWSTR strOut)

__out装饰器没有真正的作用,除了记录您想要修改strOut所指向的缓冲区并将这些修改返回给调用者。

你的c#声明是:

static extern bool func(String strIn, ref String strOut);

现在,ref String strOut根本不匹配。ref字符串形参在c++中与此匹配:

BOOL func(LPWSTR strIn, LPWSTR *strOut)

换句话说,c#代码期望你返回一个新的指针。实际上,它随后将通过调用CoTaskMemFree来释放您在strOut中返回的缓冲区。我相信那不是你想要的。

你原来的c++代码只能通过修改传递给它的缓冲区来返回字符串给c#代码。该代码看起来像这样:

BOOL func(LPWSTR strIn, __out LPWSTR strOut)
{
    ...
    wcscpy(strOut, L"the returned string");
    ...
}

如果这是你想要的,那么你应该在c#的StringBuilder对象中分配一个足够的缓冲区。

[DllImport("funcdll.dll"), CallingConvention=CallingConvention.Cdecl, 
    CharSet=CharSet.Unicode]
static extern bool func(string strIn, StringBuilder strOut);
...
StringBuilder strOutBuffer = new StringBuilder(128);
bool res = func("input string", strOutBuffer);
string strOut = StringBuilder.ToString();

如果你不能在c#代码中决定需要多大的缓冲区,那么你最好的选择是使用BSTR来封送strOut。

如果没有看到你的c++方法的细节,我很难判断…但是,我从来没有太多的运气使用String与P/Invoke。

尝试使用IntPtr而不是String,并使用Marshal.PtrToStringUni作为传出字符串,并使用Marshal.StringToHGlobalUni将托管字符串编组到非托管土地,并且在函数调用之后,确保使用Marshal.FreeHGlobal释放非托管字符串。

另外,来自C99的我的工具不能与。net工具一起工作…我不知道为什么。我必须使用byte和check == 1。不知道你是否会在c++中遇到这个问题……但是,如果你想要我的建议,从我的经验中最不容易破碎的地方开始,在这里:

[DllImport("funcdll.dll")]
static extern byte func(IntPtr strIn, out IntPtr strOut);
// use it
string myString = "Testing";
IntPtr stringOut;
IntPtr stringIn = Marshal.StringToHGlobalUni(myString);
   if(func(stringIn, out stringOut) == 1)
   {
      //do something with strout
      string stringOutValue = Marshal.PtrToStringUni(stringOut);
      // Depending on how you dealt with stringOut in your
      // unmanaged code, you may have to: Marshal.FreeCoTaskMem(stringOut);
      // or Marshal.FreeHGlobal(stringOut) if you're returning
      // an owning reference to a copied string.
   }
Marshal.FreeHGlobal(stringIn);

我认为您的调用约定不匹配。c++的默认调用约定是cdecl,而。net的默认调用约定是stdcall。试着

[DllImport("funcdll.dll", CallingConvention = CallingConvention.Cdecl)] 
static extern bool func(String strIn, ref String strOut); 

此外,您可能必须特别告诉编组,您希望使用[MarshalAs(UnmanagedType.LPWStr)]属性将字符串编组为LPWSTR。

[DllImport("funcdll.dll", CallingConvention = CallingConvention.Cdecl)] 
static extern bool func([MarshalAs(UnmanagedType.LPWStr)]String strIn
                       ,[MarshalAs(UnmanagedType.LPWStr)]ref String strOut);

看看http://msdn.microsoft.com/en-us/library/s9ts558h.aspx

在c++中,确保你有这样的东西(至少对于第二个参数)

extern "C" BOOL __stdcall func( BSTR * pBstr )
{
    *pBstr = SysAllocString( L"Foobar" );
    return 0;
}

在c#中这样写(对于第二个参数):

static extern bool func( [MarshalAs(UnmanagedType.BStr)] ref String strOut); 

对不起,我没有一个大的答案,但请记住一些从我的经验…您是否尝试使用StringBuilder,例如将c#导入函数签名更改为

   [System.Runtime.InteropServices.DllImport("funcdll.dll")]
static extern bool func(String strIn, System.Text.StringBuilder strOut);