前提
開発環境:Unity 2017 + Visual Studio Community 2017
言語:Unity C#, C++
やりたいこと
DLLのある関数をループしている間に変数を取り出すには、どのような方法を使えばいいのか考察します。
今回は、DLL内で10ずつ変数をインクリメントしている中、1を加算する方法で成功か失敗か判断します。
1をインクリメントすることが可能ならば、その記述を任意の変数を用いた命令に変換することで希望の動作が実現できるはずだからです。
DLLの設定
この節では、以下のコードで記述されたDLLを使用します。
C#で使用する前提なので、関数はC++の命名規則に違反しているかも…。
extern "C"
{
__declspec(dllexport) void ResetConstants();
__declspec(dllexport) void Loop();
__declspec(dllexport) void LoopNotToUseWhile();
__declspec(dllexport) void IncrementA();
__declspec(dllexport) void IncrementB();
__declspec(dllexport) int ReturnA();
__declspec(dllexport) int ReturnB();
}
#include "stdafx.h"
#include "DLLForDebug.h"
#include "iostream"
// グローバル変数
int a = 0;
int b = 0;
// グローバル変数はずっと値を保持するので、最初呼び出した時に初期化する。
void ResetConstants()
{
a = 0;
b = 0;
}
// whileを使用して各変数を10ずつインクリメントする
void Loop()
{
while (true)
{
a += 10;
b += 10;
if (a >= 10000 || b >= 10000)
{
a = a % 10;
b = b % 10;
}
}
}
// whileを使用しないで各変数を10ずつインクリメントする
void LoopNotToUseWhile()
{
a += 10;
b += 10;
if (a >= 10000 || b >= 10000)
{
a = a % 10;
b = b % 10;
}
}
// 変数aをインクリメントする
void IncrementA()
{
a++;
}
// 変数bをインクリメントする
void IncrementB()
{
b++;
}
int ReturnA()
{
return a;
}
int ReturnB()
{
return b;
}
失敗例:Startに無限ループを入れる
Startに無限ループで構成されたDLL内の関数「Loop」を使用し、Update()で値を返そうとしましたが、パソコンがフリーズしました。
普通に考えれば「Start」が呼び出された後「Update」が1フレームごとに呼び出されるので当たり前ですね。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// IntPtr型を使用するのに必要
using System;
// Dllの読み込みに必要
using System.Runtime.InteropServices;
public class RunDLL : MonoBehaviour
{
// Dll内の関数を宣言
[DllImport("DllForDebug")]
private extern static void ResetConstants();
[DllImport("DllForDebug")]
private extern static void Loop();
[DllImport("DllForDebug")]
private extern static void IncrementA();
[DllImport("DllForDebug")]
private extern static void IncrementB();
[DllImport("DllForDebug")]
private extern static int ReturnA();
[DllImport("DllForDebug")]
private extern static int ReturnB();
[DllImport("DllForDebug")]
private extern static void LoopNotToUseWhile();
// Use this for initialization
void Start()
{
Loop();
}
// Update is called once per frame
void Update()
{
Debug.Log(ReturnA());
}
}
Updateにループしていない関数を入れて、クリック動作で返す
「Update」内にDLLの関数「LoopNotToUseWhile」入れ、イベント「onMouseDown」を用いる方法。
「onMouseDown」を用いることで、Unity上でDLL内の関数をループしている間にインクリメントできます。
また、この方法を用いる際には、「onMouseDown」の特性上「RunDLL.cs」を適用したオブジェクトにColliderを追加する必要があります。
当方の場合、Emptyオブジェクトでやっていたので、インスペクタから「Box Collider」を追加し、サイズを(100,100,1)にしました。
蛇足ですが、(100,100,100)だと上手く機能しなかったため、そのあたりは調節する必要があります。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// IntPtr型を使用するのに必要
using System;
// Dllの読み込みに必要
using System.Runtime.InteropServices;
public class RunDLL : MonoBehaviour
{
// Dll内の関数を宣言
[DllImport("DllForDebug")]
private extern static void ResetConstants();
[DllImport("DllForDebug")]
private extern static void Loop();
[DllImport("DllForDebug")]
private extern static void IncrementA();
[DllImport("DllForDebug")]
private extern static void IncrementB();
[DllImport("DllForDebug")]
private extern static int ReturnA();
[DllImport("DllForDebug")]
private extern static int ReturnB();
[DllImport("DllForDebug")]
private extern static void LoopNotToUseWhile();
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
LoopNotToUseWhile();
Debug.Log(ReturnA());
}
private void OnMouseDown()
{
IncrementA();
}
}
Update内にDebug.Logを入れているため、しっかり反応していればクリックした際に1ずつインクリメントした値が返ってくると思います。
これにより、イベントを使用することで任意のタイミングで変数をひっぱることができることが分かりました。
LateUpdateを実装する
「Update」のみで毎回同じ動作をしようとすると、順に処理されない、とかループされないといった動作を起こすことがあります。
「Update」関数の後に実行される関数「LateUpdate」を使用することで、確実に「Update」関数内の動作を実行した後に特定の動作をさせることができます。
イベント関数の実行順については、Unityのマニュアルを参照してください。
これは特定のタイミングで変数を抜き出すのではなく、毎回その値を使用して何か同様にループさせるのに適しています。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// IntPtr型を使用するのに必要
using System;
// Dllの読み込みに必要
using System.Runtime.InteropServices;
public class RunDLL : MonoBehaviour
{
// Dll内の関数を宣言
[DllImport("DllForDebug")]
private extern static void ResetConstants();
[DllImport("DllForDebug")]
private extern static void Loop();
[DllImport("DllForDebug")]
private extern static void IncrementA();
[DllImport("DllForDebug")]
private extern static void IncrementB();
[DllImport("DllForDebug")]
private extern static int ReturnA();
[DllImport("DllForDebug")]
private extern static int ReturnB();
[DllImport("DllForDebug")]
private extern static void LoopNotToUseWhile();
// Use this for initialization
void Start()
{
ResetConstants();
}
// Update is called once per frame
void Update()
{
LoopNotToUseWhile();
Debug.Log(ReturnA());
}
void LateUpdate()
{
IncrementA();
Debug.Log(ReturnA());
}
}
このコードを実行すると分かるように、確実にループ動作をし、適切な順でインクリメントしていることがわかります。