不久前在群里跟LiarOnce唠嗑的时候,突然唠到了用VS写C#的DLL供GM8/GMS调用,当时还在犟说C#的DLL不能直接给GMS用,需要小狐狸的黑科技什么的,但是后来我研究了一下老外的源码,弄出了一套GM8/GMS使用C#的DLL的方法。如果你对这个很感兴趣的话,那看看这篇教程吧!
写在前面的一些注意事项:
1.这篇教程比较适合高端人士,比如那些有用VS开发过东西的人,会用C#写程序的人。而且你得电脑里要装有VS2013或VS2015
2.操作方法十分复杂,说的每一步都非常的有用。所以一定要仔细看,如果你闲麻烦,还是不要运用此方法比较好。
3.必须对GM8/GMS非常的熟练,熟练到可以知道怎么调用插件扩展,DLL这类的东西。
4.如果你是纯新手,没问题!没有VS那么去下载VS就好了~不会C#现学就可以了,如果你有恒心不会被上面我所说的三条需要面对的困难所击倒的话,那不妨继续往下看吧!
附加:哦对了,,忘了说一件事,既然使用C#的DLL,那么就说明你得游戏就需要依赖.NET了,所以说如果想给别人玩你做的游戏,恰巧他又没装.NET,那就是个悲剧了,我想懂的人应该知道我在说些什么东西。
1.使用VS编写C# DLL
打开Visual Studio,这里玉米使用的是VS2015,如果你用的是VS2013的话也没什么问题。新建一个项目,左边依次选择Visual C# -> Windows,然后在中间的那一大堆东西中,选择类库,工程名字随意啦,我就暂时先用GMCSDLL了,点击确定建立这个项目。
嗯,创建工程可能会比较慢,取决于你家电脑妹子活好不好,所以说如果时间太长的话不妨先去喝杯茶。
创建完项目,在项目上右键,选择属性,在弹出的设定框中选择生成,然后在配置中选择所有配置,把下面的目标平台改为X86,之后安Ctrl+S保存。这一步是非常重要的。
接下来就是写C#代码啦~那么我们这里就写两个导出函数吧,一个是弹出提示框,一个是求两个数的和。
在当前项目的Class1.cs文件中,写下如下的代码。这里你的cs文件是什么无所谓啦~因为我的是项目默认给了这个文件所以就在这里面写了。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; //使用MessageBox需要的引用 namespace GMCSDLL { public class Class1 { // 需要导出的第一个函数:弹出信息框ShowMessage public static double ShowMessage(string message, string title){ var dialogResult = MessageBox.Show(message, title); return (double)dialogResult; } // 需要导出的第二个函数:获取a+b的值。 public static double getPlus(double a, double b) { return a + b; } } }
写函数的时候要注意一些问题,因为GM8/GMS只有两种数据类型,分别是real和string,对应的就是C#的double和string类型,函数参数和返回值必须要用这两个类型,其他的绝对不可以!
这里可能会遇到MessageBox报错的情况,是因为你没在项目引用中引入System.Windows.Forms包。右键项目树状图中的引用,选择添加引用,在程序集中找到System.Windows.Forms,然后点击确定即可。
写完了代码,就可以生成啦~在工具栏中把Debug改为Release,然后点击菜单栏中的生成->生成解决方案,或直接按Ctrl+Shift+B生成。
嗯,生成DLL可能会比较慢,取决于你家电脑妹子活好不好,所以说如果时间太长的话不妨先去喝杯茶。
OK,喝完了一杯茶,东西也生成了,接下来我们来通过这个路径找到这个DLL。
当然现在这个DLL是不能直接调用的,我们需要用微软提供的两个工具来对这个DLL进行处理。
2.使用ildasm工具对dll文件进行反汇编
ildasm是微软提供的一个IL反汇编程序,我们现在要利用这个工具反汇编出一个IL文件,并对该文件进行修改,之后再编译生成DLL,才允许被GM8/GMS所调用。具体方法看好了哈~
这个工具是会随同VS一起安装的,在我的电脑中,他是在C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\路径下,所以,可以直接写一个bat来反汇编刚刚编译出的DLL文件来获取IL文件,在生成的DLL文件夹下点击右键->新建->文本文档
打开新创建的文件写入以下代码:
"C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\ildasm.exe" "%~dp0GMCSDLL.dll" /linenum /out:"%~dp0GMCSDLL.IL"
这里需要你把GMCSDLL.dll和GMCSDLL.IL这两段改写成你的DLL名字和你想要输入的IL文件名,要保留前面的%~dp0。
保存后将文件改名为run.bat,双击运行,很快就会输出一个IL文件和res文件。
使用任何文本编辑器编辑IL文件(但是别用windows自带的记事本),这里玉米使用的是Notepad++,打开这个文件。从里面找到像下面这个样式的代码:
.module GMCSDLL.dll // MVID: {FF131C6B-ABEE-4AF1-BAD0-6EFA62EA59F9} .imagebase 0x10000000 .file alignment 0x00000200 .stackreserve 0x00100000 .subsystem 0x0003 // WINDOWS_CUI .corflags 0x00000003 // ILONLY 32BITREQUIRED // Image base: 0x00E00000
接下来要做的是把.corflags这一行的0x00000003改成0x00000002。改完后如同下面这样:
.module GMCSDLL.dll // MVID: {FF131C6B-ABEE-4AF1-BAD0-6EFA62EA59F9} .imagebase 0x10000000 .file alignment 0x00000200 .stackreserve 0x00100000 .subsystem 0x0003 // WINDOWS_CUI .corflags 0x00000002 // ILONLY 32BITREQUIRED // Image base: 0x00E00000
接下来要继续在这段代码的下面添加两行新的代码:
.data VT_01 = int32[2] // 这一行是新加的代码,其中的[2]表示要导出两个函数 .vtfixup [2] int32 fromunmanaged at VT_01 // 这一行是新加的代码,其中的[2]表示要导出两个函数
加入完这两行后这段代码就变成了下面这个样子:
.module GMCSDLL.dll // MVID: {FF131C6B-ABEE-4AF1-BAD0-6EFA62EA59F9} .imagebase 0x10000000 .file alignment 0x00000200 .stackreserve 0x00100000 .subsystem 0x0003 // WINDOWS_CUI .corflags 0x00000002 // ILONLY 32BITREQUIRED // Image base: 0x00E00000 .data VT_01 = int32[2] // 这一行是新加的代码,其中的[2]表示要导出两个函数 .vtfixup [2] int32 fromunmanaged at VT_01 // 这一行是新加的代码,其中的[2]表示要导出两个函数
这里的int32[2]和.vtfixup [2]中的[2]表示这个DLL一共有几个导出函数,那么我这个例子是导出两个函数,所以这里就填写2,如果你要导出250个函数,那你就填250。
继续向下浏览这个文件,你就会找到你导出的函数名了,这里我看到了ShowMessage,就像下面这样:
// =============== CLASS MEMBERS DECLARATION =================== .class public auto ansi beforefieldinit GMCSDLL.Class1 extends [mscorlib]System.Object { .method public hidebysig static float64 ShowMessage(string message, string title) cil managed { .custom instance void [RGiesecke.DllExport.Metadata]RGiesecke.DllExport.DllExportAttribute::.ctor(string) = ( 01 00 0B 53 68 6F 77 4D 65 73 73 61 67 65 01 00 // ...ShowMessage.. 54 55 7D 53 79 73 74 65 6D 2E 52 75 6E 74 69 6D // TU}System.Runtim 65 2E 49 6E 74 65 72 6F 70 53 65 72 76 69 63 65 // e.InteropService 73 2E 43 61 6C 6C 69 6E 67 43 6F 6E 76 65 6E 74 // s.CallingConvent 69 6F 6E 2C 20 6D 73 63 6F 72 6C 69 62 2C 20 56 // ion, mscorlib, V 65 72 73 69 6F 6E 3D 34 2E 30 2E 30 2E 30 2C 20 // ersion=4.0.0.0, 43 75 6C 74 75 72 65 3D 6E 65 75 74 72 61 6C 2C // Culture=neutral, 20 50 75 62 6C 69 63 4B 65 79 54 6F 6B 65 6E 3D // PublicKeyToken= 62 37 37 61 35 63 35 36 31 39 33 34 65 30 38 39 // b77a5c561934e089 11 43 61 6C 6C 69 6E 67 43 6F 6E 76 65 6E 74 69 // .CallingConventi 6F 6E 02 00 00 00 ) // on.... // Code size 9 (0x9) .maxstack 8 .language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}' .line 16,16 : 13,64 'E:\\VSProject\\2015\\GMCSDLL\\GMCSDLL\\Class1.cs' IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: call valuetype [System.Windows.Forms]System.Windows.Forms.DialogResult [System.Windows.Forms]System.Windows.Forms.MessageBox::Show(string, string) .line 17,17 : 13,41 '' IL_0007: conv.r8 IL_0008: ret } // end of method Class1::ShowMessage
找到你要导出的每个函数的名字,在.maxstack前面加上下面的两行代码:
.vtentry 1 : 1 // 这里的语法是.vtentry 1 : 当前是第几个导出的函数,因为信息框是导出的第一个函数,所以这里填1 .export [0] as ShowMessage // 这里的语法是.export [当前是第几个导出的函数 - 1] as 导出的函数名。
貌似有点复杂的样子,前面的那个1:1,表示我现在正在到处第一个函数,所以冒号后面的值为1,冒号前面的不用管。
第二行的方括号中要填写当前是第几个导出的函数-1的值,所以这里要填写0,as后面跟随的是要导出的函数名,所以是ShowMessage
添加完了这两行代码后,这段代码就变成了下面的这个样子:
// =============== CLASS MEMBERS DECLARATION =================== .class public auto ansi beforefieldinit GMCSDLL.Class1 extends [mscorlib]System.Object { .method public hidebysig static float64 ShowMessage(string message, string title) cil managed { .custom instance void [RGiesecke.DllExport.Metadata]RGiesecke.DllExport.DllExportAttribute::.ctor(string) = ( 01 00 0B 53 68 6F 77 4D 65 73 73 61 67 65 01 00 // ...ShowMessage.. 54 55 7D 53 79 73 74 65 6D 2E 52 75 6E 74 69 6D // TU}System.Runtim 65 2E 49 6E 74 65 72 6F 70 53 65 72 76 69 63 65 // e.InteropService 73 2E 43 61 6C 6C 69 6E 67 43 6F 6E 76 65 6E 74 // s.CallingConvent 69 6F 6E 2C 20 6D 73 63 6F 72 6C 69 62 2C 20 56 // ion, mscorlib, V 65 72 73 69 6F 6E 3D 34 2E 30 2E 30 2E 30 2C 20 // ersion=4.0.0.0, 43 75 6C 74 75 72 65 3D 6E 65 75 74 72 61 6C 2C // Culture=neutral, 20 50 75 62 6C 69 63 4B 65 79 54 6F 6B 65 6E 3D // PublicKeyToken= 62 37 37 61 35 63 35 36 31 39 33 34 65 30 38 39 // b77a5c561934e089 11 43 61 6C 6C 69 6E 67 43 6F 6E 76 65 6E 74 69 // .CallingConventi 6F 6E 02 00 00 00 ) // on.... // Code size 9 (0x9) .vtentry 1 : 1 // 这里的语法是.vtentry 1 : 当前是第几个导出的函数,因为信息框是导出的第一个函数,所以这里填1 .export [0] as ShowMessage // 这里的语法是.export [当前是第几个导出的函数 - 1] as 导出的函数名。 .maxstack 8 .language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}' .line 16,16 : 13,64 'E:\\VSProject\\2015\\GMCSDLL\\GMCSDLL\\Class1.cs' IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: call valuetype [System.Windows.Forms]System.Windows.Forms.DialogResult [System.Windows.Forms]System.Windows.Forms.MessageBox::Show(string, string) .line 17,17 : 13,41 '' IL_0007: conv.r8 IL_0008: ret } // end of method Class1::ShowMessage
嗯,然后我们继续往下翻,就能找到第二个要导出的函数getPlus了,用跟上面一样的方式,在.maxstack行前添加两行代码,但是这次加的两行代码就有些不同了:
.vtentry 1 : 2 // 因为getPlus是第二个要导出的函数,所以这里写1 : 2 .export [1] as getPlus // 同上,所以方括号里要填2-1的值,as后面填写导出的函数名getPlus。
好啦~这样我们的两个导出函数就都改完了,按Ctrl+S保存这个IL文件后关闭文本编辑器。
3.使用ilasm工具重新编译DLL
马上就完事了哈~这一步进行完成就可以辣~
ilasm是微软提供的IL汇编程序,同ildasm一样,这个工具也会在安装VS时一起安装,但是他的路径跟ildasm不同,在我的电脑中,他是在C:\Windows\Microsoft.NET\Framework\v4.0.30319\路径下。接下来,我们以同样的方式,写一个BAT文件来完成最终DLL的编译,同样在输出的DLL文件夹下创建一个BAT文件,写下如下代码:
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\ilasm.exe" /dll "%~dp0GMCSDLL.IL" /out:"%~dp0GMCSDLL.Export.dll" /res:"%~dp0GMCSDLL.res"
这里要注意需要把GMCSDLL.IL、GMCSDLL.Export.dll、GMCSDLL.res这几个字段替换成你自己的文件的名字,其中GMCSDLL.IL是你的IL文件名,GMCSDLL.Export.dll是要导出的新的DLL文件名,GMCSDLL.res是res文件名。
保存为run2.bat文件后,双击运行,很快就会输出一个新的DLL文件。
4.使用GM8/GMS调用这个DLL文件
在这个DLL目录下创建一个GMK文件吧,然后写几句代码对新生成的DLL进行调用,我就直接上图了废话不多说了。
运行一下,看看结果:
行咯~那么这个GM8调用C#的DLL也就成功咯~GMS调用我就不试啦,反正代码都是这么写,当然,你也可以在GMS内写扩展,当调用其他语言写得DLL就行,并不是什么难事。