DLL을 만들 때 모든 기호 내보내기
VS2005를 사용하면 DLL을 만들고 __declspec (dllexport)을 모든 곳에 추가하지 않고 .def 파일을 직접 만들지 않고도 모든 기호를 자동으로 내보내고 싶습니다. 이 작업을 수행하는 방법이 있습니까?
할 수 있습니다 ...
여기서하는 방법은 링커의 / DEF 옵션을 사용하여 내보내기 목록을 포함하는 "모듈 정의 파일" 을 전달하는 것입니다. 나는 당신이이 파일들에 대해 알고 있다는 것을 당신의 질문에서 봅니다. 그러나 우리는 그것을 손으로하지 않습니다. 내보내기 목록 자체는 dumpbin / LINKERMEMBER 명령에 의해 생성되며 간단한 스크립트를 통해 모듈 정의 파일 형식으로 출력을 조작합니다.
설정하는 데 많은 작업이 필요하지만 Windows에서 Unix 용 dllexport 선언없이 생성 된 코드를 컴파일 할 수 있습니다.
짧은 답변
새 버전의 CMake (모든 버전 cmake-3.3.20150721-g9cd2f-win32-x86.exe 이상)를 사용하여 수행 할 수 있습니다.
현재 개발 브랜치에 있습니다. 나중에이 기능은 cmake-3.4의 릴리스 버전에 추가됩니다.
cmake dev에 링크 :
기술을 설명하는 기사 링크 :
새로운 CMake export all 기능을 사용하여 declspec ()없이 Windows에서 dll 생성
예제 프로젝트에 연결 :
cmake_windows_export_all_symbols
긴 대답
주의 : 아래의 모든 정보는 MSVC 컴파일러 또는 Visual Studio와 관련이 있습니다.
Linux의 gcc 또는 Windows의 MinGW gcc 컴파일러와 같은 다른 컴파일러를 사용하는 경우 gcc 컴파일러는 기본적으로 MSVC 또는 Intel Windows 컴파일러 대신 동적 라이브러리 (dll)의 모든 심볼을 내보내기 때문에 심볼을 내 보내지 않아서 링크 오류가 발생하지 않습니다. .
Windows에서는 dll에서 심볼을 명시 적으로 내 보내야합니다.
이에 대한 자세한 정보는 링크를 통해 제공됩니다.
따라서 MSVC (Visual Studio 컴파일러)를 사용하여 dll에서 모든 기호를 내보내려면 두 가지 옵션이 있습니다.
- 클래스 / 함수 정의에 __declspec (dllexport) 키워드를 사용하십시오.
- 모듈 정의 (.def) 파일을 만들고 DLL을 빌드 할 때 .def 파일을 사용합니다.
1. 클래스 / 함수 정의에 __declspec (dllexport) 키워드를 사용합니다.
1.1. 사용할 클래스 또는 메서드에 "__declspec (dllexport) / __declspec (dllimport)"매크로를 추가합니다. 따라서 모든 클래스를 내보내려면이 매크로를 모든 클래스에 추가해야합니다.
이에 대한 자세한 정보는 링크로 제공됩니다.
__declspec (dllexport)을 사용하여 DLL에서 내보내기
사용 예 ( "Project"를 실제 프로젝트 이름으로 대체) :
// ProjectExport.h
#ifndef __PROJECT_EXPORT_H
#define __PROJECT_EXPORT_H
#ifdef USEPROJECTLIBRARY
#ifdef PROJECTLIBRARY_EXPORTS
#define PROJECTAPI __declspec(dllexport)
#else
#define PROJECTAPI __declspec(dllimport)
#endif
#else
#define PROJECTAPI
#endif
#endif
그런 다음 모든 클래스에 "PROJECTAPI"를 추가합니다. dll에서 기호를 내보내거나 가져 오려는 경우에만 "USEPROJECTLIBRARY"를 정의하십시오. dll에 대해 "PROJECTLIBRARY_EXPORTS"를 정의하십시오.
수업 내보내기의 예 :
#include "ProjectExport.h"
namespace hello {
class PROJECTAPI Hello {}
}
함수 내보내기의 예 :
#include "ProjectExport.h"
PROJECTAPI void HelloWorld();
주의 : "ProjectExport.h"파일을 포함하는 것을 잊지 마십시오.
1.2. C 함수로 내 보냅니다. 컴파일 코드가 C로 작성된 경우 C ++ 컴파일러를 사용하는 경우 함수 앞에 extern "C"를 추가하여 이름 변경을 제거 할 수 있습니다.
C ++ 이름 맹 글링에 대한 자세한 정보는 링크를 통해 제공됩니다.
사용 예 :
extern "C" __declspec(dllexport) void HelloWorld();
이에 대한 자세한 정보는 링크로 제공됩니다.
2. 모듈 정의 (.def) 파일을 만들고 DLL을 빌드 할 때 .def 파일을 사용합니다.
이에 대한 자세한 정보는 링크로 제공됩니다.
또한 .def 파일을 만드는 방법에 대한 세 가지 접근 방식을 설명합니다.
2.1. C 함수 내보내기
이 경우 .def 파일에 함수 선언을 직접 추가 할 수 있습니다.
사용 예 :
extern "C" void HelloWorld();
.def 파일의 예 (__cdecl 명명 규칙) :
EXPORTS
_HelloWorld
2.2. 정적 라이브러리에서 심볼 내보내기
"user72260"이 제안한 접근 방식을 시도했습니다.
그는 말했다 :
- 첫째, 정적 라이브러리를 만들 수 있습니다.
- 그런 다음 "dumpbin / LINKERMEMBER"를 사용하여 정적 라이브러리에서 모든 기호를 내 보냅니다.
- 출력을 구문 분석하십시오.
- 모든 결과를 .def 파일에 넣으십시오.
- .def 파일로 dll을 만듭니다.
이 접근 방식을 사용했지만 항상 두 개의 빌드 (하나는 정적이고 다른 하나는 동적 라이브러리)를 만드는 것은 그리 편리하지 않습니다. 그러나 저는 인정해야합니다.이 접근법은 정말 효과적입니다.
2.3. .obj 파일에서 또는 CMake의 도움으로 기호 내보내기
2.3.1. CMake 사용
중요 공지 : 클래스 나 함수에 대한 내보내기 매크로가 필요하지 않습니다!
중요 공지 : 이 방법을 사용할 때는 / GL ( 전체 프로그램 최적화 )을 사용할 수 없습니다 !
- "CMakeLists.txt"파일을 기반으로 CMake 프로젝트를 만듭니다.
- "CMakeLists.txt"파일에 다음 행을 추가하십시오. set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
- 그런 다음 "CMake (cmake-gui)"를 사용하여 Visual Studio 프로젝트를 만듭니다.
- 프로젝트를 컴파일하십시오.
사용 예 :
루트 폴더
CMakeLists.txt (루트 폴더)
cmake_minimum_required(VERSION 2.6)
project(cmake_export_all)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(dir ${CMAKE_CURRENT_SOURCE_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${dir}/bin")
set(SOURCE_EXE main.cpp)
include_directories(foo)
add_executable(main ${SOURCE_EXE})
add_subdirectory(foo)
target_link_libraries(main foo)
main.cpp (루트 폴더)
#include "foo.h"
int main() {
HelloWorld();
return 0;
}
Foo 폴더 (루트 폴더 / Foo 폴더)
CMakeLists.txt (Foo 폴더)
project(foo)
set(SOURCE_LIB foo.cpp)
add_library(foo SHARED ${SOURCE_LIB})
foo.h (Foo 폴더)
void HelloWorld();
foo.cpp (Foo 폴더)
#include <iostream>
void HelloWorld() {
std::cout << "Hello World!" << std::endl;
}
예제 프로젝트에 다시 연결하십시오.
cmake_windows_export_all_symbols
CMake는 "2.2. 정적 라이브러리에서 심볼 내보내기"방식과 다른 방식을 사용합니다.
다음을 수행합니다.
1) 빌드 디렉토리에 "objects.txt"파일을 생성하고 .obj 파일 정보는 dll에서 사용합니다.
2) dll을 컴파일합니다. 즉, .obj 파일을 만듭니다.
3) "objects.txt"파일 정보를 기반으로 .obj 파일에서 모든 기호를 추출합니다.
사용 예 :
DUMPBIN /SYMBOLS example.obj > log.txt
이에 대한 자세한 정보는 링크로 제공됩니다.
4) .obj 파일 정보에서 추출한 구문 분석.
제 생각에는 "__cdecl / __ fastcall", "SECTx / UNDEF"기호 필드 (세 번째 열), "외부 / 정적"기호 필드 (5 번째 열), "??", "? " .obj 파일을 구문 분석하기위한 정보.
CMake가 .obj 파일을 정확히 구문 분석하는 방법을 모르겠습니다. 그러나 CMake는 오픈 소스이므로 관심이 있는지 확인할 수 있습니다.
CMake 프로젝트에 연결 :
5) 내 보낸 모든 기호를 .def 파일에 넣습니다.
6) .def 생성 파일을 사용하여 dll을 연결합니다.
4) -5) 단계는 .obj 파일을 구문 분석하고 CMake가 "Pre-Link event"의 도움으로 .def 파일을 연결하고 사용하기 전에 .def 파일을 만듭니다. "Pre-Link 이벤트"가 발생하는 동안 원하는 프로그램을 호출 할 수 있습니다. 따라서 "CMake 사용" "Pre-Link 이벤트"의 경우 .def 파일을 저장할 위치와 "objects.txt"파일 및 인수 "-E __create_def"에 대한 다음 정보를 사용하여 CMake를 호출합니다. "set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)"으로 CMake Visusal Studio 프로젝트를 생성하여이 정보를 확인한 다음 ".vcxproj"프로젝트 파일에서 dll을 확인할 수 있습니다.
"set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)"또는 "set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF)"을 사용하여 프로젝트를 컴파일하려고하면 기호가 dll에서 내보내지지 않기 때문에 링크 오류가 발생합니다.
이에 대한 자세한 정보는 링크로 제공됩니다.
2.3.2. CMake 사용없이
CMake를 사용하지 않고 직접 .obj 파일을 구문 분석하는 작은 프로그램을 만들 수 있습니다. Hovewer, 저는 CMake가 특히 크로스 플랫폼 개발에 매우 유용한 프로그램이라는 것을 인정해야합니다.
.lib 파일에서 "dumpbin / linkermember"의 출력을 구문 분석하는 작은 프로그램을 작성했습니다. 하나의 DLL에서 내보낼 수있는 함수 참조가 8,000 개 이상 있습니다.
DLL에서 수행하는 문제는 내 보낸 정의없이 DLL을 한 번 연결하여 .lib 파일을 만든 다음 .def를 생성해야한다는 것입니다. 즉, 이제 DLL을 .def 파일과 다시 연결해야합니다. 참조를 내 보냅니다.
정적 라이브러리로 작업하는 것이 더 쉽습니다. 모든 소스를 정적 라이브러리로 컴파일하고, dumbin을 실행하고, 작은 프로그램으로 .def를 생성 한 다음, 내보내기 이름을 사용할 수 있으므로 라이브러리를 DLL로 함께 연결합니다.
불행히도 우리 회사는 내가 당신에게 소스를 보여 주도록 허용하지 않을 것입니다. 관련된 작업은 덤프 출력에서 def 파일에 필요하지 않은 "공용 기호"를 인식하는 것입니다. NULL_IMPORT_DESCRIPTOR, NULL_THUNK_DATA, __imp * 등 많은 참조를 버려야합니다.
자세한 답변을 위해 @Maks에게 감사드립니다 .
아래는 obj에서 def 파일을 생성하기 위해 Pre-Link 이벤트에서 사용한 예입니다. 누군가에게 도움이 되길 바랍니다.
dumpbin /SYMBOLS $(Platform)\$(Configuration)\mdb.obj | findstr /R "().*External.*mdb_.*" > $(Platform)\$(Configuration)\mdb_symbols
(echo EXPORTS & for /F "usebackq tokens=2 delims==|" %%E in (`type $(Platform)\$(Configuration)\mdb_symbols`) do @echo %%E) > $(Platform)\$(Configuration)\lmdb.def
기본적으로 객체 (mdb.obj)와 grepped mdb_ * 함수 중 하나를 가져 왔습니다. 그런 다음 들여 쓰기를위한 공백의 양을 고려하여 이름 만 유지하기 위해 출력을 구문 분석했습니다 (하나는 토큰으로 분할 한 후 다른 하나는 에코로 나눕니다. 그래도 문제가되는지 모르겠습니다).
실제 스크립트는 아마도 좀 더 복잡 할 것입니다.
DLL을 만들고 __declspec (dllexport)을 모든 곳에 추가하지 않고 .def 파일을 직접 만들지 않고 모든 기호를 자동으로 내보내고 싶습니다. 이 작업을 수행하는 방법이 있습니까?
이것은 늦은 답변이지만 섹션 (2)에서 Maks의 답변에 대한 세부 정보를 제공합니다. 또한 스크립트를 피하고라는 C ++ 프로그램을 사용합니다 dump2def. 의 소스 코드 dump2def는 다음과 같습니다.
마지막으로 아래 단계에서는 실행 된 Windows 터미널 인 Visual Studio 개발자 프롬프트 에서 작업한다고 가정합니다 vcvarsall.bat. 당신은 같은 빌드 도구를 확인해야합니다 cl.exe, lib.exe, link.exe과 nmake.exe에 경로입니다.
이에 대한 자세한 정보는 링크로 제공됩니다.
사용 아래 지침 :
static.lib-정적 라이브러리 아카이브 (Linux의 경우 * .a 파일)dynamic.dll-동적 라이브러리 (Linux의 경우 * .so 파일)import.lib-동적 라이브러리 (Windows에서 라이브러리 가져 오기)
또한 DLL에서 모든 것을 내보내더라도 클라이언트는 사용 declspec(dllimport)하는 모든 심볼 (클래스, 함수 및 데이터)에 대해 계속 사용해야합니다. MSDN에서도 참조하십시오.
먼저 개체를 가져 와서 정적 아카이브를 만듭니다.
AR = lib.exe
ARFLAGS = /nologo
CXX_SRCS = a.cpp b.cpp c.cpp ...
LIB_OBJS = a.obj b.obj c.obj ...
static.lib: $(LIB_OBJS)
$(AR) $(ARFLAGS) $(LIB_OBJS) /out:$@
둘째, dumpbin.exe /LINKERMEMEBER아카이브 에서 실행 하여 파일을 만듭니다 *.dump.
dynamic.dump:
dumpbin /LINKERMEMBER static.lib > dynamic.dump
셋째, 파일 에서 실행 dump2def.exe하여 *.dump파일을 생성 *.def합니다. 의 소스 코드 dump2def.exe는 다음과 같습니다.
dynamic.def: static.lib dynamic.dump
dump2def.exe dynamic.dump dynamic.def
넷째, DLL을 빌드합니다.
LD = link.exe
LDFLAGS = /OPT:REF /MACHINE:X64
LDLIBS = kernel32.lib
dynamic.dll: $(LIB_OBJS) dynamic.def
$(LD) $(LDFLAGS) /DLL /DEF:dynamic.def /IGNORE:4102 $(LIB_OBJS) $(LDLIBS) /out:$@
/IGNORE:4102이 경고를 피하기 위해 사용됩니다. 이 경우 예상됩니다.
dynamic.def : warning LNK4102: export of deleting destructor 'public: virtual v
oid * __ptr64 __cdecl std::exception::`scalar deleting destructor'(unsigned int)
__ptr64'; image may not run correctly
때 dynamic.dll레시피가 호출, 그것은 생성 dynamic.lib가져 오기 파일과 dynamic.exp도 파일을 :
> cls && nmake /f test.nmake dynamic.dll
...
Creating library dynamic.lib and object dynamic.exp
과:
C:\Users\Test\testdll>dir *.lib *.dll *.def *.exp
Volume in drive C is Windows
Volume Serial Number is CC36-23BE
Directory of C:\Users\Test\testdll
01/06/2019 08:33 PM 71,501,578 static.lib
01/06/2019 08:33 PM 11,532,052 dynamic.lib
Directory of C:\Users\Test\testdll
01/06/2019 08:35 PM 5,143,552 dynamic.dll
Directory of C:\Users\Test\testdll
01/06/2019 08:33 PM 1,923,070 dynamic.def
Directory of C:\Users\Test\testdll
01/06/2019 08:35 PM 6,937,789 dynamic.exp
5 File(s) 97,038,041 bytes
0 Dir(s) 139,871,186,944 bytes free
여기에 함께 붙이는 것은 Nmake 메이크 파일의 모습입니다. 실제 Nmake 파일의 일부입니다 .
all: test.exe
test.exe: pch.pch static.lib $(TEST_OBJS)
$(LD) $(LDFLAGS) $(TEST_OBJS) static.lib $(LDLIBS) /out:$@
static.lib: $(LIB_OBJS)
$(AR) $(ARFLAGS) $(LIB_OBJS) /out:$@
dynamic.map:
$(LD) $(LDFLAGS) /DLL /MAP /MAPINFO:EXPORTS $(LIB_OBJS) $(LDLIBS) /out:dynamic.dll
dynamic.dump:
dumpbin.exe /LINKERMEMBER static.lib /OUT:dynamic.dump
dynamic.def: static.lib dynamic.dump
dump2def.exe dynamic.dump
dynamic.dll: $(LIB_OBJS) dynamic.def
$(LD) $(LDFLAGS) /DLL /DEF:dynamic.def /IGNORE:4102 $(LIB_OBJS) $(LDLIBS) /out:$@
clean:
$(RM) /F /Q pch.pch $(LIB_OBJS) pch.obj static.lib $(TEST_OBJS) test.exe *.pdb
다음에 대한 소스 코드는 dump2def.exe다음과 같습니다.
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <set>
typedef std::set<std::string> SymbolMap;
void PrintHelpAndExit(int code)
{
std::cout << "dump2def - create a module definitions file from a dumpbin file" << std::endl;
std::cout << " Written and placed in public domain by Jeffrey Walton" << std::endl;
std::cout << std::endl;
std::cout << "Usage: " << std::endl;
std::cout << " dump2def <infile>" << std::endl;
std::cout << " - Create a def file from <infile> and write it to a file with" << std::endl;
std::cout << " the same name as <infile> but using the .def extension" << std::endl;
std::cout << " dump2def <infile> <outfile>" << std::endl;
std::cout << " - Create a def file from <infile> and write it to <outfile>" << std::endl;
std::exit(code);
}
int main(int argc, char* argv[])
{
// ******************** Handle Options ******************** //
// Convenience item
std::vector<std::string> opts;
for (size_t i=0; i<argc; ++i)
opts.push_back(argv[i]);
// Look for help
std::string opt = opts.size() < 3 ? "" : opts[1].substr(0,2);
if (opt == "/h" || opt == "-h" || opt == "/?" || opt == "-?")
PrintHelpAndExit(0);
// Add <outfile> as needed
if (opts.size() == 2)
{
std::string outfile = opts[1];
std::string::size_type pos = outfile.length() < 5 ? std::string::npos : outfile.length() - 5;
if (pos == std::string::npos || outfile.substr(pos) != ".dump")
PrintHelpAndExit(1);
outfile.replace(pos, 5, ".def");
opts.push_back(outfile);
}
// Check or exit
if (opts.size() != 3)
PrintHelpAndExit(1);
// ******************** Read MAP file ******************** //
SymbolMap symbols;
try
{
std::ifstream infile(opts[1].c_str());
std::string::size_type pos;
std::string line;
// Find start of the symbol table
while (std::getline(infile, line))
{
pos = line.find("public symbols");
if (pos == std::string::npos) { continue; }
// Eat the whitespace after the table heading
infile >> std::ws;
break;
}
while (std::getline(infile, line))
{
// End of table
if (line.empty()) { break; }
std::istringstream iss(line);
std::string address, symbol;
iss >> address >> symbol;
symbols.insert(symbol);
}
}
catch (const std::exception& ex)
{
std::cerr << "Unexpected exception:" << std::endl;
std::cerr << ex.what() << std::endl;
std::cerr << std::endl;
PrintHelpAndExit(1);
}
// ******************** Write DEF file ******************** //
try
{
std::ofstream outfile(opts[2].c_str());
// Library name, cryptopp.dll
std::string name = opts[2];
std::string::size_type pos = name.find_last_of(".");
if (pos != std::string::npos)
name.erase(pos);
outfile << "LIBRARY " << name << std::endl;
outfile << "DESCRIPTION \"Crypto++ Library\"" << std::endl;
outfile << "EXPORTS" << std::endl;
outfile << std::endl;
outfile << "\t;; " << symbols.size() << " symbols" << std::endl;
// Symbols from our object files
SymbolMap::const_iterator it = symbols.begin();
for ( ; it != symbols.end(); ++it)
outfile << "\t" << *it << std::endl;
}
catch (const std::exception& ex)
{
std::cerr << "Unexpected exception:" << std::endl;
std::cerr << ex.what() << std::endl;
std::cerr << std::endl;
PrintHelpAndExit(1);
}
return 0;
}
아니요, __declspec(dllexport)내 보낸 함수를 구현하는 .cpp 파일에 포함 된 경우로 확인되고 __declspec(dllimport)그렇지 않은 경우로 확인 되는 매크로가 필요 합니다 .
참고URL : https://stackoverflow.com/questions/225432/export-all-symbols-when-creating-a-dll
'Nice programing' 카테고리의 다른 글
| 사전에서 숫자 값 증가 (0) | 2020.11.19 |
|---|---|
| 왜 std :: optional (0) | 2020.11.18 |
| SQL Server 2008 Developer를 설치할 때 사용할 계정 (0) | 2020.11.18 |
| 멤버 변수 대 조각의 setArguments (0) | 2020.11.18 |
| iOS5 NSURLConnection 메서드는 더 이상 사용되지 않습니다. (0) | 2020.11.18 |