强行在C# Winform中渲染Cocos2d-x 3.6

【前言】

  上一篇讲了怎么把Cocos2d-x 3.6渲染进MFC窗体,这里来讲一下怎么在C# Winform中做到同样的功能。如果你不熟悉MFC的使用但对C# Winform比较在行,请往下看。

  这一篇是作为上一篇的副属文,所以文中提到的部分操作需要在上一篇中找……博主懒逼不在这复制粘贴了。

 


【核心思想】

  同上一章不同的是,C#是托管环境,并不能直接用“对象.方法()”这样的形式来访问Cocos层的代码。我们需要在其间建立一个DLL层(C++编写)作为Cocos层的接口,让C#通过接口来控制Cocos层。

 


【需要的工具】

  1、    安装了C#组件的Visual Studio 2013

  2、    Cocos2d-x 3.6

  3、    GLFW (下载地址:点我

  4、    CMake(下载地址:点我

 


【操作步骤】

  1、    创建C# Winform项目

    .NET的版本随意,使用默认的即可。

    

 

  2、    拷贝必要文件

    参考上一篇

 

  3、    创建空的C++项目

    VS2013创建的C++ DLL项目默认是Win8.1平台的,不知道里面装了什么奇怪的东西进去……于是手动创建干净的DLL项目。项目名称自定,我使用的是“App”

    

    添加完成后,将App项目设为C#项目的依赖项。

 

  4、    在解决方案中加入Cocos项目

    将libcocos2d,libbox2d,libspine加入解决方案中,并把libcocos2d设为App项目的依赖项。

 

  5、    修改C++项目的属性

    在属性管理器(视图——属性管理器)中为项目添加cocos2dx的两个属性表。属性表位于解决方案目录\cocos2d\cocos\2d:

    

 

    常规页面,按照打框处设置:

    

 

    调试页面,设置工作目录:

    

    

    附加包含目录中加入:

    $(EngineRoot)cocos\audio\include
    $(EngineRoot)external
    $(EngineRoot)external\chipmunk\include\chipmunk
    $(EngineRoot)extensions
    ..\Classes
    ..
    %(AdditionalIncludeDirectories)
    $(_COCOS_HEADER_WIN32_BEGIN)
    $(_COCOS_HEADER_WIN32_END)

  

    预处理器定义中加入:

    _WIN32
    _WINDOWS
    COCOS2D_DEBUG=1
    _CRT_SECURE_NO_WARNINGS

 

    附加库目录中加入:

    $(_COCOS_LIB_PATH_WIN32_BEGIN)

    $(_COCOS_LIB_PATH_WIN32_END)

  

    附加依赖项加入:

    $(_COCOS_LIB_WIN32_BEGIN)

    $(_COCOS_LIB_WIN32_END)

    libcocos2d.lib

 

  6、    修改GLFW

    同上一篇

 

  7、    修改Cocos层

    同上一篇,以及修改CCFileUtils-win32.cpp 59行的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static  void  _checkPath()
{
     if  (0 == s_resourcePath.length())
     {
         char  pathBuffer[MAX_PATH] = { 0 };
         WCHAR   wszPath[MAX_PATH] = { 0 };
         int  nNum = WideCharToMultiByte(CP_ACP, 0, wszPath,
             GetCurrentDirectory( sizeof (wszPath), wszPath),
             pathBuffer, MAX_PATH, NULL, NULL);
         pathBuffer[nNum] =  '\\' ;
  
         s_resourcePath = pathBuffer;
     }
}

  

  8、    为C++项目添加代码

    首先添加一个标准DLL源文件dllmain.cpp(名字必须是这个):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// dllmain.cpp : Defines the entry point for the DLL application.
#include <windows.h>
 
BOOL  APIENTRY DllMain(  HMODULE  hModule, DWORD   ul_reason_for_call, LPVOID  lpReserved)
{
     switch  (ul_reason_for_call)
     {
     case  DLL_PROCESS_ATTACH:
     case  DLL_THREAD_ATTACH:
         break ;
     case  DLL_THREAD_DETACH:
     case  DLL_PROCESS_DETACH:
         //instance will be deleted automatically
         break ;
     }
     return  TRUE;
}

  

    然后将Classes文件夹中的源码加入到项目中:

    

 

    最后添加接口(文件名自定,我使用的API.h和API.cpp),头文件:

1
2
3
4
5
6
7
8
9
#pragma once
 
#define DLLEXPORT __declspec(dllexport) 
 
extern  "C"
{<br>  DLLEXPORT  void  Initialize( HWND  parent);
  DLLEXPORT  void  MainLoop();
  DLLEXPORT  void  Destory();
};

    实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "API.h"
 
#include "cocos2d.h"
#include "AppDelegate.h"
 
extern  "C"
{
     AppDelegate app;
     DLLEXPORT  void  Initialize( HWND  parent)
     {
         cocos2d::GLViewImpl::SetParent(parent);
         cocos2d::Application::getInstance()->run();
     }
 
 
     DLLEXPORT  void  MainLoop()
     {
         auto  director = cocos2d::Director::getInstance();
         director->mainLoop();
         director->getOpenGLView()->pollEvents();
     }
 
 
     DLLEXPORT  void  Destory()
     {
         auto  director = cocos2d::Director::getInstance();
         director->getOpenGLView()->release();
         director->end();
         director->mainLoop();
     }
}

    之后可以根据需求在接口中添加更多的函数。

 

  ⑨、    修改C#项目的属性

    设置输出路径:

    

 

    设置工作目录和启用本机代码调试(勾上后可以调试C++层):

    

 

  10、    为C#项目添加代码

    添加一个调用DLL代码的类,我使用的名字叫NativeInterface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using  System;
using  System.Runtime.InteropServices;
 
 
namespace  Cocos2dxCSharp
{
     class  NativeInterface
     {
         const  string  DLL_NAME =  "App.dll" ;
 
         [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
         public  static  extern  void  Initialize( int  parent);
 
         [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
         public  static  extern  void  MainLoop();
 
         [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
         public  static  extern  void  Destory();
     }
}

    在窗体编辑器中,对窗体添加Load和FormClosing两个事件响应方法,再拖一个Panel控件和一个Timer控件到窗体上。Timer控件的Interval值设为10,并添加Tick事件的响应方法。

    然后完成方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private  void  Form1_Load( object  sender, EventArgs e)
{
     this .timer1.Start();
     NativeInterface.Initialize( this .panel1.Handle.ToInt32());
}
 
 
private  void  Form1_FormClosing( object  sender, FormClosingEventArgs e)
{
     this .timer1.Stop();
     NativeInterface.Destory();
}
 
 
private  void  timer1_Tick( object  sender, EventArgs e)
{
     NativeInterface.MainLoop();
}

  

  11、    运行起来

    如果编译没有出错的话,运行起来会看到这个样子:

    

 


【字符串传递处理】

  普通的数据类型(int,float这些)是可以直接作为参数或返回值传递的。虽然C#中的string类型和C++中的const char*类型也是对应的,但是在调试过程中,如果不做处理会报错。

  因为调试时C#的托管堆栈和C++的DLL堆栈并不属于同一块内存,就好比你拿着“城隍庙”这个地址,在成都找到的是各种电子元件,在上海找到的是各种小吃。

  这里有篇博文讲了参数如何传递,我大概整理了一下:

  1、字符串作为参数
    C++:参数为char*
    C#:参数为string,用[MarshalAs(UnmanagedType.LPStr)]修饰

1
2
[DllImport( "A.dll)" ]
static  extern  void  Function([MarshalAs(UnmanagedType.LPStr)] string  val);

  

  2、字符串作为返回值
    C++:返回值为char*
    C#:返回值为string,使用[return:MarshalAs(UnmanagedType.LPStr)]修饰

1
2
3
[DllImport( "A.dll)" ]
[ return :MarshalAs(UnmanagedType.LPStr)]
static  extern  string  Function();

  

  3、字符串作为输入输出参数
    C++:参数为char*
    C#:参数为byte[](那篇博文提到的用StringBuilder我这里传不了,不解)

1
2
[DllImport( "A.dll)" ]
static  extern  void  Function( byte [] intoutVal);

  如果要传递宽字符,使用UnmanagedType.LPWStr即可。



文章转载自http://www.cnblogs.com/GuyaWeiren/p/4606419.html,感谢博主的分享。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值