小玉米图文教程No.10 - 教你如何用VC#撸出GM8/GMS插件

不久前在群里跟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了,点击确定建立这个项目。

小玉米图文教程No.10 - 教你如何用VC#撸出GM8/GMS插件-傲娇玉米站

嗯,创建工程可能会比较慢,取决于你家电脑妹子活好不好,所以说如果时间太长的话不妨先去喝杯茶。


创建完项目,在项目上右键,选择属性,在弹出的设定框中选择生成,然后在配置中选择所有配置,把下面的目标平台改为X86,之后安Ctrl+S保存。这一步是非常重要的。

小玉米图文教程No.10 - 教你如何用VC#撸出GM8/GMS插件-傲娇玉米站


接下来就是写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,然后点击确定即可。

小玉米图文教程No.10 - 教你如何用VC#撸出GM8/GMS插件-傲娇玉米站写完了代码,就可以生成啦~在工具栏中把Debug改为Release,然后点击菜单栏中的生成->生成解决方案,或直接按Ctrl+Shift+B生成。


嗯,生成DLL可能会比较慢,取决于你家电脑妹子活好不好,所以说如果时间太长的话不妨先去喝杯茶。


小玉米图文教程No.10 - 教你如何用VC#撸出GM8/GMS插件-傲娇玉米站

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文件夹下点击右键->新建->文本文档

小玉米图文教程No.10 - 教你如何用VC#撸出GM8/GMS插件-傲娇玉米站

打开新创建的文件写入以下代码:

"C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\ildasm.exe" "%~dp0GMCSDLL.dll" /linenum /out:"%~dp0GMCSDLL.IL"

这里需要你把GMCSDLL.dllGMCSDLL.IL这两段改写成你的DLL名字你想要输入的IL文件名,要保留前面的%~dp0

保存后将文件改名为run.bat,双击运行,很快就会输出一个IL文件和res文件。

小玉米图文教程No.10 - 教你如何用VC#撸出GM8/GMS插件-傲娇玉米站

使用任何文本编辑器编辑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.ILGMCSDLL.Export.dllGMCSDLL.res这几个字段替换成你自己的文件的名字,其中GMCSDLL.IL是你的IL文件名,GMCSDLL.Export.dll是要导出的新的DLL文件名,GMCSDLL.res是res文件名。

保存为run2.bat文件后,双击运行,很快就会输出一个新的DLL文件。

小玉米图文教程No.10 - 教你如何用VC#撸出GM8/GMS插件-傲娇玉米站

4.使用GM8/GMS调用这个DLL文件

在这个DLL目录下创建一个GMK文件吧,然后写几句代码对新生成的DLL进行调用,我就直接上图了废话不多说了。

小玉米图文教程No.10 - 教你如何用VC#撸出GM8/GMS插件-傲娇玉米站

小玉米图文教程No.10 - 教你如何用VC#撸出GM8/GMS插件-傲娇玉米站

运行一下,看看结果:

小玉米图文教程No.10 - 教你如何用VC#撸出GM8/GMS插件-傲娇玉米站


行咯~那么这个GM8调用C#的DLL也就成功咯~GMS调用我就不试啦,反正代码都是这么写,当然,你也可以在GMS内写扩展,当调用其他语言写得DLL就行,并不是什么难事。

分享:

支付宝

微信