在.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, 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());