web-dev-qa-db-ja.com

C ++ DLLエクスポート:デコレート/マングルされた名前

基本的なC++を作成DLLおよびモジュール定義ファイル(MyDLL.def)を使用してエクスポートされた名前)コンパイル後、_dumpbin.exe_を使用してエクスポートされた関数名を確認します。

_SomeFunction
_

代わりに私はこれを見ます:

_SomeFunction = SomeFunction@@@23mangledstuff#@@@@
_

どうして?

エクスポートされた関数は装飾されていないように見えますが(特にModule Defファイルを使用しない場合と比べて)、他の要素はどうなっていますか?

商用アプリケーションのDLLに対して_dumpbin.exe_を使用すると、クリーンになります。

_SomeFunction
_

何もありません...

また、モジュール定義を削除し、「C」スタイルのエクスポートを使用して名前をエクスポートしようとしました。

_extern "C" void __declspec(dllexport) SomeFunction();
_

(単に "extern" C "を使用しても、エクスポートされた関数は作成されませんでした)

ただし、これでも同じ出力が作成されます。

_SomeFunction = SomeFunction@@@23mangledstuff#@@@@
_

#define dllexport __declspec(dllexport)オプションも試して、問題なくLIBを作成しました。ただし、C#アプリケーションでDLLを使用しているユーザーにLIBファイルを提供する必要はありません。

これは単純なバニラC++ですDLL(アンマネージコード)、C++でコンパイルされたもので、単純なヘッダーとコードしかありません。モジュール定義がないと、エクスポートされた関数が破損します(静的ライブラリを作成してLIBを使用できます)問題ありません。回避しようとしています。extern "C" __declspec(dllexport)[〜#〜] or [〜#〜]モジュール定義を使用すると、装飾されていない関数名...唯一の問題は、 "="が後に続き、関数の装飾されたバージョンのように見えることです。 "="の後にあるものを取り除きたい-または少なくともその理由を理解するある。

現状では、P/Invokeを使用してC#から関数を呼び出すことができると確信しています... "="の最後のジャンクを避けたいだけです。

プロジェクト/コンパイラの設定を変更する方法についての提案は自由ですが、私は標準のVisual Studio DLLテンプレートを使用しました-特別なことは何もありません。

26
Bob

デバッグ情報の生成をオフにすると、必要な情報を取得できます。プロジェクト+プロパティ、リンカー、デバッグ、デバッグ情報の生成=いいえ。

当然、これはリリースビルドに対してのみ行います。オプションがすでにそのように設定されている場合。

16
Hans Passant

.defファイルを使用する代わりに、このようにpragma commentを挿入するだけです

#pragma comment(linker, "/EXPORT:SomeFunction=_SomeFunction@@@23mangledstuff#@@@@")

編集:またはさらに簡単:関数の本体の内部で使用

#pragma comment(linker, "/EXPORT:"__FUNCTION__"="__FUNCDNAME__)

。 。 。修飾された関数名を見つけるのに問題がある場合。この最後のプラグマは、単純なマクロ定義でさらに削減できます。

40
wqw

関数の名前を壊したくない場合は、関数をextern "C"として宣言する必要があります。

13
jwismar

経験上、関数のシグネチャで__stdcallを使用する場合は注意してください。 __stdcallを使用すると、名前はある程度壊れたままになります(すぐにわかります)。明らかに、マングリングには2つのレベルがあります。1つはextern "C"がC++レベルで処理しますが、__stdcallが原因の別のレベルの名前マングリングは処理しません。余分なマングリングは明らかにオーバーロードに関連しています-しかし、私はそれが確かではありません。

11
jjones

古いスレッドに返信して申し訳ありませんが、答えとしてマークされたものは私にとってはうまくいきませんでした。

多くの人が指摘しているように、外部「C」の装飾は重要です。 「プロジェクト/プロパティ/リンカー/デバッグ/デバッグ情報の生成」設定を変更しても、デバッグビルドモードまたはリリースビルドモードで生成されたマングル名にはまったく違いがありませんでした。

セットアップ:Visual C++クラスライブラリプロジェクトをコンパイルするVS2005。コンパイルされた.dll出力をMicrosoftのDependency Walkerツールでチェックしていました。

これは私のために働いたレシピの例です...

Project.h:

#define DllExport extern "C" __declspec( dllexport )

DllExport bool API_Init();
DllExport bool API_Shutdown();

Project.cppで:

#include "project.h"

bool API_Init()
{
  return true;
}

bool API_Shutdown()
{
  return true;
}

次に、C#マネージコードclass.csから呼び出されます。

using System.Runtime.Interopservices;
namespace Foo
{
    public class Project
    {
        [DllImport("project.dll")]
        public static extern bool API_Init();

        [DllImport("project.dll")]
        public static extern bool API_Shutdown();
    }
}

上記を実行すると、[デバッグ情報の生成]設定に関係なく、デバッグモードとリリースモードの両方でマングル名が防止されました。幸運を。

6
AlwaysLearning

マングリングがない場合でも、32ビットと64ビットのビルドでは、extern "C"を使用しても名前のエクスポートが異なります。 DEPENDS.EXEで確認してください。

これは、LoadLibrary + GetProcAdressを実行して関数にアクセスするすべてのクライアントにとって大きな問題になる可能性があります。

したがって、他のすべてのものの上に、次のようにモジュール定義ファイルを使用します。

LIBRARY MYDLL
EXPORTS
myFunction=myFunction

ええ、維持するのは少し面倒ですが、エクスポートした関数を1日にいくつ作成しますか?

さらに、DLLはC++クラスではなく関数をエクスポートし、ほとんどのプログラミング環境から呼び出し可能にしたいので、通常は以下のようにマクロを変更します。

#ifdef WTS_EXPORTS
#define WTS_API(ReturnType) extern "C" __declspec(dllexport) ReturnType WINAPI
#else
#define WTS_API(ReturnType) extern "C" __declspec(dllimport) ReturnType WINAPI
#endif

WTS_API(int) fnWTS(void);

最後の行は数年前にVisualAssistXを混乱させていましたが、それが適切に消化されているかどうかはわかりません:-)

5

コードと#pragmaを使用して関数名を強制しようとした回数はわかっています。そして、最後には常にモジュール定義ファイル(* .def)を使用して、まったく同じことで終わります。そしてここに理由があります:

//---------------------------------------------------------------------------------------------------
// Test cases built using VC2010 - Win32 - Debug / Release << doesn't matter
//---------------------------------------------------------------------------------------------------
// SET: Project > Properties > Linker > Debugging > Generate Debug Info = Yes (/DEBUG)
//  || (or, also doesn't matter)
// SET: Project > Properties > Linker > Debugging > Generate Debug Info = No + delete PDB file!

extern "C" __declspec(dllexport) void SetCallback(LPCALLBACK function);
> SetCallback

extern "C" __declspec(dllexport) void __stdcall SetCallback(LPCALLBACK function);
> _SetCallback@4

__declspec(dllexport) void SetCallback(LPCALLBACK function);
> ?SetCallback@@YAXP6AXHPADPAX@Z@Z

__declspec(dllexport) void __stdcall SetCallback(LPCALLBACK function);
> ?SetCallback@@YGXP6GXHPADPAX@Z@Z    

//---------------------------------------------------------------------------------------------------
// this also big is nonsense cause as soon you change your calling convention or add / remove
// extern "C" code won't link anymore.

// doesn't work on other cases
#pragma comment(linker, "/EXPORT:SetCallback")
extern "C" __declspec(dllexport) void SetCallback(LPCALLBACK function);

// doesn't work on other cases
#pragma comment(linker, "/EXPORT:SetCallback=SetCallback")
extern "C" __declspec(dllexport) void SetCallback(LPCALLBACK function);

// doesn't work on other cases / creates alias
#pragma comment(linker, "/EXPORT:SetCallback=_SetCallback@4")
extern "C" __declspec(dllexport) void __stdcall SetCallback(LPCALLBACK function);

// doesn't work on other cases / creates alias
#pragma comment(linker, "/EXPORT:SetCallback=?SetCallback@@YAXP6AXHPADPAX@Z@Z")
__declspec(dllexport) void SetCallback(LPCALLBACK function);

// doesn't work on other cases / creates alias
#pragma comment(linker, "/EXPORT:SetCallback=?SetCallback@@YGXP6GXHPADPAX@Z@Z")
__declspec(dllexport) void __stdcall SetCallback(LPCALLBACK function);

//---------------------------------------------------------------------------------------------------
// So far only repetable case is using Module-Definition File (*.def) in all possible cases:
EXPORTS
  SetCallback

extern "C" __declspec(dllexport) void SetCallback(LPCALLBACK function);
> SetCallback

extern "C" __declspec(dllexport) void __stdcall SetCallback(LPCALLBACK function);
> SetCallback

__declspec(dllexport) void SetCallback(LPCALLBACK function);
> SetCallback

__declspec(dllexport) void __stdcall SetCallback(LPCALLBACK function);
> SetCallback

// And by far this is most acceptable as it will reproduce exactly same exported function name 
// using most common compilers. Header is dictating calling convention so not much trouble for
// other sw/ppl trying to build Interop or similar.

なぜ誰もこれをしなかったのか、すべてのケースをテストするのに10分しかかからなかった。

4
SoLaR

基本的に、C++で関数を使用する場合、オーバーロードなどの言語機能を容易にするために、名前の一部にシグネチャなどが含まれるようになりました。

__declspec(dllexport)を使用してDLLを記述した場合は、libも生成されます。そのlibにリンクすると、起動時にCRTによって自動的にリンクされ、関数が登録されます時間(すべてのインポートをエクスポートに変更することを覚えている場合)このシステムを使用している場合は、名前のマングリングについて知る必要はありません。

0
Puppy

someFunction @@@ 23mangledstuff#@@@@は、C++関数の型とクラスを与えるためにマングルされます。単純なエクスポートは、Cから呼び出し可能な関数です。つまり、Cで記述されているか、C++コードでextern "C"と宣言されています。単純なインターフェイスが必要な場合は、エクスポートする関数をC型だけで使用し、それらを作成する必要があります。グローバル名前空間の非メンバー関数。

0
Mark

万が一の輸出をめぐる何百ものワッフルのラインからそれが明らかでなかった場合に備えて。ここに私の2cの価値があります:)

VS 2012を使用してWin32Project2というプロジェクトを作成し、ウィザードで[すべてのシンボルをエクスポート]を選択した後。 Win32Project2.cppおよびWin32project2.hという2つのファイルが必要です。

どちらも、エクスポート可能な変数の例とエクスポートされた関数の例を参照します。

Win32Project2.hでは、次のようになります。

#ifdef WIN32PROJECT2_EXPORTS
#define WIN32PROJECT2_API __declspec(dllexport)
#else
#define WIN32PROJECT2_API __declspec(dllimport)
#endif

extern WIN32PROJECT2_API int nWin32Project2;
WIN32PROJECT2_API int fnWin32Project2(void);

最後の2行を変更しないようにするには、extern "C"宣言を次のように変更します。

extern "C" WIN32PROJECT2_API int nWin32Project2;
extern "C" WIN32PROJECT2_API int fnWin32Project2(void);

Win32Project2.cppには、次のデフォルト定義もあります。

// This is an example of an exported variable
WIN32PROJECT2_API int nWin32Project2=0;

// This is an example of an exported function.
WIN32PROJECT2_API int fnWin32Project2(void)
{
    return 42;
}

これらを変更するには、次のように変更します。

// This is an example of an exported variable
extern "C" WIN32PROJECT2_API int nWin32Project2=0;

// This is an example of an exported function.
extern "C" WIN32PROJECT2_API int fnWin32Project2(void)
{
    return 42;
}

本質的に、リンカにマングル化されていないCのような名前を生成させるために、宣言の前にextern "C"プレフィックスを使用する必要があります。

余分な難読化の一部にマングル名を使用する場合(マングリング情報が誰かに役立つ場合)、VCコマンドラインから「dumpbin/exports Win32Project2.dll」を使用して、実際の参照名を検索します。 "?fnWind32Project2 @ [param bytes] @ [other info]の形式になります。DLLコマンドを実行すると、ボートが浮かない場合は、他のVC表示ツールもあります。

MSがこの規則をデフォルトにしない理由はまさに謎です。実際のマングリング情報は、検証やデバッグには役立つかもしれませんが、それ以外の場合は不快な何か(バイト単位のパラメーターサイズなど)を意味します。

上記のDLL関数をC#プロジェクト(この場合は、ボタン "button1"を含むフォームを持つ基本的なC#Windowsアプリケーション)にインポートするには、次のサンプルコードを使用します。

using System.Runtime.InteropServices;



    namespace AudioRecApp
    {

      public partial class Form1 : Form
      {
        [ DllImport("c:\\Projects\test\Debug\Win32Projects2.dll")] 
        public static extern int fnWin32Project2();

        public Form1()
        {
          InitializeComponent();
        }


        private void button1_Click(object sender, EventArgs e)
        {
          int value;

          value = fnWin32Project2();
        }
      }
    }
0
PGP