1.创建C#工程
1.创建C# Windows窗体应用
2.创建项目
输入工程名,选择存放位置
选择.NET Core 3.1框架,点击创建即工程创建完成
2.搭建框架
创建完工程后,双击Form1.cs,打开Form窗体,在左侧出现的工具栏里面找到公共控件
若无工具箱,则点击视图中工具箱即可
双击工具栏中控件或者拖动控件到Form窗体,并对控件进行配置,如单击Form窗体中Label控件,输入name及text,再双击Form窗体中的Label控件图标,即可进入控件代码界面
将控件在Form窗体中布局好
3. 功能实现
1.dll文件调用
调试时出现如下错误,参考这篇 博文 解决
未处理System.BadImageFormatException,试图加载格式不正确的程序。 (异常来自 HRESULT:0x8007000B)
删除dll文件时显示没有权限访问文件(这里改了四次,最后才想到真正的原因,泪目<_>,这里也做个记录吧)
:“Access to the path 'F:\DLL\SeedToKey\SeedToKey\bin\Debug\netcoreapp3.1\GenerateKeyExImpl.dll' is denied.”
(第一次修改 )这里改成默认以管理员权限运行,参考博文.
[STAThread]
static void Main()
{
/*
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new SeedToKey());*/
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
/**
* 当前用户是管理员的时候,直接启动应用程序
* 如果不是管理员,则使用启动对象启动程序,以确保使用管理员身份运行
*/
//获得当前登录的Windows用户标示
System.Security.Principal.WindowsIdentity identity = System.Security.Principal.WindowsIdentity.GetCurrent();
System.Security.Principal.WindowsPrincipal principal = new System.Security.Principal.WindowsPrincipal(identity);
//判断当前登录用户是否为管理员
if (principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator))
{
//如果是管理员,则直接运行
Application.Run(new SeedToKey());
}
else
{
//创建启动对象
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
startInfo.UseShellExecute = true;
startInfo.WorkingDirectory = Environment.CurrentDirectory;
startInfo.FileName = Application.ExecutablePath;
//设置启动动作,确保以管理员身份运行
startInfo.Verb = "runas";
try
{
System.Diagnostics.Process.Start(startInfo);
}
catch
{
return;
}
//退出
Application.Exit();
}
}
(第二次修改)修改后发现还是会出现这种情况,排查后确定错误位置并修改,直接删除就OK(这里有点莫名自信了啊)
(第三次修改)后期发现都不是前面的原因,想了很久才想到是因为,点击计算后,就会开始调用dll文件里面的函数,dll文件被正在执行的程序占用,再次点击dll文件选择按钮调用同名文件,因文件占用不能进行删除或者覆盖,导致出错。
解决方法:在调用完后将dll文件释放,这里就需要将静态调用更改成动态调用。
public unsafe delegate VKeyGenResultEx calculateKey(byte[] ipSeedArray,
uint iSeedArraySize,
uint iSecurityLevel,
char* ipVariant,
char* ipOptions,
byte[] iopKeyArray,
uint iMaxKeyArraySize,
uint oActualKeyArraySize);
public static class NativeMethod
{
[DllImport("kernel32.dll", EntryPoint = "LoadLibrary")]
public static extern int LoadLibrary(
[MarshalAs(UnmanagedType.LPStr)] string lpLibFileName);
[DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
public static extern IntPtr GetProcAddress(int hModule,
[MarshalAs(UnmanagedType.LPStr)] string lpProcName);
[DllImport("kernel32.dll", EntryPoint = "FreeLibrary")]
public static extern bool FreeLibrary(int hModule);
}
点击计算button调用dll文件,在CalcuButton_Click() 函数中加载dll
//1. 动态加载C++ Dll
int hModule = NativeMethod.LoadLibrary(@".\dll\GenerateKeyExImpl.dll");
if (hModule == 0) return;
//2. 读取函数指针
IntPtr intPtr = NativeMethod.GetProcAddress(hModule, "GenerateKeyExOpt");
//3. 将函数指针封装成委托
calculateKey GenerateKeyExOptFun = (calculateKey)Marshal.GetDelegateForFunctionPointer(intPtr, typeof(calculateKey));
//4.调用函数
GenerateKeyExOptFun(GenerateKeyExOptPara.ipSeedArray, GenerateKeyExOptPara.iSeedArraySize,
GenerateKeyExOptPara.iSecurityLevel, null, null, GenerateKeyExOptPara.iopKeyArray,
GenerateKeyExOptPara.iMaxKeyArraySize, GenerateKeyExOptPara.oActualKeyArraySize);
调用完,将Dll文件释放。
其余参考博文:
C# 打开文件.
C# 拷贝文件.
C# 调用dll文件.
C# 动态调用dll文件.
C# 把dll放在不同的目录让你的程序更整洁.
C# 创建子文件夹文件.
C# 创建普通文件夹.
计划实现当前窗体关闭时,删除相应dll文件,感觉还是在选择dll文件时删除已经存在的dll文件可能更好,此功能没有实现,这里只是记录一下。
2.字节大小和安全等级可进行选择
可参考
combobox控件如何添加下拉内容.
C#使用 ComboBox 控件.
注意:
行为里的DrawMode为Normoal,选择其他需自己选择字体颜色,否则会跟背景一个颜色,看不见字
下拉选择控件实现放在窗体Load里面
private void SeedToKey_Load(object sender, EventArgs e)
{
_ = ByteSelect.Items.Add("2"); //对应ByteSelect.SelectedIndex == 0
_ = ByteSelect.Items.Add("4"); //对应ByteSelect.SelectedIndex == 1
_ = ByteSelect.SelectedIndex = 0; //默认的下拉框选项
_ = securityLevelComboBox.Items.Add(1);
_ = securityLevelComboBox.Items.Add(2);
_ = securityLevelComboBox.Items.Add(3);
_ = securityLevelComboBox.SelectedIndex = 0; //默认的下拉框选项
}
1.字节选择
private void ByteSelect_SelectedIndexChanged(object sender, EventArgs e)
{
/*
switch (ByteSelect.SelectedItem.ToString()) //获取选择的内容
{
case "2": MessageBox.Show("A"); break;
case "4": MessageBox.Show("B"); break;
}*/
if (ByteSelect.SelectedIndex == 0)
{
GenerateKeyExOptPara.iSeedArraySize = 2;
}
if (ByteSelect.SelectedIndex == 1)
{
GenerateKeyExOptPara.iSeedArraySize = 4;
}
ClearSeedAndKey(); //字节选择发生改变时,将Seed和Key清除
}
3.输入种子
private void InputSeed_TextChanged(object sender, EventArgs e)
{
if (GenerateKeyExOptPara.iSeedArraySize == 2)
{
InputSeed.MaxLength = 6; //字节为2时,最大只能输入6个(如:0xAAAA,2+2*2)
}
if (GenerateKeyExOptPara.iSeedArraySize == 4)
{
InputSeed.MaxLength = 10; //字节为4时,最大只能输入10个(如:0xAAAA,2+4*2)
}
//KeyValue.Text = InputSeed.ToString(); //会提取到输入框的地址加输入框中的数据,用下面方法可解决
//KeyValue.Text = InputSeed.ToString().Remove(0, 36);
//GenerateKeyExOptPara.ipSeedStr = InputSeed.ToString().Substring(InputSeed.ToString().Length - 8);
GenerateKeyExOptPara.ipSeedStr = InputSeed.ToString().Replace("System.Windows.Forms.TextBox, Text: ", "");
//KeyValue.Text = GenerateKeyExOptPara.ipSeedStr; //测试提取到的值
GenerateKeyExOptPara.ipSeedArray = Encoding.UTF8.GetBytes(GenerateKeyExOptPara.ipSeedStr);
GenerateKeyExOptPara.ipSeedArray = ASCALLTOHex(GenerateKeyExOptPara.ipSeedArray);
//加InputSeed.TextLength==6判断是如果输入满6才能进入,不加判断就会每输入一次进入一次
if ((GenerateKeyExOptPara.iSeedArraySize==2) && ((InputSeed.TextLength==4) || (InputSeed.TextLength==6)) && (GenerateKeyExOptPara.ipSeedArray.Length!=0))
{
if (InputSeed.TextLength == 6) //有0x,将0x覆盖掉
{
for (byte i = 0; i < 4; i++)
{
GenerateKeyExOptPara.ipSeedArray[i] = GenerateKeyExOptPara.ipSeedArray[i+2];
}
}
GenerateKeyExOptPara.ipSeedArray[0] = (byte)((GenerateKeyExOptPara.ipSeedArray[0] << 4) + GenerateKeyExOptPara.ipSeedArray[1]);
GenerateKeyExOptPara.ipSeedArray[1] = (byte)((GenerateKeyExOptPara.ipSeedArray[2] << 4) + GenerateKeyExOptPara.ipSeedArray[3]);
}
if ((GenerateKeyExOptPara.iSeedArraySize==4) && ((InputSeed.TextLength==8) || (InputSeed.TextLength==10)) && (GenerateKeyExOptPara.ipSeedArray.Length!=0))
{
if (InputSeed.TextLength == 10)
{
for (byte i = 0; i < 8; i++)
{
GenerateKeyExOptPara.ipSeedArray[i] = GenerateKeyExOptPara.ipSeedArray[i + 2];
}
}
GenerateKeyExOptPara.ipSeedArray[0] = (byte)((GenerateKeyExOptPara.ipSeedArray[0] << 4) + GenerateKeyExOptPara.ipSeedArray[1]);
GenerateKeyExOptPara.ipSeedArray[1] = (byte)((GenerateKeyExOptPara.ipSeedArray[2] << 4) + GenerateKeyExOptPara.ipSeedArray[3]);
GenerateKeyExOptPara.ipSeedArray[2] = (byte)((GenerateKeyExOptPara.ipSeedArray[4] << 4) + GenerateKeyExOptPara.ipSeedArray[5]);
GenerateKeyExOptPara.ipSeedArray[3] = (byte)((GenerateKeyExOptPara.ipSeedArray[6] << 4) + GenerateKeyExOptPara.ipSeedArray[7]);
}
}
注意:
1.由于InputSeed.ToString() 提取到的字符串前面有很多存放地址,所以需要提取出有用的信息
System.Windows.Forms.TextBox, Text: 12
使用Substring(str.length()-8) 即可提取从字符串从右向左8个字符,其它提取方法参考博文.但是在后期调试是发现输入不足8个时,会将前面信息显示出来,于是改用
GenerateKeyExOptPara.ipSeedStr = InputSeed.ToString().Replace("System.Windows.Forms.TextBox, Text: ", "");
提取输入的数据
2.combox输入的是字符型的数据,需要转换成十六进制或者十进制,调试需要对照ASCll码表.
3.调试过程中发现转换后的数据显示的是ASCLL码对应16进制,"a"的16进制为61,而不是想要的十六进制a,导致计算出错。并发现输入一个字符就会占据一个byte位。
4.自己写了一个转换函数 ASCALLTOHex() 将字符转换成16进制,转换后效果如下:
private byte[] ASCALLTOHex(byte[] ascallArr)
{
byte ascallArr0To9 = 0x30; //字符"0"的16进制
byte hexArr0To9 = 0x00; //16进制0
byte ascallArraTof = 0x41; //字符"a"的16进制
byte ascallArrAToF = 0x61; //字符"A"的16进制
byte hexArrAToF = 0x0a; //16进制a
for (byte i=0; i< ascallArr.Length; i++)
{
for (byte j = 0; j < 10; j++)
{
if (ascallArr[i] == ascallArr0To9++)
{
ascallArr[i] = hexArr0To9;
}
hexArr0To9++;
}
ascallArr0To9 = 0x30;
hexArr0To9 = 0x00;
for (byte j = 0; j < 6; j++)
{
if (ascallArr[i] == ascallArraTof++)
{
ascallArr[i] = hexArrAToF;
}
hexArrAToF++;
}
ascallArraTof = 0x41;
hexArrAToF = 0x0a;
for (byte j = 0; j < 6; j++)
{
if (ascallArr[i]==ascallArrAToF++)
{
ascallArr[i] = hexArrAToF;
}
hexArrAToF++;
}
ascallArrAToF = 0x61;
hexArrAToF = 0x0a;
}
return ascallArr;
}
我想要的数据是0xAAAA,而这里显然不是想要的结果,再将数据进行处理一下,处理过程中发现需要输入完全后再进行数据处理,不加判断条件,每输入一次会进来处理数据,如只输入一个A,进来处理数据就会出错,因目前数组只有一个GenerateKeyExOptPara.ipSeedArray[0]数据,而对数据处理包含对GenerateKeyExOptPara.ipSeedArray[1]等的处理,导致报错,后期实现输入数据是否匹配字节大小判断
//加InputSeed.TextLength==6判断是如果输入满6才能进入,不加判断就会每输入一次进入一次
if ((GenerateKeyExOptPara.iSeedArraySize==2) && (InputSeed.TextLength==6) && (GenerateKeyExOptPara.ipSeedArray.Length!=0))
{
GenerateKeyExOptPara.ipSeedArray[0] = (byte)((GenerateKeyExOptPara.ipSeedArray[0] << 4) + GenerateKeyExOptPara.ipSeedArray[1]);
GenerateKeyExOptPara.ipSeedArray[1] = (byte)((GenerateKeyExOptPara.ipSeedArray[2] << 4) + GenerateKeyExOptPara.ipSeedArray[3]);
}
if ((GenerateKeyExOptPara.iSeedArraySize == 4) && (InputSeed.TextLength == 10) && (GenerateKeyExOptPara.ipSeedArray.Length != 0))
{
GenerateKeyExOptPara.ipSeedArray[0] = (byte)((GenerateKeyExOptPara.ipSeedArray[0] << 4) + GenerateKeyExOptPara.ipSeedArray[1]);
GenerateKeyExOptPara.ipSeedArray[1] = (byte)((GenerateKeyExOptPara.ipSeedArray[2] << 4) + GenerateKeyExOptPara.ipSeedArray[3]);
GenerateKeyExOptPara.ipSeedArray[2] = (byte)((GenerateKeyExOptPara.ipSeedArray[4] << 4) + GenerateKeyExOptPara.ipSeedArray[5]);
GenerateKeyExOptPara.ipSeedArray[3] = (byte)((GenerateKeyExOptPara.ipSeedArray[6] << 4) + GenerateKeyExOptPara.ipSeedArray[7]);
}
处理后效果:
4.通过点击计算Button,计算出Key值并显示
1.使用DllImport引用dll文件
在窗体类public partial class SeedToKey : Form的最前面使用DllImport函数,尝试过放在后面会报错
[DllImport("GenerateKeyExImpl.dll", EntryPoint = "GenerateKeyExOpt")]
static extern unsafe VKeyGenResultEx GenerateKeyExOpt(byte[] ipSeedArray,
uint iSeedArraySize,
uint iSecurityLevel,
char* ipVariant,
char* ipOptions,
byte[] iopKeyArray,
uint iMaxKeyArraySize,
uint oActualKeyArraySize);
注意:
1.GenerateKeyExImpl.dll是要调用的dll文件,EntryPoint = "GenerateKeyExOpt"是只这里要使用GenerateKeyExImpl.dll文件中的GenerateKeyExOpt函数,这里参照了一篇博文
2.C#中使用指针需要加unsafe关键字,在调试过程中,出现以下错误,参考这篇 博文 解决
C#:不安全代码只会在使用 /unsafe 编译的情况下出现
3.因byte和byte[]在后面函数赋值时无法转换,这里直接写成byte[],和dll文件中的函数原型不一致,但实质是一样的,都是unsigned char类型的
4.C#数组定义参考博文.
KEYGENALGO_API VKeyGenResultEx GenerateKeyExOpt(
const unsigned char* ipSeedArray, /* Array for the seed [in] */
unsigned int iSeedArraySize, /* Length of the array for the seed [in] */
const unsigned int iSecurityLevel, /* Security level [in] */
const char* ipVariant, /* Name of the active variant [in] */
const char* ipOptions, /* Optional parameter which might be used for OEM specific information [in]*/
unsigned char* iopKeyArray, /* Array for the key [in, out] */
unsigned int iMaxKeyArraySize, /* Maximum length of the array for the key [in] */
unsigned int oActualKeyArraySize) /* Length of the key [out] */
4.C#的数据类型和C++的不一致,如C#的byte在C++中为BYTE,具体对应关系可以参照MSDN和一篇博文.
在Button事件中添加代码
if (securityLevelComboBox.SelectedIndex == 0)
{
GenerateKeyExOptPara.iSecurityLevel = 1;
}
if (securityLevelComboBox.SelectedIndex == 1)
{
GenerateKeyExOptPara.iSecurityLevel = 2;
}
if (securityLevelComboBox.SelectedIndex == 2)
{
GenerateKeyExOptPara.iSecurityLevel = 3;
}
GenerateKeyExOptPara.oActualKeyArraySize = GenerateKeyExOptPara.iSeedArraySize;
GenerateKeyExOpt(GenerateKeyExOptPara.ipSeedArray, GenerateKeyExOptPara.iSeedArraySize,
GenerateKeyExOptPara.iSecurityLevel, null, null, GenerateKeyExOptPara.iopKeyArray,
GenerateKeyExOptPara.iMaxKeyArraySize, GenerateKeyExOptPara.oActualKeyArraySize);
if (GenerateKeyExOptPara.iSeedArraySize == 2)
{
KeyValue.Text = "0X" + Convert.ToString(GenerateKeyExOptPara.iopKeyArray[0], 16).ToUpper().PadLeft(2, '0') +
Convert.ToString(GenerateKeyExOptPara.iopKeyArray[1], 16).ToUpper().PadLeft(2, '0');
}
if (GenerateKeyExOptPara.iSeedArraySize == 4)
{
KeyValue.Text = "0X" + Convert.ToString(GenerateKeyExOptPara.iopKeyArray[0], 16).ToUpper().PadLeft(2, '0') +
Convert.ToString(GenerateKeyExOptPara.iopKeyArray[1], 16).ToUpper().PadLeft(2, '0') +
Convert.ToString(GenerateKeyExOptPara.iopKeyArray[2], 16).ToUpper().PadLeft(2, '0') +
Convert.ToString(GenerateKeyExOptPara.iopKeyArray[3], 16).ToUpper().PadLeft(2, '0');
}
由于计算出来的Key值是byte[]类型的,需要转换成combox控件的string类型
Convert.ToString(GenerateKeyExOptPara.iopKeyArray[3], 16).ToUpper().PadLeft(2, '0');
注意:
1.ToString(GenerateKeyExOptPara.iopKeyArray[0], 16) 是将byte[]类型转换成string类型,并且将十进制的数据转换成十六进制
2.ToUpper() 是返回大写格式
3.PadLeft(2, ‘0’) 表示检查字符串长度是否少于2位,若少于2位,则自动在其左侧以’0’补足。 参考博文.
4.C#中字符串拼接可用“+”号运算符,参考一篇博文.
5.有地方需要将byte[]类型和string类型相互转换,参考博文.
5.给应用程序添加图标
参考 给C#应用程序添加应用图标.
第一次尝试用C#写上位机,磕磕绊绊弄出来了,后面有时间再看看。
源码放在Gitee了,有需要的话也可以下载看看