C++ DLL Export: украшенные / искаженные имена

Создана базовая C++ DLL и экспортированные имена с использованием файла определения модуля (MyDLL.def). После компиляции я проверяю экспортированные имена функций, используя dumpbin.exeЯ ожидаю увидеть:

SomeFunction

но я вижу это вместо этого:

SomeFunction = SomeFunction@@@23mangledstuff#@@@@

Зачем?

Экспортируемая функция выглядит неукрашенной (особенно по сравнению с тем, что не используется файл Module Def), но что с остальными?

Если я использую dumpbin.exe по отношению к DLL из любого коммерческого приложения, вы получаете чистый:

SomeFunction

и ничего больше...

Я также попытался удалить определение модуля и экспортировать имена, используя стиль экспорта "C", а именно:

extern "C" void __declspec(dllexport) SomeFunction();

(Простое использование "extern "C"не создало экспортированную функцию)

Тем не менее, это все еще создает тот же вывод, а именно:

SomeFunction = SomeFunction@@@23mangledstuff#@@@@

Я также попробовал #define dllexport __declspec(dllexport) вариант и создал LIB без проблем. Однако я не хочу предоставлять LIB-файл людям, использующим DLL в своем приложении C#.

Это простая ванильная C++ DLL (неуправляемый код), скомпилированная с C++, не более чем простой заголовок и код. Без Module Def я получаю искаженные экспортированные функции (я могу создать статическую библиотеку и без проблем использовать LIB. Я пытаюсь избежать этого). Если я использую extern "C" __declspec(dllexport) ИЛИ Определение модуля Я получаю то, что выглядит как недекорированное имя функции... единственная проблема в том, что за ним следует "=" и что-то похожее на декорированную версию функции. Я хочу избавиться от вещей после "=" - или хотя бы понять, почему они там есть.

Я уверен, что могу вызвать функцию из C# с помощью P/Invoke... Я просто хочу избежать этого мусора в конце "=".

Я открыт для предложений о том, как изменить настройки проекта / компилятора, но я просто использовал стандартный шаблон Visual Studio DLL - ничего особенного.

10 ответов

Решение

Вы можете получить то, что вы хотите, отключив генерацию отладочной информации. Project + Properties, Linker, Debugging, Generate Debug Info = No.

Естественно, вы хотите сделать это только для сборки Release. Где опция уже установлена ​​таким образом.

Вместо использования файла.def просто вставьте pragma comment как это

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

Редактировать: или даже проще: внутри тела функции использовать

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

,,, если у вас возникли проблемы с поиском имени оформленной функции. Эта последняя прагма может быть дополнительно уменьшена с помощью простого макроопределения.

Вы должны объявить функции как extern "C" если вы не хотите, чтобы их имена были искажены.

Из опыта, будьте осторожны, если вы используете __stdcall в вашей функции подпись. С __stdcallимя останется искаженным до некоторой степени (вы узнаете достаточно быстро). По-видимому, есть два уровня искажения, один extern "C" имеет дело с уровнем C++, но не имеет дело с другим уровнем искажения имен, вызванным __stdcall, Дополнительное искажение, по-видимому, имеет отношение к перегрузке, но я не уверен в этом.

Даже без искажения, 32-битные и 64-битные сборки экспортируют имена по-разному, даже с внешним "C". Проверьте это с DEPENDS.EXE.

Это может означать большие проблемы для любого клиента, который делает LoadLibrary+GetProcAdress для доступа к вашей функции.

Итак, поверх всех остальных используйте файл определения модуля следующим образом:

LIBRARY MYDLL
EXPORTS
myFunction=myFunction

Да, это немного сложно поддерживать, но сколько же вы экспортируете функций в день?

Более того, я обычно меняю макросы, как показано ниже, так как мои библиотеки 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 пару лет назад, я не знаю, правильно ли она сейчас ее переваривает:-)

Я знаю, сколько раз я пытался форсировать имена функций, используя код и #pragma's. И я всегда заканчиваю одним и тем же, используя файл определения модуля (*.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 минут, чтобы проверить все случаи.

Извините за ответ на старую ветку, но то, что было отмечено как ответ, у меня не сработало.

Как указывалось рядом людей, внешняя отделка "С" важна. Изменение параметра "Project / Properties / Linker / Debugging / Generate debug info" не имело никакого значения для искаженных имен, сгенерированных для меня в режиме сборки Debug или Release.

Установка: VS2005 компилирует проект библиотеки классов Visual C++. Я проверял скомпилированный вывод.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();
    }
}

Выполнение вышеуказанного предотвратило искажение имен как в режиме отладки, так и в режиме выпуска, независимо от параметра "Создать информацию отладки". Удачи.

По сути, когда вы используете функции в C++, части их имен теперь включают свою сигнатуру и тому подобное, чтобы облегчить языковые функции, такие как перегрузка.

Если вы пишете DLL с помощью __declspec(dllexport), то она также должна создать lib. Ссылка на эту библиотеку, и вы будете автоматически связаны и функции, зарегистрированные CRT во время запуска (если вы помните, чтобы изменить все ваши импорт на экспорт). Вам не нужно знать об искажении имени, если вы используете эту систему.

SomeFunction@@@23mangledstufF#@@@@ искажен, чтобы дать типы и класс функции C++. Простые экспорты - это функции, которые можно вызывать из C, т. Е. Они написаны на C или объявлены extern "C" в коде C++. Если вам нужен простой интерфейс, вы должны сделать так, чтобы экспортируемые вами функции использовали только типы C и сделали их функции, не являющиеся членами в глобальном пространстве имен.

На случай, если из сотен линий вафли не было ясно, что касается искалеченного экспорта. Вот мой 2с стоит:)

После создания проекта Win32Project2 с использованием VS 2012 и выбора экспорта всех символов в мастере. У вас должно быть 2 файла с именами Win32Project2.cpp и Win32project2.h

Оба из них будут ссылаться на пример экспортируемой переменной и пример экспортируемой функции.

В 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);

Чтобы отменить изменение двух последних строк, чтобы превратить объявления "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;
}

По сути, вы должны использовать префикс extern "C" перед объявлениями, чтобы заставить компоновщик генерировать неоправданные C-подобные имена.

Если вы предпочитаете использовать искаженные имена для этого дополнительного запутывания (в случае, если информация искажения кому-то полезна), используйте "dumpbin /exports Win32Project2.dll" из командной строки VC, чтобы найти действительные ссылочные имена. Он будет иметь форму "? FnWind32Project2@[param bytes]@[other info] . Существуют также другие инструменты просмотра DLL, если запуск командной оболочки VC не поддерживает вашу лодку.

Точно, почему MS по умолчанию не придерживается этого соглашения, остается загадкой. Фактическая информация искажения означает что-то (например, размер параметра в байтах и ​​более), что может быть полезно для проверки и отладки, но в остальном бесполезно.

Чтобы импортировать указанную выше функцию DLL в проект C# (в данном случае это базовое приложение Windows на C# с формой, содержащей кнопку "button1"), приведем пример кода:

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();
        }
      }
    }
Другие вопросы по тегам