Last active
March 30, 2021 21:24
-
-
Save caiorss/2bba4c50866d9467aaa8c7792b337f71 to your computer and use it in GitHub Desktop.
Sample Windows DLL - shared library testlib.dll and client program (client code). - https://caiorss.github.io/C-Cpp-Notes/DLL-Binary-Components-SharedLibraries.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
*.dll | |
*.exe | |
*.obj | |
*.so | |
*.bin | |
*.zip | |
*.7z | |
*.exp | |
*.lib | |
.vs/* | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@echo off | |
@echo Automate CMake initialization. | |
@echo Building project | |
:: Alias to executable | |
set CMAKEBIN="C:\Program Files\CMake\bin\cmake" | |
@echo Setting up project | |
@echo -------------------------------------------------- | |
:: Generate specific preferred project for current platform | |
"C:\Program Files\CMake\bin\cmake" -H. -Bbuild -G "Visual Studio 15 2017 Win64" | |
@echo Executing Install Target | |
@echo -------------------------------------------------- | |
:: Build project and copy executable and dll to this directory | |
"C:\Program Files\CMake\bin\cmake" --build build --config install | |
:: "C:\Program Files\CMake\bin\cmake" --build build --config Release -- install VERBOSE=1 | |
@echo Compilation finished | |
::-----------------------------------------------:: | |
:: Wait user enter any key to exit | |
pause |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@echo off | |
rem Compile for x86 or x64 bits | |
rem ------------------------------ | |
rem set MODE=x86 | |
set MODE=x64 | |
@REM Visual studio building tools path - Install it with chocolately | |
set VS2017="C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsamd64_x86.bat" | |
set VS2015="C:\Program Files (x86)\Microsoft Visual C++ Build Tools\vcbuildtools.bat" | |
rem Save current directory | |
pushd %CD% | |
@REM Set visual Studio 2017 | |
call %VS2017% %MODE% | |
@REM Restore saved directory | |
popd | |
@REM ------------------ User Command Goes Here ----------------- @REM | |
@REM Build solution in Debug mode | |
cl.exe testlib.cpp /EHsc /LD /nologo user32.lib | |
cl.exe /EHsc client1.cpp /Fe:client1.exe testlib.lib && client1.exe | |
@REM Set /p Wait=Build Process Completed... | |
pause |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// File: client-csharp.cs | |
// Brief: C# client code (via P-invoke) for the DLL Shared library. | |
// Author: Caio Rodrigues | |
//----------------------------------------------------------------- | |
using System; | |
using System.Runtime.InteropServices; | |
class DllCsharpClient{ | |
static void Main(string[] args){ | |
Console.WriteLine(" ===== EXPERIMENT 1 = Using C-functions directly =="); | |
Console.WriteLine(" [CSharp] Loading Native DLL C++ Shared Library"); | |
IntPtr vectorObject = VectorD.testlib_vectorD_make0(5, 3.0); | |
Console.WriteLine(" [CSharp] Opaque pointer = ", vectorObject.ToString()); | |
VectorD.testlib_vectorD_Linalg_printVector("vectorX", vectorObject); | |
double x = VectorD.testlib_vectorD_Linalg_norm(vectorObject); | |
Console.WriteLine(" [CSharp] Vector norm = " + x.ToString()); | |
Console.WriteLine(" [CSharp] End application"); | |
VectorD.testlib_vectorD_delete(vectorObject); | |
Console.WriteLine(" ===== EXPERIMENT 2 = Using OOP wrapper for std::vector ==="); | |
Console.WriteLine(" ==> Before changing vector"); | |
VectorD v1 = new VectorD(10, 3.5); | |
v1.print(); | |
v1.print("vector_v1"); | |
Console.WriteLine("v1.norm() = " + v1.norm().ToString()); | |
Console.WriteLine(" ==> After changing vector"); | |
v1.set(1, 10.0); v1.set(2, 5.53); v1.set(3, 8.96); | |
v1.set(4, -10.34); v1.set(8, 80.54); | |
v1.print("v1_changed"); | |
Console.WriteLine(" ==> Creating Vector from Array"); | |
VectorD v2 = new VectorD(new double[] {4.5, -8.84, 78.23, 652.3, 34.56, 45.12}); | |
v2.print("v2"); | |
Console.WriteLine("v2.norm() = " + v2.norm().ToString()); | |
Console.WriteLine(" ===== EXPERIMENT 3 = OOP wrapper for InterfaceClass ==="); | |
CPPInterfaceClass instA = CPPInterfaceClass.ImplementationA(); | |
CPPInterfaceClass instB = CPPInterfaceClass.ImplementationB(); | |
// Console.WriteLine("instA.Name = " + instA.Name); | |
Console.WriteLine("instA.GetID() = " + instA.GetID()); | |
Console.WriteLine("instB.GetID() = " + instB.GetID()); | |
Console.WriteLine(" **=> Before changing"); | |
Console.WriteLine("instA.Name = " + instA.Name); | |
Console.WriteLine("instB.Name = " + instB.Name); | |
Console.WriteLine("\n **=> Before changing"); | |
instA.Name = "Instance-of-ImplA"; | |
instB.Name = "Instance-of-ImplB"; | |
Console.WriteLine("instA.Name = " + instA.Name); | |
Console.WriteLine("instB.Name = " + instB.Name); | |
Console.WriteLine(" ===== END ==========================="); | |
try { | |
Console.ReadKey(); | |
} | |
catch(System.InvalidOperationException ex) | |
{ | |
} | |
} | |
} | |
class VectorD{ | |
// Constructor Function: | |
// hVectorD testlib_vectorD_make0(size_t n, double x) | |
// void* testlib_vectorD_make0(size_t n, double x) | |
[DllImport("testlib.dll")] | |
public static extern | |
IntPtr testlib_vectorD_make0(int size, double x); | |
[DllImport("testlib.dll")] | |
public static extern | |
IntPtr testlib_vectorD_make1(int n, double[] x); | |
// Destructor function: | |
// void testlib_vectorD_delete(hVectorD hv) | |
// void testlib_vectorD_delete(void* hv) | |
[DllImport("testlib.dll")] | |
public static extern | |
IntPtr testlib_vectorD_delete(IntPtr hv); | |
// void testlib_vectorD_Linalg_printVector(const char* name, hVectorD hv) | |
// void testlib_vectorD_Linalg_printVector(const char* name, vector* hv) | |
[DllImport("testlib.dll")] | |
public static extern | |
void testlib_vectorD_Linalg_printVector(string name, IntPtr hv); | |
// double testlib_vectorD_Linalg_norm(hVectorD hv) | |
// double testlib_vectorD_Linalg_norm(void* hv) | |
[DllImport("testlib.dll")] | |
public static extern | |
double testlib_vectorD_Linalg_norm(IntPtr hv); | |
// void testlib_vectorD_set(hVectorD hv, size_t n, double x) | |
// void testlib_vectorD_set(void* hv, size_t n, double x) | |
[DllImport("testlib.dll")] | |
public static extern | |
void testlib_vectorD_set(IntPtr hv, int n, double x); | |
// ------------ Objet Oriented Wrapper --------// | |
// Hadle | |
private IntPtr m_handle; | |
public VectorD(int size, double x){ | |
m_handle = testlib_vectorD_make0(size, x); | |
} | |
public VectorD(double[] array){ | |
m_handle = testlib_vectorD_make1(array.Length, array); | |
} | |
// Finalized destructor | |
~VectorD(){ | |
testlib_vectorD_delete(m_handle); | |
} | |
public void print(string name = "std::vector<double>"){ | |
testlib_vectorD_Linalg_printVector(name, m_handle); | |
} | |
public void set(int n, double x){ | |
testlib_vectorD_set(m_handle, n, x); | |
} | |
public double norm(){ | |
return testlib_vectorD_Linalg_norm(m_handle); | |
} | |
} | |
/* Wrapper for the interface C++ interface class 'InterfaceClass' | |
* | |
*/ | |
class CPPInterfaceClass{ | |
// Handle type => Opaque pointer for InterfaceClass instances. | |
// using HInterf = IntPtr; | |
// Factory function for loading functions from this interface. | |
//----------------------------------------------------------- | |
// InterfaceClass* teslib_InterfaceClass_factory(const char* class_id) | |
// void** teslib_InterfaceClass_factory(const char* class_id) | |
[DllImport("testlib.dll")] | |
private static extern | |
IntPtr teslib_InterfaceClass_factory(string class_id); | |
// void testlib_InterfaceClass_delete(InterfaceClass* hinst) | |
// void testlib_InterfaceClass_delete(void*) | |
[DllImport("testlib.dll")] | |
private static extern | |
void testlib_InterfaceClass_delete(IntPtr hinst); | |
// char* testlib_InterfaceClass_getID(InterfaceClass* hinst) | |
[DllImport("testlib.dll")] | |
private static extern IntPtr testlib_InterfaceClass_getID(IntPtr hinst); | |
// void testlib_InterfaceClass_setName(InterfaceClass* hinst, const char* name) | |
// Note: Charset.UNICODE does not work as it is UTF16 (Wide Unicode), not UTF-8 | |
// which is the C++ default unicode type. | |
[DllImport("testlib.dll", CharSet = CharSet.Ansi)] | |
private static extern | |
void testlib_InterfaceClass_setName(IntPtr hinst, string name); | |
// char* testlib_InterfaceClass_getName(InterfaceClass* hinst) | |
[DllImport("testlib.dll")] | |
private static extern | |
IntPtr testlib_InterfaceClass_getName(IntPtr hinst); | |
private IntPtr m_handle; | |
private CPPInterfaceClass(IntPtr handle){ | |
m_handle = handle; | |
} | |
~CPPInterfaceClass(){ | |
testlib_InterfaceClass_delete(m_handle); | |
} | |
/** Creates an instance of the the C++ implementation ImplementationA (class) from the | |
* C++ interface: 'InterfaceClass' | |
*/ | |
public static CPPInterfaceClass ImplementationA(){ | |
return new CPPInterfaceClass(teslib_InterfaceClass_factory("ImplementationA")); | |
} | |
/** Creates an instance of the C++ implementationa ImplementationB (class) */ | |
public static CPPInterfaceClass ImplementationB(){ | |
return new CPPInterfaceClass(teslib_InterfaceClass_factory("ImplementationB")); | |
} | |
// Get type of the wrapped C++ class | |
public string GetID(){ | |
IntPtr p = testlib_InterfaceClass_getID(m_handle); | |
return Marshal.PtrToStringAnsi(p); | |
} | |
// Get/Set name of current instance | |
public string Name | |
{ | |
get{ | |
IntPtr p = testlib_InterfaceClass_getName(m_handle); | |
return Marshal.PtrToStringAnsi(p); | |
} | |
set{ | |
testlib_InterfaceClass_setName(m_handle, value); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// File: client.cpp | |
// Brief: Sample C++ client code for DLL testlib.dll | |
// Author: Caio Rodrigues | |
//--------------------------------------------------------------- | |
#include <iostream> | |
#include <ostream> | |
#include <vector> | |
#include <string> | |
#include "testlib.hpp" | |
#define WindbgTrace(text) \ | |
{ std::stringstream ss; \ | |
ss << __FILE__ << ":" << __LINE__ << ": <" << __FUNCTION__ "> " \ | |
<< text << std::endl;\ | |
OutputDebugString(ss.str().c_str()); \ | |
} | |
#define DbgTrace(text) \ | |
{ std::cerr << __FILE__ << ":" << __LINE__ << ": <" << __FUNCTION__ << "> " \ | |
<< text << std::endl; } | |
#define DbgDisp(expr) \ | |
{ std::cerr << __FILE__ << ":" << __LINE__ << ": <" << __FUNCTION__ << "> " \ | |
<< #expr << " = " << (expr) << std::endl; } | |
extern "C" hVectorD testlib_vectorD_make0(size_t n, double); | |
extern "C" hVectorD testlib_vectorD_make1(size_t n, double array []); | |
extern "C" void testlib_vectorD_delete(hVectorD hv); | |
int main(){ | |
#ifndef DISABLE | |
std::cout << "\n=== EXPERIMENT 1 ===> Import C++ functions from DLL" << std::endl; | |
DbgTrace("Main process starts here."); | |
std::vector<double> xs{1.0, 2.0, 3.0, 4.0, 5.0}; | |
std::cout << " => Linalg::norm(xs) " << Linalg::norm(xs) << std::endl; | |
std::cout << "=> xs = "; Linalg::printVector(std::cout, xs); std::cout << std::endl; | |
std::cout << "=== EXPERIMENT 2 ===> Import class from DLL" << std::endl; | |
auto cls = SampleClass("Dummy"); | |
cls.set(100); | |
std::cout << "cls.getName() = " << cls.getName() << std::endl; | |
std::cout << " cls.get() = " << cls.get() << std::endl; | |
#endif // -- eof DISABLE flag | |
//=========>> Load functions and classes using C-interface ==============// | |
std::cout << "\n== EXPERMIMENT 3 ===> Import C-functions from DLL - C-interface" << std::endl; | |
double arr [] = {1, 2, 3, 4, 5}; | |
hVectorD v1 = testlib_vectorD_make1(5, arr); | |
testlib_vectorD_Linalg_printVector("v1", v1); | |
std::cout << "norm(v1) = " << testlib_vectorD_Linalg_norm(v1) << std::endl; | |
testlib_vectorD_delete(v1); | |
std::cout << "\n== EXPERMIMENT 4 ===> Non-polymorphic class with C-interface " << std::endl; | |
hSampleClass hcls = testlib_SampleClass_make1("[EXPERIMENT4]ClassHandle-OOP-C-API"); | |
std::cout << "[EXPERIMENT 4] hcls.getName() = " << testlib_SampleClass_getName(hcls) << std::endl; | |
testlib_SampleClass_set(hcls, 100); | |
std::cout << "[EXPERIMENT 4] hcls.get() = " << testlib_SampleClass_get(hcls) << std::endl; | |
testlib_SampleClass_set(hcls, 200); | |
std::cout << "[EXPERIMENT 4] hcls.get() = " << testlib_SampleClass_get(hcls) << std::endl; | |
testlib_SampleClass_delete(hcls); | |
std::cout << "\n== EXPERMIMENT 5 ===> Load polymorphic classes from DLL " << std::endl; | |
InterfaceClass* hinstA = teslib_InterfaceClass_factory("ImplementationA"); | |
InterfaceClass* hinstB = teslib_InterfaceClass_factory("ImplementationB"); | |
std::cout << " => hinstA->getID() = " << hinstA->getID() << std::endl; | |
std::cout << " => hinstA->getID() = " << hinstB->getID() << std::endl; | |
hinstA->setName("ClassA-implA"); | |
hinstB->setName("ClassB-implB"); | |
std::cout << " => hinstA->getName() = " << hinstA->getID() << std::endl; | |
std::cout << " => hinstB->getName() = " << hinstB->getID() << std::endl; | |
std::cout << " [INFO] hinstA->getID() = " << hinstA->getID() << std::endl; | |
// Note: If delete is used directly to delete hinstA and hinstB, | |
// a segmentatin fault will happen whenc compiling with Mingw/GCC | |
testlib_InterfaceClass_delete(hinstA); | |
testlib_InterfaceClass_delete(hinstB); | |
std::cout << " [INFO] After deleting instances" << std::endl; | |
DbgTrace("Program ended OK."); | |
#if defined(_WIN32) | |
std::cout << "\n ** Enter RETURN to exit" << std::endl; | |
std::cin.get(); | |
#endif | |
return 0; | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Description: Minimal CMake for building shared libraries in a cross platform way. | |
# Author: Caio Rodrigues | |
# Tested on: Linux, Windows 10 with MSVC/MSBuild and Mingw Makefiles and NMake JOM. | |
#================================================================== | |
cmake_minimum_required(VERSION 3.9) | |
set(CMAKE_VERBOSE_MAKEFILE ON) | |
project(My_shared_library) | |
# Set C++ 14 Standard | |
set(CMAKE_CXX_STANDARD 14) | |
# Set default building type to debug if it was not set | |
# in command line with -DCMAKE_BUILD_TYPE=release | |
if(NOT DEFINED CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE MATCHES "") | |
set(CMAKE_BUILD_TYPE debug) | |
endif() | |
message(" [INFO] Build type => CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}") | |
if(CMAKE_SIZEOF_VOID_P EQUAL 8) | |
message(" [INFO] 64 bits build.") | |
else() | |
message(" [INFO] 32 bits build.") | |
endif() | |
#====== Shared library target (testlib) =================# | |
# File name => | |
add_library( | |
testlib # Target Name | |
SHARED # Specify that target is a shared library | |
./testlib.cpp | |
# src/source2.cpp | |
# src/source3.cpp | |
) | |
set_target_properties( | |
# Remove prexix 'lib' from file name, so instead of generating | |
# the file liblibtest.so, it will build the file liblibtest.so. | |
#----------------------------------------- | |
# testlib PROPERTIES PREFIX "" | |
# TL;DR => -fvisibility=hidden for this library (testlib.so or testlib.dll). | |
# | |
# Make library symbols hidden by default as it happens in Windows were | |
# exported symbols are required be explicitly annotated | |
# with __declspec(dllexport) or __declspec(dllimport). | |
# On U-nix-like OSes (Linux, BSD, OSX ...), it will add the compilation flag | |
# -fvisibility=hidden | |
# Reference: https://stackoverflow.com/questions/17080869 | |
#-------------------------------------------------------------------- | |
testlib PROPERTIES CXX_VISIBILITY_PRESET hidden | |
# testlib PROPERTIES OUTPUTT_NAME "someOtherName" | |
# mylib PROPERTIES VERSION ${PROJECT_VERSION} | |
) | |
# Include current directory (.) PWD | |
# target_include_directories(testlib PRIVATE .) | |
# Post Target Build. -- Event target | |
#--------------------------------------- | |
# Automatically copies the target testlib (testlib.so) to | |
# project root directory whenever this target is rebuilt. | |
# Ref: https://stackoverflow.com/questions/31277186 | |
# Ref: https://stackoverflow.com/questions/15694347/ | |
add_custom_command(TARGET testlib POST_BUILD | |
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:testlib> | |
${CMAKE_CURRENT_SOURCE_DIR}/libtest.so | |
) | |
#====== Test executable for loading the library (testlib) =======# | |
# | |
add_executable(client1-executable ./client1.cpp) | |
# Add extension .bin to the built Unix-executable, so it will generate | |
# the program 'client1-executable.bin' instead of 'client1-executable' | |
if(UNIX) | |
set_target_properties(client1-executable PROPERTIES SUFFIX ".bin") | |
endif() | |
find_library(libtest_location libtest) | |
message(" [INFO] Libtest location is at: ${libtest_location}") | |
target_link_libraries(client1-executable PUBLIC testlib) | |
# target_link_libraries(client1-executable PUBLIC libtest.so) | |
# target_compile_definitions(client1-executable PUBLIC libtest) | |
#target_compile_options(client1-executable PUBLIC testlib) | |
# If not set the install directory, attemp set the install directory | |
# CMAKE_INSTALL_PREFIX as this directory | |
if(NOT DEFINED CMAKE_INSTALL_PREFIX OR CMAKE_INSTALL_PREFIX MATCHES "") | |
set(CMAKE_INSTALL_PREFIX ".") | |
endif() | |
message(" [INFO] CMakeLists.txt is in the directory ${CMAKE_CURRENT_LIST_DIR}") | |
# Copy targets to ./ - Directory where is this file CMakeLists.txt (project top level dir) | |
install(TARGETS client1-executable testlib DESTINATION ${CMAKE_CURRENT_LIST_DIR}/bin) | |
add_custom_target(run | |
COMMAND client1-executable | |
DEPENDS client1-executable | |
WORKING_DIRECTORY ${CMAKE_PROJECT_DIR} | |
) | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
all: build | |
# Generate build script | |
cmake: | |
cmake -H. -Bbuild | |
compile: | |
cmake --build build --config Release -- VERBOSE=1 | |
# Compile | |
build: | |
cmake -H. -Bbuild -DCMAKE_INSTALL_PREFIX=. && \ | |
cmake --build build --config Release -- install VERBOSE=1 | |
run: | |
build/client1-executable.bin | |
linux: | |
echo "Build shared library" | |
g++ testlib.cpp -o libtestlib.so -std=c++14 -fPIC -shared -Wall | |
echo "Build client code 1" | |
g++ client1.cpp -o client1.bin -std=c++14 libtestlib.so | |
# Run python client code. | |
py: | |
python3 pywrapper.py | |
# Clean ./build directory | |
clean: | |
rm -rf -v ./build |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#=========================================================================== | |
# File: pywrapper.py | |
# Brief: Python wrapper for the DLL - Shared Library testlib.dll | |
# Python Version: 3.0 | |
# Author: Caio Rodrigues | |
#========================================================================= | |
import ctypes | |
def _getSharedLibrary(libname): | |
import sys | |
import os | |
libfile = libname | |
if sys.platform == "linux" or sys.platform == "linux2": | |
libfile = "lib" + libname + ".so" | |
elif sys.platform == "darwin": | |
libfile = libname + ".dylyb" | |
elif sys.platform == "win32": | |
libfile = libname + ".dll" | |
libpath = os.path.join(os.path.dirname(__file__), libfile) | |
print(" [INFO] libpath = " + libpath) | |
return libpath | |
# _lib = ctypes.cdll.LoadLibrary("testlib") | |
_lib = ctypes.cdll.LoadLibrary(_getSharedLibrary("testlib")) | |
# Startup ctypes FFI - Foreign Function Interface | |
def _config(): | |
print("Intializing library") | |
# ======= std::vector<double> and Linalg:: namespace ==========## | |
# hVectorD testlib_vectorD_make0(size_t n, double x) | |
_lib.testlib_vectorD_make0.argtypes = [ctypes.c_int, ctypes.c_double] | |
_lib.testlib_vectorD_make0.restype = ctypes.c_void_p | |
# hVectorD testlib_vectorD_make1(size_t n, double array []) | |
_lib.testlib_vectorD_make1.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_double)] | |
_lib.testlib_vectorD_make1.restype = ctypes.c_void_p | |
# void testlib_vectorD_Linalg_printVector(const char* name, hVectorD hv) | |
_lib.testlib_vectorD_Linalg_printVector.argtypes = [ctypes.POINTER(ctypes.c_char), ctypes.c_void_p ] | |
_lib.testlib_vectorD_Linalg_printVector.restype = None | |
# Set vector elements hv[n] = x | |
# void testlib_vectorD_set(hVectorD hv, size_t n, double x) | |
_lib.testlib_vectorD_set.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_double] | |
_lib.testlib_vectorD_set.restype = None | |
# double Linalg::norm(const std::vector<double>& xs) | |
# double testlib_vectorD_Linalg_norm(hVectorD hv) | |
_lib.testlib_vectorD_Linalg_norm.argtypes = [ ctypes.c_void_p ] | |
_lib.testlib_vectorD_Linalg_norm.restype = ctypes.c_double | |
# Function: void testlib_vectorD_delete(hVectorD hv) | |
_lib.testlib_vectorD_delete.argtypes = [ ctypes.c_void_p ] | |
_lib.testlib_vectorD_delete.restype = None | |
#======== C-wrappers for function interface class InterfaceClass =============# | |
# InterfaceClass* teslib_InterfaceClass_factory(const char* class_id) | |
_lib.teslib_InterfaceClass_factory.argtypes = [ ctypes.POINTER(ctypes.c_char) ] | |
_lib.teslib_InterfaceClass_factory.restype = ctypes.c_void_p | |
# const char* testlib_InterfaceClass_getID(InterfaceClass* hinst) | |
_lib.testlib_InterfaceClass_getID.argtypes = [ ctypes.c_void_p ] | |
_lib.testlib_InterfaceClass_getID.restype = ctypes.POINTER(ctypes.c_char) | |
# const char* testlib_InterfaceClass_getName(InterfaceClass* hinst) | |
_lib.testlib_InterfaceClass_getName.argtypes = [ ctypes.c_void_p ] | |
_lib.testlib_InterfaceClass_getName.restype = ctypes.POINTER(ctypes.c_char) | |
# void testlib_InterfaceClass_setName(InterfaceClass* hinst, const char* name) | |
_lib.testlib_InterfaceClass_setName.argtypes = [ ctypes.c_void_p, ctypes.POINTER(ctypes.c_char) ] | |
_lib.testlib_InterfaceClass_setName.restype = None | |
print("Library initialized OK.") | |
# Initializae module. | |
_config() | |
class VectorD: | |
def __init__(self, handle): | |
self.hnd = ctypes.c_void_p(handle) | |
self.name = "std::vector<double> vx" | |
@classmethod | |
def fromValue(cls, size, x): | |
return VectorD(_lib.testlib_vectorD_make0(size, x)) | |
@classmethod | |
def fromArray(cls, array): | |
carray_size_n = ctypes.c_double * len(array) | |
return VectorD(_lib.testlib_vectorD_make1(len(array), carray_size_n(*array))) | |
# Destructor | |
def __del__(self): | |
print(" [TRACE] - Vector disposed - C++ Destructor invoked Ok.") | |
_lib.testlib_vectorD_delete(self.hnd) | |
def setName(self, name): | |
self.name = name | |
# Display vector | |
def disp(self): | |
_lib.testlib_vectorD_Linalg_printVector(self.name.encode('utf-8'), self.hnd) | |
# Set element at nth position | |
def set(self, idx, x): | |
_lib.testlib_vectorD_set(self.hnd, idx, x) | |
def norm(self): | |
return _lib.testlib_vectorD_Linalg_norm(self.hnd) | |
# Proxy for C++ Interface class in the shared library | |
class CPPInterfaceClass: | |
# Constructor | |
def __init__(self, handle): | |
self.hnd = ctypes.c_void_p(handle) | |
# Destructor | |
def __del__(self): | |
# Call C++ destructor | |
# print(" [__del__] => self.hnd = " + str(self.hnd)) | |
_lib.testlib_InterfaceClass_delete(self.hnd) | |
@classmethod | |
def factory(cls, classID): | |
return CPPInterfaceClass(_lib.teslib_InterfaceClass_factory(classID.encode('utf-8'))) | |
@classmethod | |
def makeA(cls): | |
"Instantiate the class ImplementationA from the DLL." | |
return CPPInterfaceClass.factory("ImplementationA") | |
@classmethod | |
def makeB(cls): | |
"Instantiate the class ImplementationB from the DLL." | |
return CPPInterfaceClass.factory("ImplementationB") | |
def getType(self): | |
return ctypes.string_at(_lib.testlib_InterfaceClass_getID(self.hnd)).decode('utf-8') | |
def getName(self): | |
return ctypes.string_at(_lib.testlib_InterfaceClass_getName(self.hnd)).decode('utf-8') | |
def setName(self, name): | |
_lib.testlib_InterfaceClass_setName(self.hnd, name.encode('utf-8')) | |
# String representation | |
def __str__(self): | |
s = "CInterfaceClass ; type = " + self.getType() | |
s += " - name = " + self.getName() + "\n" | |
return s | |
# Make class printable in the REPL | |
def __repr__(self): | |
return self.__str__() | |
def test1(): | |
print("\n ======== Test 1 - std::vector<double> wrapper and Linalg module ======") | |
v1 = VectorD.fromValue(4, 3.5) | |
print(" [*]=> Before changing std::vector<double> object") | |
v1.disp() | |
print("v1.norm() = " + str(v1.norm())) | |
print() | |
print(" [*]=> After changing std::vector<double> object") | |
v1.set(0, 5); v1.set(1, 2.6); v1.set(2, 9.81); v1.set(3, 3.76) | |
v1.disp() | |
print("v1.norm() " + str(v1.norm())) | |
print() | |
print("\n ======== Interface class 'InterfaceClass' ======") | |
clsA = CPPInterfaceClass.makeA() | |
print("clsA = " + str(clsA)) | |
print("clsA.getType() = " + clsA.getType()) | |
clsB = CPPInterfaceClass.makeB() | |
print("clsB = " + str(clsB)) | |
print("clsB.getType() = " + clsB.getType()) | |
if __name__ == "__main__": | |
test1() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// File: testlib.cpp | |
// Brief: Sample Windows shared library / DLL implementation | |
// Author: Caio Rodrigues | |
//--------------------------------------------------------------- | |
#include <iostream> | |
#include <string> | |
#include <vector> | |
#include <cmath> | |
#include <sstream> | |
#include <functional> | |
#ifdef _WIN32 | |
#include <windows.h> | |
#else | |
#define OutputDebugString(msg) std::perror(msg) | |
#endif | |
#include "testlib.hpp" | |
#define WindbgTrace(text) \ | |
{ std::stringstream ss; \ | |
ss << __FILE__ << ":" << __LINE__ << ": <" << __FUNCTION__ << "> " \ | |
<< text << std::endl; \ | |
OutputDebugString(ss.str().c_str()); \ | |
} | |
#define DbgTrace(text) \ | |
{ std::cerr << __FILE__ << ":" << __LINE__ << ": <" << __FUNCTION__ << "> " \ | |
<< text << std::endl; } | |
#define DbgDisp(expr) \ | |
{ std::cerr << __FILE__ << ":" << __LINE__ << ": <" << __FUNCTION__ << "> " \ | |
<< #expr << " = " << (expr) << std::endl; } | |
/** - DLL Entry point - main function of DLL which is executed when | |
the DLL is loaded by some process. | |
*/ | |
#if defined(_WIN32) | |
extern "C" __declspec(dllexport) | |
BOOL APIENTRY DllMain (HINSTANCE hInst, DWORD reason, LPVOID lpReserved) | |
{ | |
std::string text = | |
std::string("DLL Loaded into the process => PID = ") | |
+ std::to_string(::GetCurrentProcessId()); | |
WindbgTrace(text); | |
DbgTrace(text); | |
switch (reason) | |
{ | |
case DLL_PROCESS_ATTACH: | |
WindbgTrace("DLL attached to process"); | |
DbgTrace("DLL attached to process."); | |
break; | |
case DLL_PROCESS_DETACH: | |
WindbgTrace("=> DLL detached."); | |
DbgTrace("=> DLL attached"); | |
break; | |
case DLL_THREAD_ATTACH: | |
WindbgTrace("DLL attached to thread"); | |
DbgTrace("DLL detached to thread."); | |
break; | |
case DLL_THREAD_DETACH: | |
WindbgTrace("DLL detached from thread"); | |
DbgTrace("DLL detached from thread."); | |
break; | |
} | |
return TRUE; | |
} | |
#endif | |
//================================================================// | |
// Linear algebra tools | |
namespace Linalg{ | |
EXPORT_CPP | |
double norm(const std::vector<double>& xs){ | |
double sum = 0.0; | |
for(const auto& x : xs) sum += x * x; | |
return std::sqrt(sum); | |
} | |
EXPORT_CPP | |
std::vector<double> | |
linTransform(double a, double b, std::vector<double>& xs){ | |
std::vector<double> out(xs.size()); | |
for(size_t i = 0; i < xs.size(); i++){ | |
out[i] = a * xs[i] + b; | |
} | |
return out; | |
} | |
EXPORT_CPP | |
std::ostream& | |
printVector(std::ostream& os, std::vector<double>& xs){ | |
os << "[" << xs.size() << "]( "; | |
for(const auto& x: xs) | |
os << x << ", "; | |
return os << " )"; | |
} | |
} | |
//=========== C-wrappers ---------------/// | |
// Handler for double vector | |
using hVectorD = void*; | |
using pVectorD = std::vector<double>*; | |
/** C-wrapper for vector<double> constructor */ | |
EXPORT_C | |
hVectorD testlib_vectorD_make0(size_t n, double x){ | |
return new std::vector<double>(n, x); | |
} | |
/** C-wrapper for range constructor */ | |
EXPORT_C | |
hVectorD testlib_vectorD_make1(size_t n, double array []){ | |
return new std::vector<double>(array, array + n); | |
} | |
/** C-wrapper for setting elements of vector<double> */ | |
EXPORT_C | |
void testlib_vectorD_set(hVectorD hv, size_t n, double x){ | |
static_cast<pVectorD>(hv)->operator[](n) = x; | |
} | |
/** C-wrapper for vector<double> destructor */ | |
EXPORT_C void testlib_vectorD_delete(hVectorD hv){ | |
delete reinterpret_cast<pVectorD>(hv); | |
} | |
/** C-wrapepr for Linalg::norm function */ | |
EXPORT_C | |
double testlib_vectorD_Linalg_norm(hVectorD hv){ | |
return Linalg::norm(*reinterpret_cast<pVectorD>(hv)); | |
} | |
EXPORT_C | |
void testlib_vectorD_Linalg_printVector(const char* name, hVectorD hv){ | |
std::cout << name << " = "; | |
Linalg::printVector(std::cout, *reinterpret_cast<pVectorD>(hv)); | |
std::cout << std::endl; | |
} | |
//==========>>> class SampleClass <<=========== | |
SampleClass::SampleClass(const std::string& name) | |
: m_name(name), m_counter(0) | |
{ | |
std::cout << " Instance created with name = " << m_name << std::endl; | |
} | |
/** Delegated constructor on right-hand-side */ | |
SampleClass::SampleClass(): SampleClass("unnamed"){} | |
SampleClass::~SampleClass(){ | |
std::string text = std::string("SampleClass => name = ") + m_name + " deleted"; | |
DbgTrace(text); | |
} | |
std::string SampleClass::getName() const { | |
return m_name; | |
} | |
int SampleClass::SampleClass::get(){ | |
return m_counter; | |
} | |
void SampleClass::set(int n){ | |
std::cout << " Counter set to value = " << n << std::endl; | |
m_counter = n; | |
} | |
//======= C-interface of SampleClass ===========// | |
using hSampleClass = void*; | |
/** Nullable constructor zero-arg constructor */ | |
EXPORT_C hSampleClass testlib_SampleClass_make0() | |
{ | |
return new SampleClass(); | |
} | |
/** Other constructor */ | |
EXPORT_C | |
auto testlib_SampleClass_make1(const char* name) -> hSampleClass | |
{ | |
return new SampleClass(name); | |
} | |
/** Destructor */ | |
EXPORT_C | |
auto testlib_SampleClass_delete(hSampleClass hnd) -> void | |
{ | |
delete reinterpret_cast<SampleClass*>(hnd); | |
} | |
/** Wrapper for get method */ | |
EXPORT_C | |
int testlib_SampleClass_get(hSampleClass hnd) | |
{ | |
return reinterpret_cast<SampleClass*>(hnd)->get(); | |
} | |
/** Wrapper for set method */ | |
EXPORT_C | |
void testlib_SampleClass_set(hSampleClass hnd, int n) | |
{ | |
return reinterpret_cast<SampleClass*>(hnd)->set(n); | |
} | |
EXPORT_C | |
const char* testlib_SampleClass_getName(hSampleClass hnd) | |
{ | |
return reinterpret_cast<SampleClass*>(hnd)->getName().c_str(); | |
} | |
//========= Implementations of the interface class ===// | |
class ImplementationA: public InterfaceClass | |
{ | |
private: | |
std::string m_name; | |
public: | |
static constexpr const char* class_id = "ImplementationA"; | |
ImplementationA(): m_name("Unammed-A"){ } | |
ImplementationA(const std::string& name) | |
: m_name(name){} | |
~ImplementationA(){ | |
std::cout << " [INFO] ImplementationA deleted => name = " | |
<< m_name | |
<< " ; type = " << class_id | |
<< std::endl; | |
} | |
const char* getID() const { | |
return class_id; | |
} | |
void setName(const char* name) { | |
m_name = name; | |
} | |
const char* getName() { | |
return m_name.c_str(); | |
} | |
}; | |
class ImplementationB: public InterfaceClass | |
{ | |
private: | |
std::string m_name; | |
public: | |
static constexpr const char* class_id = "ImplementationB"; | |
ImplementationB(): m_name("Unammed-B"){ } | |
ImplementationB(const std::string& name) | |
: m_name(name){} | |
~ImplementationB(){ | |
std::cout << " [INFO] ImplementationB deleted => name = " | |
<< m_name | |
<< " ; type = " << class_id | |
<< std::endl; | |
} | |
const char* getID() const { | |
return class_id; | |
} | |
void setName(const char* name) { | |
m_name = name; | |
} | |
const char* getName() { | |
return m_name.c_str(); | |
} | |
}; | |
EXPORT_C | |
InterfaceClass* | |
teslib_InterfaceClass_factory(const char* class_id) | |
{ | |
auto s = std::string(class_id); | |
if(s == "ImplementationA") | |
return new ImplementationA(); | |
if(s == "ImplementationB") | |
return new ImplementationB(); | |
return nullptr; | |
} | |
EXPORT_C void testlib_InterfaceClass_delete(InterfaceClass* hinst) | |
{ | |
delete hinst; | |
} | |
EXPORT_C | |
const char* testlib_InterfaceClass_getID(InterfaceClass* hinst) | |
{ | |
return hinst->getID(); | |
} | |
EXPORT_C | |
void testlib_InterfaceClass_setName(InterfaceClass* hinst, const char* name) | |
{ | |
hinst->setName(name); | |
} | |
EXPORT_C | |
const char* testlib_InterfaceClass_getName(InterfaceClass* hinst){ | |
return hinst->getName(); | |
} | |
///================================================// | |
/** Static object used for DLL initialization, it is a cross platform | |
* replacement for the Windows function DLLMain | |
* | |
* Class defined in anonymous namespace becomes private to this | |
* compilation unit - cannot be accessed from any other file | |
*************************************************************/ | |
namespace { | |
class _StaticObject{ | |
public: | |
using Action = std::function<void ()>; | |
Action m_end; | |
_StaticObject(Action init, Action end) | |
: m_end(std::move(end)) | |
{ | |
init(); | |
} | |
~_StaticObject(){ m_end(); } | |
_StaticObject(const _StaticObject&) = delete; | |
_StaticObject& operator=(const _StaticObject&) = delete; | |
_StaticObject(_StaticObject&&) = default; | |
_StaticObject& operator=(_StaticObject &&) = default; | |
}; | |
auto initDLL = _StaticObject( | |
[]{ | |
std::cout << " [StaticObject] => Initialize DLL" | |
<< std::endl; | |
}, | |
[]{ | |
std::cout << " [StaticObject] => Shutdown DLL" | |
<< std::endl; | |
}); | |
} | |
/*** ===========>>>> run32dll Entry Points ================= */ | |
#if defined(_WIN32) | |
extern "C" __declspec(dllexport) | |
void entryPoint1(HWND hwn, HINSTANCE hinst, LPSTR cmdLine, int nCmdShow){ | |
DbgDisp(cmdLine); | |
OutputDebugString("Rudll32 called entryPoint1()"); | |
MessageBoxA(NULL, "DLL ENTRY POINT", "Entry point 1", 0); | |
} | |
#endif | |
extern "C" __declspec(dllexport) | |
int main(){ | |
std::cout << "Running program OK." << std::endl; | |
return 0; | |
} | |
void NonExportedFunction(){ | |
std::cout << "This function is not visible exported" << std::endl; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// File: testlib.hpp | |
// Brief: Sample Windows shared library / DLL header | |
// Author: Caio Rodrigues | |
//------------------------------------------------------ | |
#ifndef _TESTLIB_H_ | |
#define _TESTLIB_H_ | |
#include <ostream> | |
#include <vector> | |
#include <string> | |
/** Macro EXPORT_CPP makes a symbol visible. */ | |
#if defined(_WIN32) | |
#define EXPORT_CPP __declspec(dllexport) | |
#else | |
#define EXPORT_CPP __attribute__ ((visibility ("default"))) | |
// If not compiled on Windows, remove declspec compiler extension. | |
#ifndef __declspec | |
// For GCC only - make exported symbol visible symbol | |
#define __declspec(param) __attribute__ ((visibility ("default"))) | |
#endif | |
#endif | |
/* Macro EXPORT_C is used for exporting symbol with C-linkage, it | |
* means, without name mangling */ | |
#ifdef __cplusplus | |
// extern "C" - Indicates that a given symbol/function has C-linkage and | |
// does not have name mangling. | |
// | |
// On Linux EXPORT_C becomes | |
// => extern "C" __attribute__ ((visibility ("default"))) | |
// | |
// On Windows EXPORT_C becomes | |
// => extern "C" __declspec(dllexport) | |
#define EXPORT_C extern "C" EXPORT_CPP | |
#else | |
// If a C-compiler uses this header, remove 'extern "C"' | |
#define EXPORT_C EXPORT_CPP | |
#endif | |
/** The macro __cplusplus is used for allowing this | |
* header to be used from 'C'. If a C compiler is used | |
* all definitions inside this #ifdef are discarded. | |
*/ | |
#ifdef __cplusplus | |
namespace Linalg { | |
EXPORT_CPP | |
double norm(const std::vector<double>& xs); | |
EXPORT_CPP std::vector<double> | |
linTransform( | |
double a, | |
double b, | |
std::vector<double>& xs | |
); | |
EXPORT_CPP | |
std::ostream& | |
printVector(std::ostream& os, std::vector<double>& xs); | |
} | |
#endif | |
// ======= C-interface for Linalg namespace =========// | |
/** Handle or opaque pointer for std::vector<double> */ | |
typedef void* hVectorD; | |
/* ----- C-Wrappers for Linalg namespace ---- */ | |
EXPORT_C | |
double testlib_vectorD_Linalg_norm(hVectorD hv); | |
EXPORT_C | |
void testlib_vectorD_Linalg_printVector(const char* name, hVectorD hv); | |
// ======= Non-polymorphic class exported by DLL =========// | |
#ifdef __cplusplus | |
// Non-polymorphic class | |
class EXPORT_CPP SampleClass{ | |
public: | |
SampleClass(); | |
SampleClass(const std::string& name); | |
~SampleClass(); | |
std::string getName() const; | |
int get(); | |
void set(int n); | |
private: | |
std::string m_name; | |
int m_counter; | |
}; | |
#endif | |
/* ----- C-Wrappers for SampleClass namespace ---- */ | |
using hSampleClass = void*; | |
/** Nullable constructor zero-arg constructor */ | |
EXPORT_C | |
hSampleClass | |
testlib_SampleClass_make0(); | |
/** Other constructor */ | |
EXPORT_C hSampleClass testlib_SampleClass_make1(const char* name); | |
/** Destructor */ | |
EXPORT_C | |
void | |
testlib_SampleClass_delete(hSampleClass hnd); | |
/** Wrapper for get method */ | |
EXPORT_C | |
int | |
testlib_SampleClass_get(hSampleClass hnd); | |
/** Wrapper for set method */ | |
EXPORT_C | |
void | |
testlib_SampleClass_set(hSampleClass hnd, int n); | |
EXPORT_C | |
const char* | |
testlib_SampleClass_getName(hSampleClass hnd); | |
//========== Interface class ============// | |
// Polymorphic Interface class | |
// binary compatible across different compilers | |
// as it does not use any STL container on the | |
// interface. | |
#ifdef __cplusplus | |
struct InterfaceClass{ | |
/* Returns class unique ID */ | |
virtual const char* getID() const = 0; | |
/** Set class internal state */ | |
virtual void setName(const char* name) = 0; | |
virtual const char* getName() = 0; | |
/** Virtual constructor */ | |
virtual ~InterfaceClass() = default; | |
// virtual ~InterfaceClass(); | |
}; | |
#else | |
#define InterfaceClass void | |
#endif | |
/** Factory function */ | |
EXPORT_C InterfaceClass* teslib_InterfaceClass_factory(const char* class_id); | |
/** C-wrapper for destructor */ | |
EXPORT_C void testlib_InterfaceClass_delete(InterfaceClass* hinst); | |
/** C-wrapper for getID method */ | |
EXPORT_C const char* testlib_InterfaceClass_getID(InterfaceClass* hinst); | |
EXPORT_C void testlib_InterfaceClass_setName(InterfaceClass* hinst, const char* name); | |
EXPORT_C const char* testlib_InterfaceClass_getName(InterfaceClass* hinst); | |
#endif /** --- End of file */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment