41936


VisualStudioでTestという名前でプロジェクトを作成し，dlltest.cppとdlltest.hに下記のように書き込み，x64,Releaseでビルドし，作成されたTest.dllというファイルをunityプロジェクトのAssets/Pluginsにコピーしました．unityでシーン上に空のオブジェクトを作成し，そこに下記に示すDllという名前のスクリプトを入れ実行すると，  
EntryPointNotFoundException: Return  
Dll.Start () (at Assets/Dll.cs:13)  
とエラーが出てしまいます．  
自分では何が原因なのか皆目見当がつきません．解決策が分かる方がおりましたら教えてください．

ちなみに使用しているのはVisualStudio2017,unity2017 3.0f3 Personal(64bit) です．

dlltest.cpp

[code]

    #include"dlltest.h"
    int Return() {
        return 10;
    }
    
[/code]

dlltest.h

[code]

    #pragma once
    
    extern "C" {
        int Return();
    }
    
[/code]

Dll

[code]

    using UnityEngine;
    using System.Runtime.InteropServices;
    using System;
    
    public class Dll : MonoBehaviour {
    
        [DllImport("Test")] private static extern int Return();
    
        // Use this for initialization
        void Start () {
            Debug.Log(Return());
        }
    }
    
[/code]


CC BY-SA 3.0
2018-02-21T17:57:56.687
41904
2018-02-22T22:18:14.507
2018-02-22T22:18:14.507
4236
27493
question
c++ unity3d visual-studio
DLLを外部から呼び出し可能なように構成したい


最終的な実現方法はある程度まとまっているのですが、そこに辿り着くためにはかなりややこしい話が絡んできます。

C++言語はC言語と互換性があり、`extern
"C"`とはC言語から呼び出し・静的リンクするための記述です。外部からのDLL呼び出し、つまり動的リンクには異なる記述が必要になります。  
その際、動的リンクに使用する関数名と静的リンクの関数名の両方を考慮する必要があり、更に静的リンクの関数名は単純にC++言語での関数名というわけではなく[呼び出し規約](https://ja.wikipedia.org/wiki/%E5%91%BC%E5%87%BA%E8%A6%8F%E7%B4%84)が影響してきます。  
呼び出し規約は引数や戻り値の受け渡し方法を決定するものですので、呼び出し側と一致させないと関数呼び出しに失敗します。呼び出し規約には`cdecl`、`fastcall`、`stdcall`などがあり例えば`stdcall`の場合、C++言語で

[code]

    int __stdcall Return() {
        ...;
    }
    
[/code]

と記述した場合、C#言語では

[code]

    [DllImport("Test", CallingConvention = CallingConvention.StdCall)]
    private static extern int Return();
    
[/code]

といった具合に対応させる必要があります。  
x86で動的リンクする場合は上記`stdcall`が推奨されています。x64では呼び出し規約が統一されていてどれを選択しても影響はないのですが、x86も考慮して記述しておくことをお勧めします。

呼び出し規約と静的リンクの関数名が定まったところで本題の動的リンクですが、記述方法が３種類あります。

### DEFファイル

１つ目の方法は[DEFファイルを作成](https://msdn.microsoft.com/ja-
jp/library/d91k01sh.aspx)することです。拡張子を`.DEF`とし内容は次のように外部から呼び出し可能とする関数名を列挙します。

[code]

    EXPORTS
        Return
    
[/code]

この方法は簡単ではありますが、C++ソースコードからどの関数がエクスポートされているか判別がつかないため規模が大きくなるとミスを誘発します。

### __declspec(dllexport)

`__declspec(dllexport)`というキーワードが用意されています。

[code]

    __declspec(dllexport) int __stdcall Return() {
        ...;
    }
    
[/code]

非常に簡単に記述できますが、１つ欠点がありエクスポートされる関数名は静的リンクの関数名がそのまま使われるため[マングル化名](https://ja.wikipedia.org/wiki/%E5%90%8D%E5%89%8D%E4%BF%AE%E9%A3%BE)となり（上記例では`?Return@@YAHXZ`）、C#からは呼び出しが煩雑になります。

[code]

    [DllImport("Test", EntryPoint = "?Return@@YAHXZ", CallingConvention = CallingConvention.StdCall)]
    private static extern int Return();
    
[/code]

### #pragma comment(linker)

最後の方法ですが

[code]

    int __stdcall Return() {
        #pragma comment(linker, "/EXPORT:Return=?Return@@YAHXZ")
        ...;
    }
    
[/code]

とすることで静的リンクの関数名`?Return@@YAHXZ`が動的リンクする際`Return`で呼び出し可能になります。

[code]

    [DllImport("Test", CallingConvention = CallingConvention.StdCall)]
    private static extern int Return();
    
[/code]

しかしまだ記述方法が煩雑です。これを改善するには定義済みマクロを使用します。

[code]

    int __stdcall Return() {
        #pragma comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)
        ...;
    }
    
[/code]

だいぶ楽になりましたが長いですし、実はコンパイルはできてもVisual Studio
IntelliSenseが誤ってエラーを報告します。これらすべてを解決するためには

[code]

    #ifdef __EDG__
    #define DLLEXPORT
    #else
    #define DLLEXPORT __pragma(comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__))
    #endif
    
    int __stdcall Return() {
        DLLEXPORT;
        ...;
    }
    
[/code]

と記述することで、一通りの問題を解決しつつ、簡単な記述が実現できます。これらについては[ブログ](https://sayurin.blogspot.jp/2016/08/dllexport.html)で紹介しています。


CC BY-SA 3.0
2018-02-22T22:14:34.333
41936
2018-02-22T22:14:34.333
4236
41904
answer