您的位置:首页 > 编程语言 > C#

C#调用C++代码(CSharp Platform Invoke)

2017-08-01 08:59 246 查看
在.Net开发的过程中,有时候我们需要从C#中调用C++开发的代码,原因之一就是集成第三方的程序库(C++)写的,另外一个原因就是为了速度,将一些功能在C或C++里面实现。C#调用C++的功能有两种方法,一是用C++/CLI进行封装,其二就是使用.Net的Platform Invoke。本文只讨论第二种情况。C++代码的DLL也分好几种情况,

1、DLL使用C++编写,没有Export函数

2、DLL使用C++编写,有Export函数

3、DLL有C导出函数

期中第3种情况最简单了,直接使用DllImport导入函数就可以,第1、2种情况必须转换为第3种情况来进行处理。

1、准备C++的DLL,没有Export。

HelloWorld.h文件

#pragma once

class HelloWorld

{

public:
HelloWorld();
~HelloWorld();

public:
char* say(const char* name);

private:

};

HelloWorld.cpp文件

#include "stdafx.h"

#include "HelloWorld.h"

#include <string.h>

HelloWorld::HelloWorld()

{

}

HelloWorld::~HelloWorld()

{

}

const char * HelloWorld::say(const char * name)

{
const char* field = "你好,";

char* hello = new char[  strlen(field) + strlen(name) + 1];
char* header = hello;

while (*field != ('\0'))
{
*hello++ = *field++;
}

while (*name != ('\0'))
{
*hello++ = *name++;
}

*hello = ('\0');

return header;

}

然后编译为DLL文件,通过Dependency Walker查看,没有任何的导出函数。



导出C++类。

HelloWorldProxy.h

#pragma once

#include "HelloWorld.h"

#ifdef TESTHELLOWORLD_EXPORTS

#define API_EXPORT __declspec(dllexport)

#else

#define API_EXPORT __declspec(dllimport)

#endif

class API_EXPORT HelloWorldProxy

{

public:
HelloWorldProxy();
~HelloWorldProxy();

public:

char* say(const char* name);

private:
HelloWorld* m_helloWorld;

};

HelloWorldProxy.cpp

#include "stdafx.h"

#include "HelloWorldProxy.h"

HelloWorldProxy::HelloWorldProxy()

{
m_helloWorld = new HelloWorld();

}

HelloWorldProxy::~HelloWorldProxy()

{
delete m_helloWorld;
m_helloWorld = NULL;

}

char * HelloWorldProxy::say(const char * name)

{
return m_helloWorld->say(name);

}

然后编译为DLL文件,通过Dependency Walker查看,C++风格的导出函数。



2、C#访问C++函数的声明,通过DllImport来导入函数

    public static class HelloWorld

    {

        // [return:MarshalAs(UnmanagedType.LPStr)]

        // [MarshalAs(UnmanagedType.LPStr)]

        

        private const string LibraryName = "testHelloWorld";

        [DllImport(LibraryName, EntryPoint = "??0HelloWorldProxy@@QEAA@XZ")]

        public static extern IntPtr Create();

        [DllImport(LibraryName, EntryPoint = "?say@HelloWorldProxy@@QEAAPEBDPEBD@Z")]

        [return: MarshalAs(UnmanagedType.LPStr)]

        public static extern string SayCpp([MarshalAs(UnmanagedType.LPStr)] string name);

    }

备注:这里导入的是C++的函数,

    class Program

    {

        static void Main(string[] args)

        {

            // 创建C++对象,这个没有问题

            IntPtr ptr = HelloWorld.Create();

            // 调用C++对象里面的方法,这个是有问题的,因为系统不知道你调用的那个对象。

            string hello = HelloWorld.SayCpp("jacky");

            Console.WriteLine(hello);

        }

    }

3、转换为C风格的函数

文件ApiMain.h

#pragma once

#include "HelloWorldProxy.h"

#ifdef  __cplusplus

extern "C" {

#endif //  __cplusplus

API_EXPORT char* say(const char *name);
API_EXPORT void say2(char* buffer, int size, const char* name);
API_EXPORT int Add(int a, int b);

#ifdef __cplusplus

}

#endif // __cplusplus

文件ApiMain.cpp

#include "stdafx.h"

#include "ApiMain.h"

void WriteLog(const char* content) {
FILE *file = fopen("testHelloWorld.txt", "a");
fputs(content, file);
fputc('\n', file);
fclose(file);

}

char* say(const char* name) {
HelloWorldProxy *hello = new HelloWorldProxy();
const char *result = hello->say(name);

delete hello;
hello = NULL;

return result;

}

void say2(char* buffer, int size, const char* name) {

WriteLog("say2 begin");

HelloWorldProxy *hello = new HelloWorldProxy();
char *result = hello->say(name);

strcpy_s(buffer, size, result);

WriteLog("say2 end");

}

int Add(int a, int b) {
return a + b;

}

重新编译成testHelloWorld.dll文件,通过dependency walker看到的如下:



4、使用C风格的导出函数

        // say函数,直接转换为string或者StringBuilder不可用。

        [DllImport(LibraryName, EntryPoint = "say", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]

        public static extern StringBuilder SayC(string name);

        // say函数,返回值转换为IntPtr,可以正常调用,然需要在代码中将指针转换为字符串。

        [DllImport(LibraryName, EntryPoint = "say", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]

        public static extern IntPtr SayC2(string name);

        // say2是say函数的变形,原来say的返回值通过参数传递。

        [DllImport(LibraryName, EntryPoint = "say2", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]

        public static extern void say2([MarshalAs(UnmanagedType.LPStr)]StringBuilder buffer, int size, string name);

        // 纯C方法。

        [DllImport(LibraryName, EntryPoint = "Add", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]

        public static extern int Add(int a, int b);

5、在C#中使用导入的函数。

            StringBuilder sb = new StringBuilder(250);

            HelloWorld.say2(sb, sb.Capacity, "jacky, ok");

            Console.WriteLine("sb = " + sb.ToString());

            sb = new StringBuilder(250);

            HelloWorld.say2(sb,
a53b
sb.Capacity, "祖国");

            Console.WriteLine("sb = " + sb.ToString());

            int sum = HelloWorld.Add(20, 10);

            Console.WriteLine("sum = " + sum.ToString());

            IntPtr ptr = HelloWorld.SayC2("纽约交易所。");

            if(ptr == IntPtr.Zero)

            {

                Console.WriteLine("错误的数据。");

            }

            else

            {

                string aa = Marshal.PtrToStringAnsi(ptr);

                Console.WriteLine("IntPtr里面保存的字符串:" + aa);

            }

            

            // 下面的调用会报错,不能正常使用。

            sb = HelloWorld.SayC("jacky");

            Console.WriteLine(sb.ToString());
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: