由于C#存在易反编译的弊端,虽然有一些工具和方法,混淆,重命名,加密,但是不是很方便,有些情况下混淆后不能成功运行了。很多人都想把核心的代码封装成C++的DLL就没那么简单被反编译了,当然没有绝对的。我个人认为这是比较好的方法 把最核心的功能封装在c++dll中然后用C#做界面或其他。
下面是一篇简单介绍C#如何调用C++DLL
C#语言使用方便,入门门槛较代,上手容易,并且语法与C,java有很类似的地方,IDE做的也好,通用性好,是MS下一代开发的主要力量.但是其开源代码较少,类库不是十分完美,在架构方面还有一些需要做的工作.
C++写的程序占用内存较小,直接对内存或者文件操作,因此一些关键的步骤或者一些最内层的循环在一定程序上还需要依赖C++.
下面我做一些简单的例子
第一步,用C++做一个可以导出函数的dll(不采用def文件)
cxyMath.h
//在这里定义导出哪一些函数
{
public:
// Returns a + b
static __declspec(dllexport)double Add(double a,double b);
// Returns a - b
static __declspec(dllexport)double Subtract(double a,double b);
// Returns a * b
static __declspec(dllexport)double Multiply(double a,double b);
// Returns a / b
// Throws DivideByZeroException if b is 0
static __declspec(dllexport)double Divide(double a,double b);
} ;
cxyMath.cpp的实现就很简单了,代码附在上传的文件中,在这里就不贴代码了,编译成dll后,拷贝dll,lib文件到C#的工程中的debug的目录下(如果你写的是release版,请将dll,lib拷贝到relase文件夹下)
第二步:找出导出的函数名
写成如下形式,方便CS的调用
不采用def文件导出的函数名有些奇怪,但还是可以看出函数的层次,?函数名@类名@命名空间@@******,
找函数名可以使用ultraedit32,打开lib文件,就可看到了
另外,我们可以使用dllexp这个程序找出导出的函数(这个程序见附录)
public static extern double Divide( double a, double b);
[DllImport( " cppdll.dll " ,EntryPoint = " ?Multiply@MyMathFuncs@MathFuncs@@SANNN@Z " ,CharSet = CharSet.Auto)]
public static extern double Multiply( double a, double b);
第三步,调用
{
MessageBox.Show(Multiply(12,13).ToString());
}
采用def 文件导出函数
第一种方式比较简单,但是找一个dll函数的入口地址,还是比较麻烦的,并且,入口地址没有太大的意义,不直观,不好记忆
一般情况下,我们可以选择使用def文件导出函数
第一步,新建一个win32 application然后在应用程序的设置中选择动态dll,然后选择导出符号,这样,vs2003就为我们生成了一个非常完整的架子,但是美中不足的是生成的dll导出的函数也是和第一中情况一样
第二步,添加一个def文件,生成def文件的同时,vs2003自动为我们添加了这样一行,
LIBRARY win32dll
我们只要在他的下面加上我们要导出的函数就可以了.
GetAName @1
ShowMyName @2
PerfTest @3
这样经过编译我们使用dllexp查看,看到的就不再是一些没有意义的函数名了,而是我们在def中定义的文件函数名
第三步,拷贝lib,dll文件到CS工程中就可以了,
我们就不在这里一一叙述了
刚才我们写的dll同样也可以为C++的工程调用
第一步:新建一个console的C++ application
第二步:添加引用,引用C++ dll application
第三步:拷贝c++ dll的.h文件到console app的目录下,并添加到console app中
第四步:#include "cxyMath.h"
调用就可以了
详情见代码
C#调用C++生成的dll例子,文件中共包含四个工程文件:
cppdll是不使用def文件导出dll函数的示例
win32dll是使用def文件导出dll函数的示例
CsApp是C#一个工程文件,用来调用C++的dll
dlltest是C++调用C++dll的一个示例
代码:http://dl2.csdn.net/down4/20070725/25180037196.rar
dllexp:查看dll导出的一个小程序
http://www.nirsoft.net/utils/dll_export_viewer.html
看了这篇文章后,就知道如何去调用了,但还有一些问题,比如C#的一些类型和C++的类型是不同的
比如C++中的string和C#中的string是不一样的
C#调用dll时的类型转换总结
| C# |
char** | 作为输入参数转为char[],通过Encoding类对这个string[]进行编码后得到的一个char[] |
作为输出参数转为byte[],通过Encoding类对这个byte[]进行解码,得到字符串 | |
C++ Dll接口: void CplusplusToCsharp(in char** AgentID, out char** AgentIP); C#中的声明: [DllImport("Example.dll")] public static extern void CplusplusToCsharp(char[] AgentID, byte[] AgentIP); C#中的调用: Encoding encode = Encoding.Default; byte[] tAgentID; byte[] tAgentIP; string[] AgentIP; tAgentID = new byte[100]; tAgentIP = new byte[100]; CplusplusToCsharp(encode.GetChars(tAgentID), tAgentIP); AgentIP[i] = encode.GetString(tAgentIP,i*Length,Length); | |
Handle | IntPtr |
Hwnd | IntPtr |
int* | ref int |
int& | ref int |
void* | IntPtr |
unsigned char* | ref byte |
BOOL | bool |
DWORD | int 或 uint(int 更常用一些) |
枚举类型 | Win32: BOOL MessageBeep(UINT uType // 声音类型); 其中的声音类型为枚举类型中的某一值。 C#: 用户需要自己定义一个枚举类型: public enum BeepType { SimpleBeep = -1, IconAsterisk = 0x00000040, IconExclamation = 0x00000030, IconHand = 0x00000010, IconQuestion = 0x00000020, Ok = 0x00000000, } C#中导入该函数: [DllImport("user32.dll")] public static extern bool MessageBeep(BeepType beepType); C#中调用该函数: MessageBeep(BeepType.IconQuestion); |
结构类型 | Win32: 使用结构指针作为参数的函数: BOOL GetSystemPowerStatus( LPSYSTEM_POWER_STATUS lpSystemPowerStatus ); Win32中该结构体的定义: typedef struct _SYSTEM_POWER_STATUS { BYTE ACLineStatus; BYTE BatteryFlag; BYTE BatteryLifePercent; BYTE Reserved1; DWORD BatteryLifeTime; DWORD BatteryFullLifeTime; } SYSTEM_POWER_STATUS, *LPSYSTEM_POWER_STATUS; C#: 用户自定义相应的结构体: struct SystemPowerStatus { byte ACLineStatus; byte batteryFlag; byte batteryLifePercent; byte reserved1; int batteryLifeTime; int batteryFullLifeTime; } C#中导入该函数: [DllImport("kernel32.dll")] public static extern bool GetSystemPowerStatus( ref SystemPowerStatus systemPowerStatus); C#中调用该函数: SystemPowerStatus sps; ….sps初始化赋值…… GetSystemPowerStatus(ref sps); |
字符串 | 对于字符串的处理分为以下几种情况: 1、 字符串常量指针的处理(LPCTSTR),也适应于字符串常量的处理,.net中的string类型是不可变的类型。 2、 字符串缓冲区的处理(char*),即对于变长字符串的处理,.net中StringBuilder可用作缓冲区 Win32: BOOL GetFile(LPCTSTR lpRootPathName); C#: 函数声明: [DllImport("kernel32.dll", CharSet = CharSet.Auto)] static extern bool GetFile ( [MarshalAs(UnmanagedType.LPTStr)] string rootPathName); 函数调用: string pathname; GetFile(pathname); 备注: DllImport中的CharSet是为了说明自动地调用该函数相关的Ansi版本或者Unicode版本
变长字符串处理: C#: 函数声明: [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public static extern int GetShortPathName( [MarshalAs(UnmanagedType.LPTStr)] string path, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder shortPath, int shortPathLength); 函数调用: StringBuilder shortPath = new StringBuilder(80); int result = GetShortPathName( @"d:/test.jpg", shortPath, shortPath.Capacity); string s = shortPath.ToString(); |
struct | 具有内嵌字符数组的结构: Win32: typedef struct _TIME_ZONE_INFORMATION { LONG Bias; WCHAR StandardName[ 32 ]; SYSTEMTIME StandardDate; LONG StandardBias; WCHAR DaylightName[ 32 ]; SYSTEMTIME DaylightDate; LONG DaylightBias; } TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION; C#: [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] struct TimeZoneInformation { public int bias; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string standardName; SystemTime standardDate; public int standardBias; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string daylightName; SystemTime daylightDate; public int daylightBias; } |
具有回调的函数 | Win32: BOOL EnumDesktops( HWINSTA hwinsta, // 窗口实例的句柄 DESKTOPENUMPROC lpEnumFunc, // 回调函数 LPARAM lParam // 用于回调函数的值 ); 回调函数DESKTOPENUMPROC的声明: BOOL CALLBACK EnumDesktopProc( LPTSTR lpszDesktop, // 桌面名称 LPARAM lParam // 用户定义的值 ); C#: 将回调函数的声明转化为委托: delegate bool EnumDesktopProc( [MarshalAs(UnmanagedType.LPTStr)] string desktopName, int lParam); 该函数在C#中的声明: [DllImport("user32.dll", CharSet = CharSet.Auto)] |
该表对C#中调用win32函数,以及c++编写的dll时参数及返回值的转换做了一个小的总结,如果想进一步了解这方面内容的话,可以参照msdn中“互操作封送处理”一节。
转自http://blog.csdn.net/xiaochongchong1248/archive/2010/01/13/5181345.aspx
这里具体讲一下常用的字符串吧
C#调用C++动态库中如何传递字符串/char转CString/string转char/string转CString
我们在C#的框架中实现实时要求很强的功能时,通常会调用C++编写的动态链接库,常常我们会希望能将字符串传递给C#搭建的框架,譬如很典型的,我们想传递一个文件名,C#中没有CString类,咋办。这时我们可以借助char[] XXX作为中间媒介。下面举个例子:
C#框架中:
[DllImport("TestDll.dll"(可以是你自己的任意名字), CharSet = CharSet.Ansi)]
static extern bool Function(char[] fileName);//Function(任意被调用函数名)
OpenFileDialog openFileDialog1 = new OpenFileDialog();
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
Function(openFileDialog1.FileName.ToCharArray());
}
C++编写的库中:
DLL1_API bool Function(char* fileName)//DLL1_API为任意你定义的名字
{
CString DllfileName;
DllfileName=(char*)fileName;
FunctionDLLOpenFile(DllfileName);//FunctionDLL为任意功能函数
return true;
}
因为Ansi码表示字符串结尾都是0('/n'),CString类可以自动找到filename后的第一个0值。
本篇转自http://blog.csdn.net/soul4past/archive/2009/05/07/4158368.aspx
但还有问题,这样的方法作为返回值时还存在问题,返回值的解决方案是利用StringBuild。另外我们能不能通过参数传递值呢?
C#调用C++的DLL时,参数传递便成了一个问题。今天我碰到的一个问题是,在C++中导出的函数的参数是string类型的,在C#中通过string的参数调用时,便会出现该内存已损坏或不能读取的异常信息。后来我把C++的导出函数的参数由string改为LPTSTR类型,也即char*类型,然后在C#中对应的参数改为StringBuilder类型,既解决了传进去的参数问题,又解决了传出参数的问题。
另外对于传结构体参数时一定要对应好每个成员的顺序,否则相应的值存在错乱。