개요
본 포스팅은 하기 기능의 예제를 제공합니다.
1. Visual Studio 2022를 활용한 동적 라이브러리 파일 (.so / .dll) CMake 빌드 환경 구성
2. java jna를 활용한 동적 라이브러리 파일 실행 환경 구성
[개발 환경]
로컬 환경 : Windows x64
원격지 환경 : Linux CentOS7
IDE : Cmake (Visual Studio 2022), java JNA (IntelliJ, gradle)
사전 환경 세팅
Visual Studio 2022
설치 파일
Visual Studio 2022 | 무료 다운로드
Visual Studio에서 코드 완성, 디버깅, 테스트, Git 관리, 클라우드 배포를 사용하여 코드를 작성합니다. 지금 무료로 커뮤니티를 다운로드하세요.
visualstudio.microsoft.com
설치 방법은 아래 포스팅을 참고해주세요.
2025.04.27 - [DEV/C, C++] - [C, C++, CMake] 개발 환경을 위한 Visual Studio 2022 설치 가이드
[C, C++, CMake] 개발 환경을 위한 Visual Studio 2022 설치 가이드
본 메뉴얼은 Cmake, C, C++ 개발 환경 설정을 위한Windows x64와 Linux OS 호환Visual Studio 2022 설치 가이드입니다. Visual Studio 2022 설치 파일 다운 온라인 다운 링크https://visualstudio.microsoft.com/ko/vs/ Visual Studio
doinitright.tistory.com
MinGW
설치 파일(x64)
Releases · niXman/mingw-builds-binaries
MinGW-W64 compiler binaries. Contribute to niXman/mingw-builds-binaries development by creating an account on GitHub.
github.com
Visual Studio 구성 (Cmake)
프로젝트 생성
새 프로젝트 만들기 클릭
CMake 프로젝트 클릭
- 프로젝트 이름 : MakeDll
- 프로젝트 위치 : 개별 지정
- '솔루션 및 프로젝트를 같은 디렉터리에 배치' 체크
- 체크 해제 시 : /{프로젝트명}/{프로젝트명} 구조로 디렉터리가 생성됨
참고) 예제의 프로젝트 구조
MakeDll // Root Dir
ㄴ lib // dll export Dir
ㄴ build.ninja // makefile (ninja로 라이브러리 파일 빌드)
ㄴ MyMath.dll // Windows 빌드 시
ㄴ MyMath.so // Linux 빌드 시
ㄴ CMakeLists.txt // 빌드 간 소스 파일 / 출력명 설정
ㄴ CMakePresets.json // 빌드 간 출력 디렉토리 설정
ㄴ MyMath.cpp // 소스 파일
ㄴ MyMath.h // 헤더 파일
(본 예제는 java 프로젝트 아래에 본 MakeDll 디렉터리를 위치시킴)
최초 프로젝트 생성 시 만들어지는 기본 파일(총 4개)
참고) CMake 프로젝트 닫은 후 다시 여는 방법
파일 > 열기 > CMakeCMakeLists.txt 파일 지정 후 열기 클릭
소스 파일 구성
CmakeLists.txt
# 소스 파일
set(SOURCE_FILES MyMath.cpp)
# 동적 라이브러리 생성
add_library(MakeDll SHARED ${SOURCE_FILES})
# lib 접두사 제거 (Linux 환경 .so 파일 default 접두사)
set(CMAKE_SHARED_LIBRARY_PREFIX "")
# 출력 파일 이름 지정
set_target_properties(MakeDll PROPERTIES OUTPUT_NAME "MyMath")
# 라이브러리 출력 디렉토리 설정 (선택 사항)
## CMAKE_BINARY_DIR은 CMakePresets.json에서 설정 변경 가능
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
CMakePresets.json: 빌드 간 출력 경로, 빌드 툴, 컴파일러 지정
{
"version": 3,
"configurePresets": [
{
"name": "windows-base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/lib",
"cacheVariables": {
"CMAKE_C_COMPILER": "cl.exe",
"CMAKE_CXX_COMPILER": "cl.exe"
}
},
{
"name": "x64-debug",
"displayName": "x64 Debug",
"inherits": "windows-base",
"architecture": {
"value": "x64",
"strategy": "external"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "x64-release",
"displayName": "x64 Release",
"inherits": "windows-base",
"architecture": {
"value": "x64",
"strategy": "external"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "linux-debug",
"displayName": "Linux Debug",
"generator": "Ninja",
"binaryDir": "/home/mrisk/sjlee/MakeDll/lib", // Custom, /MakeDll/lib 은 고정
"cacheVariables": {
"CMAKE_C_COMPILER": "/usr/bin/gcc",
"CMAKE_CXX_COMPILER": "/usr/bin/g++",
"CMAKE_BUILD_TYPE": "Debug"
}
}
]
}
- 윈도우의 경우 프로젝트 폴더 예하에 lib이 생성됨
- 리눅스(원격지)의 경우 /MakeDll/lib 상위의 경로는 별도 설정
MyMath.cpp
#include "MyMath.h"
double Sum(double a, double b) {
return a + b;
}
double Sub(double a, double b) {
return a - b;
}
double Mul(double a, double b) {
return a * b;
}
double Div(double a, double b) {
if (b != 0) {
return a / b;
}
else {
// Zero division error
return 0;
}
}
MyMath.h
#pragma once
#ifdef _WIN32 // For Windows
#ifdef BUILD_MY_MATH_LIBRARY
#define MY_MATH_API __declspec(dllexport) // DLL 출력
#else
#define MY_MATH_API __declspec(dllimport) // DLL 입력
#endif
#elif __linux__ // For Linux
#define MY_MATH_API __attribute__((visibility("default"))) // .so Export
#else
#error "Unsupported platform"
#endif
extern "C" {
MY_MATH_API double Sum(double a, double b);
MY_MATH_API double Sub(double a, double b);
MY_MATH_API double Mul(double a, double b);
MY_MATH_API double Div(double a, double b);
}
리눅스 원격지 연결 설정
도구 > 옵션
추가 버튼 클릭
원격지 정보 입력 후 연결
CMake로 dll, so 파일 빌드
[로컬, 원격지 공통 빌드 방법]
소스 코드에서 Ctrl + S
: 루트 폴더 예하의 /lib 폴더가 만들어지며, 이하의 build.ninja 빌드 파일을 만든다.
이후 build.ninja가 위치한 디렉터리에서 cmd나 bash 등 cli에서 ninja
명령어를 입력 시 수동으로 .so, .dll 파일이 빌드됨
빌드 > 모두 빌드
: 루트 폴더 예하의 /lib 폴더 및 .so, .dll 파일 빌드까지 실행빌드 > 모두 다시 빌드
: make clean → make → makefile로 빌드 캐시 수행 후 재빌드빌드 > 모두 정리
: make clean 실행
[로컬 빌드 방법]
빌드 구성을 로컬 컴퓨터, x64 Debug로 변경
최초 변경 시 /lib 폴더가 생성됨
참고) lib 폴더가 보이지 않을 시
모든 파일 보기 설정을 활성화 시 루트 디렉토리 예하의 모든 파일 열람이 가능
빌드 > 모두 빌드 클릭 (Ctrl + Shift + B)
(혹은 해당 lib 경로에서 cmd > ninja 명령어 수행)
. dll 파일 생성 확인
[원격지 빌드 방법 (Linux)]
참고) 원격지 빌드 구성간 설치 필요 라이브러리
- build.ninja
- gcc
- g++
CMakePresets.json
에서 경로 설정
/MakeDll/lib을 제외한 상위 경로를 설정 (so 파일 위치를 잡는다)
빌드 구성을 원격지 IP, Linux Debug로 변경
build file (build.ninja가 export 되었음을 확인)
빌드 > 모두 빌드 클릭 (Ctrl + Shift + B)
(혹은 해당 lib 경로에서 bash > ninja 명령어 수행)
. so 파일 생성 확인
JNA java application 구성 (실행)
(본 예제는 java 프로젝트 아래에 상기 작업한 CMake 프로젝트인 MakeDll 디렉터리를 위치시킴)
exec // Root Dir
ㄴ gradle
ㄴ bin
ㄴ out // 빌드된 jar 파일이 위치할 디렉토리
ㄴ MakeDll // CMake 프로젝트 (상기 작업 폴더)
ㄴ src
ㄴ main
ㄴ java.org.jna
ㄴ Main.java
ㄴ LibraryRunner.java // 기본 시스템 기능 정의
ㄴ NativeLibraryLoader.java // 함수 리스트 출력, 실행
ㄴ Config.java // properties 객체
ㄴ resources
ㄴ application.properties // .so, .dll 파일경로 설정
ㄴ build.gradle // 의존성 주입 (기존 pom.xml 기능)
ㄴ settings.gradle
ㄴ gradlew
ㄴ gradlew.bat
새 프로젝트 생성(IntelliJ)
- Name : exec
- Location : 지정 (단, java 프로젝트 루트 디렉토리 아래에 MakeDll 디렉터리가 포함되게 설정)
- Language : Java
- Build System : Gradle
- JDK : 1.8
- Gradle DSL : Groovy
- Advanced Settings > GroupId : org.jna
설정 후 Create 클릭
생성 확인
소스 파일 구성
/build.gradle
plugins {
id 'java'
}
group 'org.jna'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation 'net.java.dev.jna:jna:5.11.0'
implementation 'org.slf4j:slf4j-api:1.7.32'
implementation 'org.slf4j:slf4j-simple:1.7.32'
compileOnly 'org.projectlombok:lombok:1.18.22'
annotationProcessor 'org.projectlombok:lombok:1.18.22'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
test {
useJUnitPlatform()
}
/src/main/java/org/jna/Config.java
package org.jna;
import lombok.Getter;
import lombok.AllArgsConstructor;
import java.util.Properties;
@Getter
@AllArgsConstructor
public class Config {
private String execFileDir;
private String execFileName;
public static Config fromProperties(Properties properties) {
return new Config(
properties.getProperty("execFileDir"),
properties.getProperty("execFileName")
);
}
}
/src/main/java/org/jna/Main.java
package org.jna;
import java.io.InputStream;
import java.util.Properties;
public class Main {
public static void main(String[] args) {
Config config = loadConfig();
LibraryRunner.runNativeLibrary(config);
}
private static Config loadConfig() {
Properties properties = new Properties();
try (InputStream input = Main.class.getClassLoader().getResourceAsStream("application.properties")) {
if (input == null) {
throw new RuntimeException("Unable to find application.properties");
}
properties.load(input);
} catch (Exception e) {
throw new RuntimeException("Failed to load configuration", e);
}
return Config.fromProperties(properties);
}
}
/src/main/java/org/jna/LibraryRunner.java
package org.jna;
import com.sun.jna.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Scanner;
public class LibraryRunner {
private static final Logger logger = LoggerFactory.getLogger(LibraryRunner.class);
public static void runNativeLibrary(Config config) {
if (config == null) {
logger.error("Configuration is missing.");
return;
}
String osFile;
if (Platform.isWindows()) {
osFile = config.getExecFileName() + ".dll";
logger.info("::::: [DYNAMIC LIBRARY RUNNER] for Windows :::::");
} else if (Platform.isLinux()) {
osFile = config.getExecFileName() + ".so";
logger.info("::::: [DYNAMIC LIBRARY RUNNER] for Linux :::::");
} else if (Platform.isMac()) {
osFile = config.getExecFileName() + ".dylib";
} else {
logger.error("호환되지 않는 운영체제입니다.");
return;
}
logger.info("Loading native library : {}", osFile);
NativeLibraryLoader loader = new NativeLibraryLoader(config.getExecFileDir() + "/" + osFile);
Scanner scanner = new Scanner(System.in);
logger.info("Successfully loaded library : {}", osFile);
logger.info("명령어를 입력해주세요. [-help, -list, -{functionName}]");
while (true) {
String input = scanner.nextLine().trim();
if ("-exit".equals(input)) {
logger.info("시스템을 종료합니다.");
break;
} else if ("-list".equals(input)) {
logger.info("[{}] 사용 가능 함수", config.getExecFileName());
for (String function : loader.getFunctions())
logger.info("{}. {}", loader.getFunctions().indexOf(function) + 1, function);
} else if ("-help".equals(input)) {
logger.info("사용 가능한 명령어");
logger.info("-list : 호출 가능한 함수를 출력합니다.");
logger.info("-exit : 시스템을 종료합니다.");
logger.info("-{함수명} : 함수를 호출합니다.");
} else if (input.startsWith("-") && loader.getFunctions().contains(input.replaceFirst("^-+", ""))) {
String inputSubstrBar = input.replaceFirst("^-+", "");
logger.info("[{}] 입력값 1", inputSubstrBar);
String param1 = scanner.nextLine().trim();
logger.info("[{}] 입력값 2", inputSubstrBar);
String param2 = scanner.nextLine().trim();
logger.info("[{}] 계산 결과 : {} ", inputSubstrBar, loader.callFunction(inputSubstrBar, param1, param2));
} else {
logger.warn("알 수 없는 명령어입니다. -help를 입력해 보세요.");
}
}
}
}
/src/main/java/org/jna/NativeLibraryLoader.java
package org.jna;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Platform;
import lombok.Getter;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Getter
public class NativeLibraryLoader {
private final String libraryPath;
private final List<String> functions;
public NativeLibraryLoader(String libraryPath) {
this.libraryPath = libraryPath;
this.functions = new ArrayList<>();
loadFunctionList();
}
private void loadFunctionList() {
try {
ProcessBuilder processBuilder;
if (Platform.isWindows()) {
// Windows : 함수 목록 파싱 (dumpbin / EXPORTS)
processBuilder = new ProcessBuilder("cmd.exe", "/s", "/c", "dumpbin /EXPORTS \"" + libraryPath + "\"");
} else if (Platform.isLinux()) {
// Linux : 함수 목록 파싱 (nm -D)
processBuilder = new ProcessBuilder("nm", "-D", libraryPath);
} else {
return;
}
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = "";
while ((line = reader.readLine()) != null) {
if (Platform.isWindows()) {
String functionNamePattern = ".*\\((\\w+)\\).*"; // 괄호 안의 함수명 추출
Pattern pattern = Pattern.compile(functionNamePattern);
Matcher matcher = pattern.matcher(line);
// "R", "C" 등 네이티브 함수 제외
if (matcher.matches()) {
if (!matcher.group(1).equals("R") && !matcher.group(1).equals("C")) {
functions.add(matcher.group(1));
}
}
} else {
// Linux : 함수 목록 파싱 (nm
if (line.contains(" T ")) {
String[] parts = line.trim().split("\\s+");
// "__init", "__fini" 등 네이티브 함수 제외
if (!parts[parts.length -1].startsWith("_")) {
functions.add(parts[parts.length - 1]); // 마지막 컬럼이 함수명
}
}
}
}
reader.close();
process.waitFor();
} catch (Exception e) {
throw new RuntimeException("Failed to load functions from native library: " + e.getMessage(), e);
}
}
// 함수명을 입력하면 해당 함수를 실행하는 메서드 (JNA를 활용)
public String callFunction(String functionName, String param1, String param2) {
try {
NativeLibrary library = NativeLibrary.getInstance(libraryPath);
return library.getFunction(functionName).invoke(
double.class, new Object[]{Double.parseDouble(param1), Double.parseDouble(param2)}
).toString();
} catch (Exception e) {
throw new RuntimeException("Failed to call function: " + functionName, e);
}
}
}
빌드 / 실행 환경 구성
[소스로 컴파일, 실행]
Main 소스 코드 진입 후 Line 6의 화살표 클릭 > Run Main.main 클릭
콘솔창 업로드 후 실행 확인
최초 실행 후에는 우측 상단을 통해 재실행 가능
[runnable jar 빌드 환경 구성]
ctrl + alt + shift + s
또는 File > Project Structure 진입
좌측부터 Artifacts > + 버튼 > Jar > From modules with dependencies 클릭
- Module : <All Modules>
- Main Class : 프로젝트 browse 후 org.jna의 Main 지정 후 OK
- Drirectory for META-INF/MANIFEST.MF : MANIFEST.MF 생성 폴더 지정 (예제의 경우 src 예하로)
설정 후 OK 클릭
참고) Available Elements에 resources 디렉토리 미포함 시 조치사항
빌드 시 resources를 포함하기 위해 + 버튼 > Directory Content 클릭
resources 폴더 지정 후 OK 클릭
빌드 간 포함 확인
설정 완료 후 OK 클릭, 이후 빌드 완료된 jar는 위의 Output Directory에 탑재됨
Build > Build Artifacts > exec.jar > Build로 빌드
/out/artifacts/exec_jar/exec.jar 생성 확인
리눅스 환경에서 java -jar 실행 시 MakeDll과 같은 디렉토리에 jar 파일이 위치해야 함 (예제 소스 기준)
'DEV > C, C++' 카테고리의 다른 글
[C, C++, CMake] 개발 환경을 위한 Visual Studio 2022 설치 가이드 (0) | 2025.04.27 |
---|