繁体   English   中英

如何使用PInvoke从C#导入C ++ dll?

[英]How to import C++ dll from c# using PInvoke?

C ++ DLL中导出函数的定义是

int func1(const char* input,char** output)
{
 int returnCode = 0;

 std::stringstream xmlInputStream;
 xmlInputStream<< std::string(input);
 std::stringstream xmlOutputStream;
 returnCode = doRequest(xmlInputStream,xmlOutputStream);
 std::string xmlOutputString =xmlOutputStream.str();
 *output=const_cast<char *> (xmlOutputString.c_str());

cout<<xmlOutputString;

 return returnCode ;
}

我试图像这样从c#导入函数...

==================================================================

[DllImport("sample.dll")]
    public static extern int func1(String str1,out String str2);


string str1="hello";
string str2=String.Empty;

MyClass.func1(str1,out str2);
Console.writeln(str2);

====================================================================

输出是垃圾值...

为什么会这样,以及如何从c#导入此函数?

就是这么简单,请尝试使用自定义封送处理(尤其是如果您要使用char **指针)。

我发现由于doRequest(char *,char **)的某些不正确实现以及对结果的赋值处理不当,您没有得到“ Memory ass dumb”字符串。

首先,如果要在非托管进程中分配内存并将其传递给托管进程,则需要声明某种机制以释放非托管内存。 托管GC不了解有关此内存的任何信息,这些内存会因丢失而丢失。

其次,您需要为结果分配内存,并将它们传递给非托管进程,因为原始内存位置可以随时重写。

最后,仅由于未为结果分配内存而仅获得输入的第一个字符,实际上仅将内存指针传递到第一个输出字符的内存位置,例如&array [0]

这是竞争代码(MS VC ++ / MS C#),它解决了所有问题:

lib.cpp:

#include "stdafx.h"
#include "lib.h"
using namespace std;

int func1(char* input, char** output)
{
    stringstream xmlInputStream, xmlOutputStream;

    xmlInputStream << string(input);    
    int returnCode = doRequest(&xmlInputStream, &xmlOutputStream);
    string xmlOutputString = xmlOutputStream.str();

    //*output=const_cast<char *> (xmlOutputString.c_str());
    long length = sizeof(char) * xmlOutputString.length();
    char* src = const_cast<char *>(xmlOutputString.c_str());
    char* dst = (char*)malloc(length+1);
    memcpy_s(dst, length, src, length);
    dst[length]=0; // 0 byte always ends up given ANSI string
    *output = dst;

    //cout << xmlOutputString;
    return returnCode;
}

int func1_cleanup(char* memptr)
{
    free(memptr);
    return 0;
}


int doRequest(stringstream* xmlInputStream, stringstream* xmlOutputStream)
{
    *xmlOutputStream << "Memory ass dumb";
    return 0;
}

Program.cs中:

using System;
using System.Runtime.InteropServices;
namespace test
{
    class Program
    {
        [DllImport("lib.dll", EntryPoint = "func1", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern unsafe int func1(char* input, char** data);

        [DllImport("lib.dll", EntryPoint = "func1_cleanup", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern unsafe int func1_cleanup(char* data);

        static void Main(string[] args)
        {
            string input = "Hello, World!";
            string output;
            int result = func1(input, out output);
        }

        private const int S_OK = 0;

        public static int func1(string input, out string output)
        {
            unsafe
            {
                output = null;
                int result = -1;
                fixed (char* parray1 = &input.ToCharArray()[0])
                {
                    //
                    // if you allocating memory in a managed process, you can use this
                    //
                    //char[] array = new char[0xffffff];
                    //fixed(char* parray = &array[0])
                    {
                        //
                        // if you allocating memory in unmanaged process do not forget to cleanup the prevously allocated resources
                        //
                        char* array = (char*)0; 
                        char** parray2 = &array;
                        result = func1(parray1, parray2);
                        if (result == S_OK)
                        {
                            //
                            // if your C++ code returns the ANSI string, you can skip this extraction code block (it can be useful in Unicode, UTF-8, UTF-7, UTF-32, all C# supported encodings)
                            //
                            //byte* self = (byte*)*((int*)parray2);
                            //byte* ptr = self;
                            //List<byte> bytes = new List<byte>();
                            //do
                            //{
                            //    bytes.Add(*ptr++);
                            //}
                            //while (*ptr != (byte)0);
                            //output = Encoding.ASCII.GetString(bytes.ToArray());
                            output = Marshal.PtrToStringAnsi(new IntPtr(*parray2));
                        }
                        func1_cleanup(array);
                    }
                }
                return result;
            }
        }
    }
}

我相信这应该有用

[DllImport("sample.dll")]
public static extern int func1(
    [MarhsalAs(UnmanagedType.LPStr)] String str1,
    [MarshalAs(UnmanagedType.LPStr)] out String str2);

MarshalAs属性对于控制互操作非常强大。

编辑 (基于带有C ++代码的更新帖子):

也许更像这样的东西应该可以工作(我在这台计算机上没有VC ++来构建伪造的C ++ DLL进行检查)

[DllImport("sample.dll")]
public static extern int func1(
    [MarshalAs(UnmanagedType.LPStr)] String str1,
    [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStr, SizeConst=1), Out] out String[] str2);

注意,我在str2中添加了[Out],我不确定这是好还是必要,因此请尝试使用或不使用它(再次,抱歉,我不能在这里尝试)。 另外,您可能需要使用SizeParamIndex而不是SizeConst,我不记得了-如果它需要是SizeParamIndex,则需要更改C ++代码以将1作为数组元素的数目传回。

这作为字符串[]返回str2,但是您可以仅使用该数组的第一个元素来获取字符串。

也可能有更干净的方法可以做到这一点-C ++中的__deref_out参数不能很好地转换为我记得的C#,但是我可能在这里忘记了一些奥秘。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM