为C#包装本机DLL

时间:2013-02-03 13:36:23

标签: c# c++ string pinvoke

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

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

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

所以,从C#我将调用该函数传递2个输入参数,1个输出参数,显然我将读取返回bool值。

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

5 个答案:

答案 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);