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++代码是
cdecl
, c#代码是stdcall
。 c++代码使用宽字符串,但c#代码封送ANSI字符串。 - 第二个参数不匹配。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);