8

我编写了一个 C++ DLL,现在我需要从托管应用程序调用本机函数。

导出的原生函数如下所示:

extern "C" __declspec(dllexport) 
bool NativeMethod(char *param1, char *param2, char *result);

因此,从 C# 中,我将调用传递 2 个输入参数、1 个输出参数的函数,显然我将读取返回的 bool 值。

我试图以多种方式包装所有这些,但总是遇到PInvokeStackImbalance异常。我知道调用本机函数的唯一方法是CallingConvention = CallingConvention.Cdecl在 .NET 函数声明上应用 )。但是,通过这种方式,我无法读取输出参数(始终为空字符串),并且返回值始终为真。

4

5 回答 5

16

首先,我会调整你的原生函数的原型。

由于这个函数有一个C 接口,你应该使用 C 类型的布尔值,而不是像bool. 您可能想使用 Win32 的BOOL类型。

此外,就目前而言,您的函数容易出现缓冲区溢出:最好添加另一个参数来指定目标result字符串缓冲区的最大大小。

另请注意,导出纯 C 接口函数(如许多 Win32 API 函数)的 DLL 的广泛调用约定__stdcall(not __cdecl)。我也会用那个。

最后,由于前两个参数是输入字符串,您可能希望使用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使用了 a。

另请注意,CharSet = CharSet.Ansi它用于将 C# 的 Unicode UTF-16 字符串编组为 ANSI(请注意转换是有损的- 如果您想要无损转换,也只需wchar_t*在 C++ 端使用字符串)。

我用一个简单的 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());
        }
    }
}
于 2013-02-03T17:01:55.770 回答
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 分别用于字符串和布尔值的类型。此外,所有 COM 方法都必须返回HRESULT。如果您想要一个“实际”返回值,您必须将其添加为最后一个输出参数,并使用retval属性对其进行标记。

然后从 C# 项目中添加对 COM 组件的引用,您将获得直观的 C​​# 签名,而无需猜测如何将 C++ 类型与 C# 类型匹配:

bool NativeMethod(string bstrParam1, string bstrParam2, out string pbstrParam3)

(这就是它在对象浏览器中的显示方式。)

于 2013-02-03T14:36:17.957 回答
1

为什么注意使用 .Net 代码编组使用 DLLImport 如下

[DllImport(@"C:\TestLib.dll")]
        public static extern void ProtectDocument(
            out [MarshalAs(UnmanagedType.LPStr)]string validToDate);

然后您可以将该函数调用为本地函数,如下所示

string x=string.empty;
ProtectDocument(out x); 
于 2013-02-03T13:41:43.417 回答
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

于 2013-02-03T13:58:46.013 回答
-1
[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);

输出参数必须是 a 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);
于 2013-02-03T13:59:17.567 回答