C#调用C++DLL(x64)

沈某的第一篇文章

  • C#调用C++Dll
    • 建立一个C++Dll项目
    • 编码.h头文件
    • 编码.cpp文件
    • 编写.def文件
    • 建立一个用于测试的C#项目
    • C#编码
    • 测试
    • 后记

C#调用C++Dll

由于C#的反编译太过容易,所以之前自己客户端里调用的加密Dll被反编译之后,可以很容易的看到一些比较重要的信息,所以这次制作一个C++的Dll,调用C++的加密库Crypto++并且把密钥写在自己的C++Dll中,C#程序直接调用,这样可以把密钥隐藏起来。(但是有什么用呢?反正别人可以直接拷贝走C++Dll,这样不知道密钥只要知道输入输出就能解密,不得不说之前用C#制作这种需要安全性的软件简直就是一个大坑。但是在这里我们先假装它有用吧。)

建立一个C++Dll项目

本文使用的是window10系统下的VS2015,生产环境则是win7 x64的机器。

  1. 文件->新建->项目 选择VC++模板,选择Win32项目,命名为MyDLL点击确定;C#调用C++DLL(x64)_第1张图片
  2. 点确定,然后直接下一步,选择DLL,附加选项选择空项目,点击确定(可以考虑不勾选安全开发生命周期检测,这样可以直接使用一些类似getchar()之类的函数。)。C#调用C++DLL(x64)_第2张图片
  3. 在右侧解决方案资源管理器中右键头文件,新增头文件“MyDLL.h”;
  4. 同样的,在源文件中新增源文件“MyDLL.cpp”和“MyDLL.def”;
  5. 到这里基本上所需要的文件都写好了,接下来会说明这些文件是干嘛的;

编码.h头文件

#pragma once
#include
using namespace std;
extern "C" __declspec(dllexport) int EncryptStr(char* plainText);
extern "C" __declspec(dllexport) int DecryptStr(char* plainText);

在.h头文件中申明需要导出的函数:EncryptStr和DecryptStr。
其中,extern "C"是为了解决命名问题,如果不加的话最后DLL的导出函数名字可能会和自己写的函数名不一致。__declspec(dllexport)是声明导出函数。当然也可以写成这样:

#pragma once
#include
using namespace std;
int EncryptStr(char* plainText);
int DecryptStr(char* plainText);

一会在.def(模块定义文件)中声明导出函数。
具体可以参考这篇博客,讲的比较详细。
这里比较值得注意的一点是我之前直接使用string类型的返回值,结果最后函数传递参数的时候不知道为什么出现了错误,可能是因为我使用了__stdcall关键字。

编码.cpp文件

#include "MyDLL.h"
#pragma comment(lib, "cryptlib.lib" )
#pragma warning( disable : 4996)
#include   
#include       // StreamTransformationFilter  
#include     // CFB_Mode  
#include    // std:cerr    
#include    // std::stringstream    
#include   

using namespace std;
using namespace CryptoPP;

std::string ECB_AESEncryptStr(std::string sKey, const char *plainText)
{
	std::string outstr;

	//填key    
	SecByteBlock key(AES::MAX_KEYLENGTH);
	memset(key, 0x30, key.size());
	sKey.size() <= AES::MAX_KEYLENGTH ? memcpy(key, sKey.c_str(), sKey.size()) : memcpy(key, sKey.c_str(), AES::MAX_KEYLENGTH);


	AES::Encryption aesEncryption((byte *)key, AES::MAX_KEYLENGTH);

	ECB_Mode_ExternalCipher::Encryption ecbEncryption(aesEncryption);
	StreamTransformationFilter ecbEncryptor(ecbEncryption, new HexEncoder(new StringSink(outstr)));
	ecbEncryptor.Put((byte *)plainText, strlen(plainText));
	ecbEncryptor.MessageEnd();

	return outstr;
}

std::string ECB_AESDecryptStr(std::string sKey, const char *cipherText)
{
	std::string outstr;

	//填key    
	SecByteBlock key(AES::MAX_KEYLENGTH);
	memset(key, 0x30, key.size());
	sKey.size() <= AES::MAX_KEYLENGTH ? memcpy(key, sKey.c_str(), sKey.size()) : memcpy(key, sKey.c_str(), AES::MAX_KEYLENGTH);

	ECB_Mode<AES >::Decryption ecbDecryption((byte *)key, AES::MAX_KEYLENGTH);

	HexDecoder decryptor(new StreamTransformationFilter(ecbDecryption, new StringSink(outstr)));
	decryptor.Put((byte *)cipherText, strlen(cipherText));
	decryptor.MessageEnd();

	return outstr;
}

int EncryptStr(char*  plainText)
{
	string s = ECB_AESEncryptStr("1234", plainText);
	strcpy(plainText, s.data());
	return 1;
}

int DecryptStr(char*  plainText)
{
	string s = ECB_AESDecryptStr("1234", plainText);
	strcpy(plainText, s.data());
	return 1;
}

在这里写了具体的函数和一些需要用的内容。如何调用Crypto++可以参考这篇文章。
值得注意的点是,在配置属性页的时候一定要注意上面的平台。C#调用C++DLL(x64)_第3张图片
可以自行在右侧的配置管理器中进行配置。

编写.def文件

LIBRARY "MyDLL"
EXPORTS
EncryptStr @1
DecryptStr @2

这里同样声明了导出函数,最前面的LIBRARY是自己的库名,后面的EncryptStr @1是指第一个函数导出为EncryptStr,当然也可以不写@1,会按照默认顺序进行导出函数。

建立一个用于测试的C#项目

C#调用C++DLL(x64)_第4张图片
右键解决方案,然后新建一个C#控制台程序,这边就不再赘述了。

C#编码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace MyDLLTest
{
    class Program
    {
        [DllImport("MyDLL.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int EncryptStr(StringBuilder a);
        [DllImport("MyDLL.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int DecryptStr(StringBuilder a);
        static void Main(string[] args)
        {
            int outdata;
            StringBuilder str1 = new StringBuilder(1024);
            str1.Append("test");
            StringBuilder str2 = new StringBuilder(1024);
            outdata = EncryptStr(str1);
            Console.WriteLine(str1.ToString());
            str2.Append(str1.ToString());
            outdata = DecryptStr(str2);
            Console.WriteLine(str2.ToString());
            Console.Read();
        }
    }
}

使用DllImport引用DLL文件,值得注意的是如果要往C++的语言中传入char*,则在C#中需要使用StringBuilder。

测试

  1. 首先右键MyDLLTest,属性->设置为启动项目,然后再点击上方的配置管理器,将其配置为Debug x64位的程序。C#调用C++DLL(x64)_第5张图片
    之后右键生成。
  2. 右键之前创建的MyDLL项目(C++)那个,属性->配置属性->常规->输出目录选择为同项目下MyDLLTest里面的x64/Debug目录,这样之后生成的DLL就会直接出现在该目录下,省的复制(当然也可以在C#中引用的时候采用绝对路径)。配置完之后,右键生成。
  3. 最后需要说明的是在DLL中,如果生成的是x64Debug的DLL文件那么调用的时候也需要是x64Debug的程序调用,同样的,如果是x64Release项目则需要调用的是x64Release的DLL。
  4. 最后直接启动C#项目。C#调用C++DLL(x64)_第6张图片

后记

虽然感觉C#通过调用C++来实现加密解密并没有什么卵用,因为C#自己就带有一个非常好用的加密解密库,所以感觉这篇文章有点多此一举的感觉。
但是话又说回来,在制作C++DLL的时候还是有非常多的坑的,这篇文章主要说明了几个我之前卡了很久的点,包括一开始制作了一个AnyCPU的结果一到x64程序里就报错了(因为我的项目是x64的),还包括C#和C++之间相互传递字符串,最后选择了传入char*,返回string,之前传入传出都使用string的话不知道为什么会读不到字符串。
总结一下,还是有非常多的问题没有仔细研究,希望之后再学习之中会慢慢的明白其中的原理。
下一篇文章应该是JAVA后台和C#程序建立一个长连接。

你可能感兴趣的:(C#调用C++DLL(x64))