我编写了一个C ++ DLL,现在我需要从托管应用程序调用本机函数。
导出的原生函数如下所示:
extern "C" __declspec(dllexport)
bool NativeMethod(char *param1, char *param2, char *result);
所以,从C#我将调用该函数传递2个输入参数,1个输出参数,显然我将读取返回bool值。
我试图以多种方式包装所有这些,但总是得到PInvokeStackImbalance
例外。
我知道调用本机函数的唯一方法是在.NET函数声明中应用CallingConvention = CallingConvention.Cdecl
)。但是通过这种方式我无法读取输出参数(它总是空字符串),并且返回值始终为真。
答案 0 :(得分:14)
首先,我会调整原生函数的原型。
由于此函数具有 C接口,因此您应该将C类型用于布尔值,而不是像bool
这样的C ++类型。您可能希望使用Win32的BOOL
类型。
此外,就目前而言,您的函数容易出现缓冲区溢出:最好添加另一个参数来指定目标result
字符串缓冲区的最大大小。
另请注意,导出纯C接口函数(如许多Win32 API函数)的DLL的广泛调用约定是__stdcall
(不是__cdecl
)。我也会用它。
最后,由于前两个参数是 input 字符串,因此您可能希望使用const
使其清晰并强制执行const正确性。
所以,我会像这样制作导出的原生函数的原型:
extern "C" __declspec(dllexport)
BOOL __stdcall NativeFunction(
const char *in1,
const char *in2,
char *result,
int resultMaxSize);
然后,在C#端,您可以使用以下P / Invoke:
[DllImport(
"NativeDll.dll",
CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool NativeFunction(
string in1,
string in2,
StringBuilder result,
int resultMaxSize);
请注意,对于输出字符串,使用了StringBuilder
。
另请注意,CharSet = CharSet.Ansi
用于将C#的Unicode UTF-16字符串封送到ANSI(请注意转换有损的事实 - 如果您想要无损转换,只需在C ++端使用wchar_t*
字符串。)
我使用简单的C ++本机DLL进行了测试:
// NativeDll.cpp
#include <string.h>
#include <windows.h>
extern "C" __declspec(dllexport)
BOOL __stdcall NativeFunction(
const char *in1,
const char *in2,
char *result,
int resultMaxSize)
{
// Parameter check
if (in1 == nullptr
|| in2 == nullptr
|| result == nullptr
|| resultMaxSize <= 0)
return FALSE;
// result = in1 + in2
strcpy_s(result, resultMaxSize, in1);
strcat_s(result, resultMaxSize, in2);
// All right
return TRUE;
}
通过以下C#控制台应用程序代码成功调用它:
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace CSharpClient
{
class Program
{
[DllImport(
"NativeDll.dll",
CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool NativeFunction(
string in1,
string in2,
StringBuilder result,
int resultMaxSize);
static void Main(string[] args)
{
var result = new StringBuilder(200);
if (! NativeFunction("Hello", " world!", result, result.Capacity))
{
Console.WriteLine("Error.");
return;
}
Console.WriteLine(result.ToString());
}
}
}
答案 1 :(得分:2)
如果你只是使用COM Interop,你将为自己省去很多P / Invoke头痛。将该方法放在COM接口中并更改签名以遵循COM约定:
interface ISomeInterface : IUnknown
{
HRESULT NativeMethod([in] BSTR bstrParam1, [in] BSTR bstrParam2,
[out] BSTR* pbstrParam3, [out, retval] VARIANT_BOOL* pvbResult);
}
我将 char * 更改为 BSTR ,将 bool 更改为 VARIANT_BOOL ,因为这些是COM使用的类型字符串和bools分别。此外,所有COM方法都必须返回 HRESULT 。如果您想要“实际”返回值,则必须将其添加为最后一个 out 参数,并使用 retval 属性对其进行标记。
然后从C#项目添加对COM组件的引用,您将获得直观的C#签名,而不必猜测如何将C ++类型与C#类型匹配:
bool NativeMethod(string bstrParam1, string bstrParam2, out string pbstrParam3)
(这就是它在对象浏览器中的显示方式。)
答案 2 :(得分:1)
为什么要注意使用DLLImport使用.Net代码编组,如下所示
[DllImport(@"C:\TestLib.dll")]
public static extern void ProtectDocument(
out [MarshalAs(UnmanagedType.LPStr)]string validToDate);
然后您可以将该函数作为本地函数调用,如下所示
string x=string.empty;
ProtectDocument(out x);
答案 3 :(得分:0)
[DllImport("MyDll.dll", EntryPoint = "NativeMethod", CallingConvention = CallingConvention.Cdecl)]
static extern bool NativeMethod(
[MarshalAs(UnmanagedType.LPStr)]string param1,
[MarshalAs(UnmanagedType.LPStr)]string param2,
[MarshalAs(UnmanagedType.LPStr)]string param3);
如果您正在使用宽字符,请将LPStr
替换为LPWStr
。
答案 4 :(得分:0)
[DllImport("MyLibrary.dll", EntryPoint = "NativeMethod")]
public static unsafe extern bool NativeMethod(
[MarshalAs(UnmanagedType.LPStr)] string param1,
[MarshalAs(UnmanagedType.LPStr)] string param2,
[MarshalAs(UnmanagedType.LPStr)] char *param3);
输出参数必须是char *
,因为C#字符串是不可变的。您可以像这样调用方法(在不安全的上下文中):
char[] output = new char[100];
fixed (char *param = &output[0])
{
NativeMethod("blahblah", "blahblah", param);
}
除非输出参数不是字符串而只是单个字符,否则你可以这样做:
[DllImport("MyLibrary.dll", EntryPoint = "NativeMethod")]
public static unsafe extern bool NativeMethod(
[MarshalAs(UnmanagedType.LPStr)] string param1,
[MarshalAs(UnmanagedType.LPStr)] string param2,
out char param3);
你可以像这样使用它:
char output;
NativeMethod("blahblah", "blahblah", out output);