【续】强行在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:
常规页面,按照打框处设置:
调试页面,设置工作目录:
附加包含目录中加入:
..\Classes
..
%(AdditionalIncludeDirectories)
预处理器定义中加入:
_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即可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端