본문 바로가기

프로그래밍/iOS,macOS

[swift] swift 라이브러리 c 코드에서 연동하기

swift 로 만든 정적, 동적 라이브러리를 c, c++ 에서 사용하는 방법

 

라이브러리 패키지 생성

mkdir MySwiftLib
cd MySwiftLib
swift package init —type library
open Package.swift

 

 

Package.swift 수정

라이브러리 type 을 지정해 준다.

// swift-tools-version: 6.0
import PackageDescription

let package = Package(
  name: "MySwiftLib",
  products: [
    .library(
      name: "MySwiftLib",
      type: .dynamic, // .static
      targets: ["MySwiftLib"]
  ],
  targets: [
    .target(
      name: "MySwiftLib",
      path: "Sources/MySwiftLib",
      publicHeaderPath: "include"
  ],
)

 

 

swift 함수 작성

c에서 호출할 함수들은 @_cdecl("함수명") 형식으로 작성되어야 한다.

// 기본 함수
@_cdecl("helloWorld")
public func helloWorld() {
  print("hello world")
}

// 문자열 입력 함수 예시
@_cdecl("helloWorld")
public func helloWorld(_ cString: UnsafePointer<CChar>) {
  let message = String(cString: cString)
  print("hello world: \(message)")
}

 

 

클래스의 경우 직접 사용은 불가능 하고, 포인터 형으로 전달해 c 에서 void* 로 사용한다.

public class MyClass {
  // 클래스 구현
  func doSomething() {
    print("hello world")
  }
}

@_cdecl("createMyClass")
public func createMyClass() -> UnsafeMutableRawPointer {
  let obj = MyClass()
  return UnsafeMutableRawPointer(Unmanaged.passRetained(obj).toOpaque())
}

@_cdecl("doSomething")
public func doSomething(_ ptr: UnsafeMutableRawPointer) {
  let obj = Unmanaged<MyClass>.fromOpaque(ptr).takeUnretainedValue()
  obj.doSomething()
}

@_cdecl("releaseMyClass")
public func releaseMyClass(_ ptr: UnsafeMutableRawPointer) {
  Unmanaged<MyClass>.fromOpaque(ptr).release()
}

 

 

호환 가능한 데이터 타입

Int32 : int32_t / int
UInt32 : uint32_t
Int64 : int64_t / long long
UInt64 : uint64_t
Float : float
Double : double
Bool : bool
UnsafePointer<T> : const T*
UnsafeMutablePointer<T> : void*
Void : void

 

 

c에서 include 할 헤더 파일작성

include/MySwiftLib-Swift.h

#ifndef My_SwiftLib_H
#define My_SwiftLib_H

#ifndef __cplusplus
extern "C" {
#endif 

// swift 에서 작성한 함수들
void helloWorld(const char *msg)
  .
  .
  .

#ifndef __cplusplus
}
#endif 

#endif

 

 

라이브러리 빌드

swift build -c release

 

.build/release/ 폴더에 libMySwiftLib.a 혹은 libMySwiftLib.dylib 생성 확인

 

 

 

c/c++ 샘플 작성

샘플 c 프로젝트 폴더에 헤더와 빌드한 라이브러리 복사

동적 라이브러리의 경우 컴퍼일 시점에 링크해도 되고, 별도 런타임에 연결 수도 있음

 

main.c : 기본적인 연동 방법

#include <stdio.h>
#include "MySiwftLib-Swfit.h"

int main() {
  helloWorld("hi");
  return 0;
}

 

 

main.cpp : 동적라이브러리 + 동적 로드

#include <iostream>
#include <dlfcn.h>

int main() {
  void* handle = dlopen("./libMySwiftLib.dylib", RTLD_LAZY);
  if (!handle) {
    std::cerr << "dlopen error: " << dlerror() << std::endl;
    return 1;
  }
  
 auto helloWorld = (void(*)() dlsym(handle, "helloWorld");
  if (!helloWorld) {
    std::cerr << "dlsym error: " << dlerror() << std::endl;
    return 1;
  }

  dlclose(handle);
  return 0;
}

 

 

빌드

clang main.c -L. -lMySiwftLib -Wl,-rpath,@executable_path -o main

path 구성 등은 라이브러리 위치나 플랫폼에 따라 변경

 

정적라이브러리 사용하는 경우 swift runtime 위치를 지정해 주어야 한다.

xcrun -find swift 로 현재 swift 바이너리 위치를 얻고, dirname 으로 위치를 수정해 준다.

clang main.c \
-L. -lMySwiftLib \
-L$(dirname $(dirname $(xcrun —find swift)))/lib/swift/macosx \
-o main