【續】強行在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行的方法:

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(名字必須是這個):

// 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),頭文件:

#pragma once

#define DLLEXPORT __declspec(dllexport) 

extern "C"
{
  DLLEXPORT void Initialize(HWND parent);   DLLEXPORT void MainLoop();   DLLEXPORT void Destory(); };

    實現:

#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:

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事件的響應方法。

    然后完成方法:

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)]修飾

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

  

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

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

  

  3、字符串作為輸入輸出參數
    C++:參數為char*
    C#:參數為byte[](那篇博文提到的用StringBuilder我這里傳不了,不解)

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

  如果要傳遞寬字符,使用UnmanagedType.LPWStr即可。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM