UnityにおけるC++活用の基礎知識
なぜUnityでC++を使用するのか:パフォーマンスとコスト
UnityでC++を活用する最大の理由は、パフォーマンスの最適化とコスト効率の向上です。具体的なメリットは以下の通りです:
- 処理速度の向上
- C++による直接的なハードウェアアクセス
- 最適化されたネイティブコードの実行
- マネージドコードのオーバーヘッド削減
- メモリ管理の最適化
- 細かなメモリ制御が可能
- ガベージコレクションの影響を最小限に
- キャッシュ効率の向上
- 既存コードの再利用
- レガシーシステムとの統合
- 既存のC++ライブラリの活用
- クロスプラットフォーム開発の効率化
ただし、以下のようなコストも考慮する必要があります:
- 開発工数の増加
- デバッグの複雑化
- プラットフォーム間の互換性対応
Unity Native Plugin Interfaceの仕組みと特徴
Unity Native Plugin Interfaceは、C++で作成したネイティブプラグインをUnityエンジンと連携させるための橋渡し役として機能します。
基本的な構造
// プラグインの基本構造
extern "C" {
// UnityからC++関数を呼び出すためのエクスポート関数
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API MyPluginFunction() {
// 実装
}
}
主要な機能と特徴
- プラットフォーム検出
#if UNITY_IPHONE
// iOS向けの実装
#elif UNITY_ANDROID
// Android向けの実装
#endif
- データマーシャリング
// Unity側のデータ構造
struct Vector3 {
float x, y, z;
};
// C++側での受け取り
extern "C" UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API
ProcessVector(Vector3* vector) {
// ネイティブコードでの処理
}
- コールバック管理
// コールバック関数の定義
typedef void (*FunctionCallback)(const char* message);
// コールバックの登録
static FunctionCallback callback = nullptr;
extern "C" UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API
RegisterCallback(FunctionCallback cb) {
callback = cb;
}
C++とC#の連携における重要な考慮点
- メモリ管理の整合性
- C++側でのメモリリークの防止
- 適切なリソース解放のタイミング
- スマートポインタの活用
// スマートポインタを使用したメモリ管理の例
class ResourceManager {
private:
std::unique_ptr<Resource> resource;
public:
void Initialize() {
resource = std::make_unique<Resource>();
}
// リソースは自動的に解放される
};
- 例外処理とエラーハンドリング
- C++の例外をC#側で適切に処理
- エラーコードの統一的な管理
- ログ出力の一元化
// エラーハンドリングの実装例
extern "C" UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API
ExecuteOperation() {
try {
// 処理の実装
return SUCCESS_CODE;
}
catch (const std::exception& e) {
// エラーログの記録
return ERROR_CODE;
}
}
- パフォーマンス最適化のポイント
- データのコピーを最小限に抑える
- スレッド間の同期オーバーヘッドの考慮
- キャッシュ効率を意識した設計
// パフォーマンスを考慮したデータ処理の例
struct DataBlock {
alignas(16) float data[4]; // SIMD操作のためのアライメント
};
extern "C" UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API
ProcessDataBlock(DataBlock* block) {
// SIMDを活用した高速な処理
}
以上の基礎知識を踏まえることで、UnityとC++の効果的な連携が可能になります。次のセクションでは、具体的な開発環境の構築方法について説明します。
UnityでのC++プラグイン開発環境構築
各OS向け開発環境のセットアップ方法
Windows環境の構築
- Visual Studioのインストール
- Visual Studio 2019以降を推奨
- 「C++によるデスクトップ開発」ワークロードを選択
- Unity開発用コンポーネントを追加
// Visual Studioプロジェクト設定例 #ifdef _WIN32 #define UNITY_INTERFACE_EXPORT __declspec(dllexport) #define UNITY_INTERFACE_API __stdcall #else #define UNITY_INTERFACE_EXPORT #define UNITY_INTERFACE_API #endif
- プロジェクト設定
- DLLプロジェクトの作成
- プラットフォームターゲットの設定
- ランタイムライブラリの選択(/MD or /MT)
macOS環境の構築
- Xcodeのセットアップ
- Command Line Toolsのインストール
- CMakeのインストール(
brew install cmake)
# macOS用ビルドスクリプト例 #!/bin/bash mkdir -p build cd build cmake .. -G Xcode -DCMAKE_BUILD_TYPE=Release xcodebuild -configuration Release
Linux環境の構築
- 必要なパッケージのインストール
sudo apt-get update sudo apt-get install build-essential sudo apt-get install cmake
- コンパイラ設定
# CMakeLists.txt例
if(UNIX AND NOT APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
endif()
必要なSDKとツールチェーンの準備
- 必須ツール一覧
| ツール | 用途 | インストール方法 |
|---|---|---|
| CMake | クロスプラットフォームビルド | 公式サイトまたはパッケージマネージャー |
| Git | バージョン管理 | 公式サイトまたはパッケージマネージャー |
| Unity | ゲームエンジン | Unity Hub経由 |
| プラットフォームSDK | 各プラットフォーム向けビルド | 各プラットフォームの開発者サイト |
- Unity Plugin SDKの設定
// Plugin SDKのインクルード
#include "Unity/IUnityInterface.h"
#include "Unity/IUnityGraphics.h"
// グラフィックスインターフェースの取得
static IUnityGraphics* s_Graphics = nullptr;
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
UnityPluginLoad(IUnityInterfaces* unityInterfaces) {
s_Graphics = unityInterfaces->Get<IUnityGraphics>();
}
クロスプラットフォーム開発の基盤作り
- プロジェクト構造の設計
MyUnityPlugin/
├── CMakeLists.txt
├── src/
│ ├── plugin.cpp
│ └── plugin.h
├── include/
│ └── Unity/
├── build/
└── unity_project/
└── Assets/
└── Plugins/
- CMakeの設定例
# CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(MyUnityPlugin)
# プラットフォーム別の設定
if(WIN32)
add_definitions(-DPLATFORM_WINDOWS)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
elseif(APPLE)
add_definitions(-DPLATFORM_MACOS)
set(CMAKE_MACOSX_RPATH ON)
elseif(ANDROID)
add_definitions(-DPLATFORM_ANDROID)
endif()
# ライブラリの設定
add_library(${PROJECT_NAME} SHARED
src/plugin.cpp
)
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
)
- ビルドスクリプトの作成
# build.py
import os
import platform
import subprocess
def main():
build_dir = 'build'
if not os.path.exists(build_dir):
os.makedirs(build_dir)
if platform.system() == 'Windows':
subprocess.run(['cmake', '-S', '.', '-B', build_dir,
'-G', 'Visual Studio 16 2019'])
else:
subprocess.run(['cmake', '-S', '.', '-B', build_dir,
'-G', 'Unix Makefiles'])
subprocess.run(['cmake', '--build', build_dir,
'--config', 'Release'])
if __name__ == '__main__':
main()
- デバッグ設定の準備
// デバッグログ機能の実装例
#ifdef _DEBUG
#define DEBUG_LOG(msg) DebugLog(msg)
#else
#define DEBUG_LOG(msg)
#endif
extern "C" UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API
DebugLog(const char* message) {
// Unityのデバッグコンソールにログを出力
}
この環境構築が完了すれば、各プラットフォーム向けのC++プラグイン開発を効率的に進めることができます。次のセクションでは、具体的な実装テクニックについて説明します。
実践的なC++プラグイン実装テクニック
メモリ管理の最適化手法
- スマートポインタの活用
// スマートポインタを使用した安全なリソース管理
class ResourceManager {
private:
// 共有リソースの管理
std::shared_ptr<LargeResource> sharedResource;
// 排他的リソースの管理
std::unique_ptr<ExclusiveResource> uniqueResource;
public:
void Initialize() {
// リソースの初期化
sharedResource = std::make_shared<LargeResource>();
uniqueResource = std::make_unique<ExclusiveResource>();
}
// リソースの安全な共有
std::shared_ptr<LargeResource> GetSharedResource() {
return sharedResource;
}
};
- メモリプール実装
// 高速なメモリアロケーション用のメモリプール
template<typename T, size_t PoolSize = 1000>
class MemoryPool {
private:
struct Block {
T data;
Block* next;
};
Block* freeList;
std::array<Block, PoolSize> pool;
public:
MemoryPool() {
// メモリプールの初期化
for (size_t i = 0; i < PoolSize - 1; ++i) {
pool[i].next = &pool[i + 1];
}
pool[PoolSize - 1].next = nullptr;
freeList = &pool[0];
}
T* Allocate() {
if (!freeList) return nullptr;
Block* block = freeList;
freeList = freeList->next;
return &block->data;
}
void Deallocate(T* ptr) {
if (!ptr) return;
Block* block = reinterpret_cast<Block*>(ptr);
block->next = freeList;
freeList = block;
}
};
パフォーマンスクリティカルな処理の実装例
- SIMD最適化
// SIMD命令を使用したベクトル演算
#include <immintrin.h>
class VectorProcessor {
public:
// 4つのfloat値を同時に処理
void ProcessVectors(float* input, float* output, size_t count) {
for (size_t i = 0; i < count; i += 4) {
__m128 vec = _mm_load_ps(&input[i]);
__m128 result = _mm_mul_ps(vec, _mm_set1_ps(2.0f));
_mm_store_ps(&output[i], result);
}
}
};
- マルチスレッド処理
// スレッドプール実装
class ThreadPool {
private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex queue_mutex;
std::condition_variable condition;
bool stop;
public:
ThreadPool(size_t threads) : stop(false) {
for (size_t i = 0; i < threads; ++i) {
workers.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queue_mutex);
condition.wait(lock, [this] {
return stop || !tasks.empty();
});
if (stop && tasks.empty()) return;
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}
template<class F>
void Enqueue(F&& f) {
{
std::unique_lock<std::mutex> lock(queue_mutex);
tasks.emplace(std::forward<F>(f));
}
condition.notify_one();
}
};
デバッグとプロファイリングの効率的な方法
- デバッグ用マクロとロギング
// デバッグ支援システム
class DebugSystem {
public:
enum class LogLevel {
Info,
Warning,
Error
};
static void Log(LogLevel level, const char* message) {
#ifdef _DEBUG
std::string prefix;
switch (level) {
case LogLevel::Info: prefix = "[INFO] "; break;
case LogLevel::Warning: prefix = "[WARN] "; break;
case LogLevel::Error: prefix = "[ERROR] "; break;
}
// Unity側に送信するログメッセージを作成
SendToUnity((prefix + message).c_str());
#endif
}
private:
static void SendToUnity(const char* message) {
// Unityのデバッグコンソールにメッセージを送信
}
};
// 使用例
#define DEBUG_LOG(msg) DebugSystem::Log(DebugSystem::LogLevel::Info, msg)
#define DEBUG_WARN(msg) DebugSystem::Log(DebugSystem::LogLevel::Warning, msg)
#define DEBUG_ERROR(msg) DebugSystem::Log(DebugSystem::LogLevel::Error, msg)
- パフォーマンスプロファイリング
// プロファイリングツール
class Profiler {
private:
struct ProfilePoint {
std::string name;
std::chrono::high_resolution_clock::time_point start;
double duration;
};
static std::unordered_map<std::string, ProfilePoint> points;
static std::mutex profileMutex;
public:
static void StartProfile(const std::string& name) {
std::lock_guard<std::mutex> lock(profileMutex);
points[name] = {
name,
std::chrono::high_resolution_clock::now(),
0.0
};
}
static void EndProfile(const std::string& name) {
std::lock_guard<std::mutex> lock(profileMutex);
auto now = std::chrono::high_resolution_clock::now();
auto& point = points[name];
point.duration = std::chrono::duration<double>(
now - point.start).count() * 1000.0;
// プロファイリング結果をログ出力
char buffer[256];
snprintf(buffer, sizeof(buffer),
"%s: %.3f ms", name.c_str(), point.duration);
DEBUG_LOG(buffer);
}
};
// 使用例
#define PROFILE_SCOPE(name) \
Profiler::StartProfile(name); \
auto scopeExit = std::make_unique<ScopeGuard>([](){ \
Profiler::EndProfile(name); \
})
これらの実装テクニックを適切に組み合わせることで、高性能で安定したC++プラグインを開発することができます。次のセクションでは、具体的な実装例を通じて、これらのテクニックの実践的な活用方法を説明します。
C++プラグインの実装例と解説
物理演算エンジンの実装例
以下は、簡単な2D物理演算エンジンのプラグイン実装例です:
// 2D物理演算エンジンの基本実装
namespace Physics2D {
struct Vector2 {
float x, y;
Vector2 operator+(const Vector2& other) const {
return {x + other.x, y + other.y};
}
Vector2 operator*(float scalar) const {
return {x * scalar, y * scalar};
}
};
class RigidBody {
private:
Vector2 position;
Vector2 velocity;
float mass;
float restitution; // 反発係数
public:
RigidBody(const Vector2& pos, float m)
: position(pos), mass(m), restitution(0.8f) {}
void ApplyForce(const Vector2& force, float deltaTime) {
Vector2 acceleration = force * (1.0f / mass);
velocity = velocity + acceleration * deltaTime;
}
void Update(float deltaTime) {
position = position + velocity * deltaTime;
}
void HandleCollision(RigidBody& other) {
// 簡単な衝突応答
Vector2 normal = {
other.position.x - position.x,
other.position.y - position.y
};
float distance = sqrt(normal.x * normal.x + normal.y * normal.y);
if (distance < 0.001f) return;
normal = {normal.x / distance, normal.y / distance};
float relativeVelocity =
(velocity.x - other.velocity.x) * normal.x +
(velocity.y - other.velocity.y) * normal.y;
if (relativeVelocity > 0) return;
float j = -(1 + restitution) * relativeVelocity;
j /= 1/mass + 1/other.mass;
velocity = velocity + normal * (j / mass);
other.velocity = other.velocity - normal * (j / other.mass);
}
};
}
// Unityプラグインインターフェース
extern "C" {
UNITY_INTERFACE_EXPORT Physics2D::RigidBody* UNITY_INTERFACE_API
CreateRigidBody(float x, float y, float mass) {
return new Physics2D::RigidBody(Physics2D::Vector2{x, y}, mass);
}
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API
UpdatePhysics(Physics2D::RigidBody* body, float deltaTime) {
if (body) body->Update(deltaTime);
}
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API
ApplyForce(Physics2D::RigidBody* body, float fx, float fy, float deltaTime) {
if (body) body->ApplyForce(Physics2D::Vector2{fx, fy}, deltaTime);
}
}
画像処理アルゴリズムの実装例
画像処理プラグインの実装例です:
// 画像処理プラグインの実装
namespace ImageProcessing {
struct Pixel {
unsigned char r, g, b, a;
};
class ImageProcessor {
private:
std::vector<Pixel> buffer;
int width, height;
public:
ImageProcessor(int w, int h) : width(w), height(h) {
buffer.resize(w * h);
}
// ガウシアンブラー処理の実装
void ApplyGaussianBlur(float sigma) {
// カーネルサイズの計算
int kernelSize = static_cast<int>(sigma * 6 + 1) | 1;
std::vector<float> kernel(kernelSize);
// ガウシアンカーネルの生成
float sum = 0.0f;
int half = kernelSize / 2;
for (int i = 0; i < kernelSize; ++i) {
float x = static_cast<float>(i - half);
kernel[i] = exp(-(x * x) / (2 * sigma * sigma));
sum += kernel[i];
}
// カーネルの正規化
for (float& k : kernel) {
k /= sum;
}
// 水平方向のブラー
std::vector<Pixel> temp(buffer.size());
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
float r = 0, g = 0, b = 0, a = 0;
for (int i = 0; i < kernelSize; ++i) {
int sx = x + i - half;
sx = std::max(0, std::min(sx, width - 1));
Pixel& p = buffer[y * width + sx];
float k = kernel[i];
r += p.r * k;
g += p.g * k;
b += p.b * k;
a += p.a * k;
}
temp[y * width + x] = {
static_cast<unsigned char>(r),
static_cast<unsigned char>(g),
static_cast<unsigned char>(b),
static_cast<unsigned char>(a)
};
}
}
// 垂直方向のブラー
for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
float r = 0, g = 0, b = 0, a = 0;
for (int i = 0; i < kernelSize; ++i) {
int sy = y + i - half;
sy = std::max(0, std::min(sy, height - 1));
Pixel& p = temp[sy * width + x];
float k = kernel[i];
r += p.r * k;
g += p.g * k;
b += p.b * k;
a += p.a * k;
}
buffer[y * width + x] = {
static_cast<unsigned char>(r),
static_cast<unsigned char>(g),
static_cast<unsigned char>(b),
static_cast<unsigned char>(a)
};
}
}
}
};
}
// Unityプラグインインターフェース
extern "C" {
UNITY_INTERFACE_EXPORT ImageProcessing::ImageProcessor* UNITY_INTERFACE_API
CreateImageProcessor(int width, int height) {
return new ImageProcessing::ImageProcessor(width, height);
}
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API
ApplyBlur(ImageProcessing::ImageProcessor* processor, float sigma) {
if (processor) processor->ApplyGaussianBlur(sigma);
}
}
サードパーティライブラリの統合方法
以下は、OpenCVを使用した画像処理プラグインの統合例です:
// OpenCVを使用した画像処理プラグイン
#include <opencv2/opencv.hpp>
namespace CVIntegration {
class OpenCVProcessor {
private:
cv::Mat image;
public:
bool LoadImage(const unsigned char* data, int width, int height) {
image = cv::Mat(height, width, CV_8UC4, (void*)data);
return !image.empty();
}
void ApplyFeatureDetection() {
// FAST特徴点検出の実装
std::vector<cv::KeyPoint> keypoints;
cv::FAST(image, keypoints, 40);
// 検出した特徴点を描画
for (const auto& kp : keypoints) {
cv::circle(image, kp.pt, 3, cv::Scalar(0, 255, 0, 255), 1);
}
}
const unsigned char* GetProcessedData() const {
return image.data;
}
};
}
// CMakeの設定例
/*
cmake_minimum_required(VERSION 3.12)
project(OpenCVPlugin)
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
add_library(${PROJECT_NAME} SHARED
src/opencv_plugin.cpp
)
target_link_libraries(${PROJECT_NAME}
${OpenCV_LIBS}
)
*/
// Unityプラグインインターフェース
extern "C" {
UNITY_INTERFACE_EXPORT CVIntegration::OpenCVProcessor* UNITY_INTERFACE_API
CreateOpenCVProcessor() {
return new CVIntegration::OpenCVProcessor();
}
UNITY_INTERFACE_EXPORT bool UNITY_INTERFACE_API
ProcessImage(CVIntegration::OpenCVProcessor* processor,
unsigned char* data, int width, int height) {
if (!processor) return false;
return processor->LoadImage(data, width, height);
}
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API
DetectFeatures(CVIntegration::OpenCVProcessor* processor) {
if (processor) processor->ApplyFeatureDetection();
}
}
これらの実装例は、基本的な機能を示すためのものです。実際の開発では、エラーハンドリング、メモリ管理、パフォーマンス最適化などをより慎重に考慮する必要があります。次のセクションでは、これらの実装における注意点やベストプラクティスについて説明します。
C++プラグインのベストプラクティスとトラブルシューティング
メモリリークを防ぐための設計パターン
- RAIIパターンの活用
// リソース管理クラスの実装例
class ResourceManager {
private:
struct ResourceDeleter {
void operator()(void* ptr) {
if (ptr) {
// リソースの解放処理
ReleaseResource(ptr);
}
}
};
std::unique_ptr<void, ResourceDeleter> resource;
static std::unordered_map<void*, size_t> allocations;
static std::mutex allocationMutex;
public:
void* Allocate(size_t size) {
void* ptr = std::malloc(size);
if (ptr) {
std::lock_guard<std::mutex> lock(allocationMutex);
allocations[ptr] = size;
}
return ptr;
}
void Deallocate(void* ptr) {
if (ptr) {
std::lock_guard<std::mutex> lock(allocationMutex);
allocations.erase(ptr);
std::free(ptr);
}
}
// メモリリーク検出
static void CheckLeaks() {
std::lock_guard<std::mutex> lock(allocationMutex);
if (!allocations.empty()) {
for (const auto& alloc : allocations) {
DEBUG_LOG(("Memory leak detected: " +
std::to_string(alloc.second) +
" bytes at " +
std::to_string((uintptr_t)alloc.first)).c_str());
}
}
}
};
- 循環参照の防止
// 弱参照を使用した循環参照の防止
class Component;
class GameObject {
private:
std::vector<std::shared_ptr<Component>> components;
public:
template<typename T>
std::shared_ptr<T> AddComponent() {
auto component = std::make_shared<T>();
components.push_back(component);
return component;
}
};
class Component {
private:
// 弱参照を使用してGameObjectを参照
std::weak_ptr<GameObject> gameObject;
public:
void SetGameObject(std::shared_ptr<GameObject> go) {
gameObject = go;
}
std::shared_ptr<GameObject> GetGameObject() {
return gameObject.lock();
}
};
プラットフォーム間の互換性確保のコツ
- プラットフォーム依存コードの分離
// プラットフォーム固有の実装を分離
namespace PlatformSpecific {
#if defined(_WIN32)
class WindowsImplementation {
public:
static void* LoadLibrary(const char* name) {
return ::LoadLibraryA(name);
}
static void* GetProcAddress(void* handle, const char* name) {
return ::GetProcAddress((HMODULE)handle, name);
}
};
using PlatformImpl = WindowsImplementation;
#elif defined(__APPLE__)
class AppleImplementation {
public:
static void* LoadLibrary(const char* name) {
return dlopen(name, RTLD_NOW);
}
static void* GetProcAddress(void* handle, const char* name) {
return dlsym(handle, name);
}
};
using PlatformImpl = AppleImplementation;
#elif defined(__ANDROID__)
class AndroidImplementation {
public:
static void* LoadLibrary(const char* name) {
return dlopen(name, RTLD_NOW);
}
static void* GetProcAddress(void* handle, const char* name) {
return dlsym(handle, name);
}
};
using PlatformImpl = AndroidImplementation;
#endif
}
- バイトオーダーの考慮
// バイトオーダーを考慮したデータ変換
class ByteOrderConverter {
public:
static uint16_t SwapEndian(uint16_t value) {
return (value << 8) | (value >> 8);
}
static uint32_t SwapEndian(uint32_t value) {
return ((value << 24) & 0xFF000000) |
((value << 8) & 0x00FF0000) |
((value >> 8) & 0x0000FF00) |
((value >> 24) & 0x000000FF);
}
static float SwapEndian(float value) {
union {
float f;
uint32_t i;
} u;
u.f = value;
u.i = SwapEndian(u.i);
return u.f;
}
};
よくあるエラーとその解決方法
- エラー種別と対処法
| エラー種別 | 原因 | 解決策 |
|---|---|---|
| DllNotFoundException | プラグインが見つからない | プラグインの配置パスを確認、ビルド設定の見直し |
| EntryPointNotFoundException | 関数名の不一致 | エクスポート関数名の確認、呼び出し規約の確認 |
| AccessViolationException | メモリアクセス違反 | ポインタの有効性確認、メモリ管理の見直し |
| BadImageFormatException | プラットフォーム不一致 | ターゲットプラットフォームの設定確認 |
- エラーハンドリングの実装
// エラーハンドリングシステム
class ErrorHandler {
private:
struct Error {
std::string message;
int code;
std::string stackTrace;
};
static std::vector<Error> errorLog;
static std::mutex errorMutex;
public:
static void LogError(const char* message, int code) {
std::lock_guard<std::mutex> lock(errorMutex);
Error error;
error.message = message;
error.code = code;
error.stackTrace = GetStackTrace();
errorLog.push_back(error);
// Unityにエラーを通知
ReportErrorToUnity(message, code);
}
static std::string GetStackTrace() {
std::string trace;
#ifdef _WIN32
void* stack[100];
HANDLE process = GetCurrentProcess();
SymInitialize(process, NULL, TRUE);
WORD frames = CaptureStackBackTrace(0, 100, stack, NULL);
std::vector<char> symbol_buffer(sizeof(SYMBOL_INFO) + 256);
SYMBOL_INFO* symbol = (SYMBOL_INFO*)symbol_buffer.data();
symbol->MaxNameLen = 255;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
for (WORD i = 0; i < frames; i++) {
SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol);
trace += std::string(symbol->Name) + "\n";
}
#endif
return trace;
}
};
// エラーハンドリングの使用例
extern "C" UNITY_INTERFACE_EXPORT bool UNITY_INTERFACE_API
ProcessData(void* data, size_t size) {
try {
if (!data || size == 0) {
ErrorHandler::LogError("Invalid data parameters", -1);
return false;
}
// データ処理
return true;
}
catch (const std::exception& e) {
ErrorHandler::LogError(e.what(), -2);
return false;
}
}
- デバッグ支援ツール
// デバッグ支援システム
class DebugHelper {
public:
static void DumpMemoryState() {
#ifdef _DEBUG
std::stringstream ss;
ss << "Memory State Dump:\n";
ss << "Allocated Objects: " << GetAllocatedObjectCount() << "\n";
ss << "Total Memory Usage: " << GetTotalMemoryUsage() << " bytes\n";
DEBUG_LOG(ss.str().c_str());
#endif
}
static void ValidatePointers() {
#ifdef _DEBUG
// ポインタの有効性チェック
for (const auto& ptr : trackedPointers) {
if (!IsValidPointer(ptr)) {
std::string msg = "Invalid pointer detected: " +
std::to_string((uintptr_t)ptr);
DEBUG_ERROR(msg.c_str());
}
}
#endif
}
static void EnableMemoryTracking(bool enable) {
#ifdef _DEBUG
isMemoryTrackingEnabled = enable;
if (enable) {
StartMemoryTracking();
} else {
StopMemoryTracking();
}
#endif
}
};
これらのベストプラクティスとトラブルシューティング手法を適切に活用することで、より安定したC++プラグインの開発が可能になります。特に、メモリ管理とプラットフォーム互換性に関する問題は、初期段階で適切に対処することが重要です。