{"id":5787,"date":"2020-08-10T07:00:22","date_gmt":"2020-08-10T14:00:22","guid":{"rendered":"http:\/\/writeasync.net\/?p=5787"},"modified":"2020-08-07T12:03:41","modified_gmt":"2020-08-07T19:03:41","slug":"cross-platform-without-complexity-shared-libraries","status":"publish","type":"post","link":"http:\/\/writeasync.net\/?p=5787","title":{"rendered":"Cross-platform without complexity: shared libraries"},"content":{"rendered":"<p>Continuing from our <a href=\"http:\/\/writeasync.net\/?p=5765\">cross-platform project skeleton<\/a>, let&#8217;s try to add a shared library. Windows users may know these as <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/win32\/dlls\/dynamic-link-libraries\">dynamic link libraries (DLLs)<\/a>, while Linux users would recognize them as <a href=\"https:\/\/stackoverflow.com\/questions\/9688200\/difference-between-shared-objects-so-static-libraries-a-and-dlls-so\">shared objects (.so)<\/a>. To make things even trickier, most Windows compilers must <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/build\/exporting-from-a-dll-using-declspec-dllexport?view=vs-2019\">explicitly export DLL members<\/a>, while Linux compilers usually <a href=\"http:\/\/anadoxin.org\/blog\/control-over-symbol-exports-in-gcc.html\">export everything by default<\/a>. Alas, cross-platform is a must so we cannot take any hard dependencies on these Win32\/Linux-isms.<\/p>\n<p>Luckily, CMake has us covered. <a href=\"https:\/\/stackoverflow.com\/questions\/58597988\/how-to-use-dynamic-link-library-with-cmake\">Using GenerateExportHeader<\/a>, we can easily account for platform specific behaviors without any ugly <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/preprocessor\/hash-ifdef-and-hash-ifndef-directives-c-cpp?view=vs-2019\"><code>#ifdef<\/code>s<\/a> (at least none that <em>we<\/em> have to write personally). This is the desired revised project structure:<\/p>\n<ul>\n<li><strong>inc<\/strong> <em>(public header file root)<\/em>\n<ul>\n<li><strong>core<\/strong> <em>(header files for `core` lib)<\/em><\/li>\n<li><ins><strong>lib<\/strong> <em>(header files for shared `lib`)<\/em><\/ins><\/li>\n<\/ul>\n<\/li>\n<li><strong>src<\/strong> <em>(source code root)<\/em>\n<ul>\n<li><strong>app<\/strong> <em>(executable target)<\/em><\/li>\n<li><strong>core<\/strong> <em>(static lib target)<\/em><\/li>\n<li><ins><strong>lib<\/strong> <em>(shared lib target)<\/em><\/ins><\/li>\n<\/ul>\n<\/li>\n<li><strong>test<\/strong> <em>(test code root)<\/em><\/li>\n<\/ul>\n<p>&#8230;and this is the CMake project to implement it, with minor additions and edits from the previous version:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ncmake_minimum_required(VERSION 3.17)\r\nproject(sample LANGUAGES CXX)\r\n\r\ninclude(GenerateExportHeader)\r\ninclude(GoogleTest)\r\nfind_package(GTest)\r\n\r\nadd_library(sample-core STATIC\r\n    src\/core\/Sample.cpp\r\n)\r\n\r\nset_property(TARGET sample-core\r\n    PROPERTY POSITION_INDEPENDENT_CODE ON\r\n)\r\n\r\ntarget_include_directories(sample-core\r\n    PUBLIC\r\n        ${CMAKE_CURRENT_SOURCE_DIR}\/inc\/core\r\n)\r\n\r\nadd_library(sample-lib SHARED\r\n    src\/lib\/SampleLib.cpp\r\n)\r\n\r\ngenerate_export_header(sample-lib\r\n    BASE_NAME samplelib\r\n    EXPORT_MACRO_NAME SAMPLE_LIB_EXPORT)\r\n\r\ntarget_include_directories(sample-lib\r\n    PUBLIC\r\n        ${CMAKE_CURRENT_SOURCE_DIR}\/inc\/lib\r\n        ${CMAKE_CURRENT_BINARY_DIR}\r\n)\r\n\r\ntarget_link_libraries(sample-lib\r\n    sample-core\r\n)\r\n\r\nadd_executable(sample-app\r\n    src\/app\/main.cpp\r\n)\r\n\r\ntarget_link_libraries(sample-app\r\n    sample-lib\r\n)\r\n . . . (remainder omitted as it is unchanged)\r\n<\/pre>\n<p>The summary of changes is as follows:<\/p>\n<ul>\n<li>Include <a href=\"https:\/\/cmake.org\/cmake\/help\/latest\/module\/GenerateExportHeader.html\">GenerateExportHeader<\/a> to use the export header functionality.<\/li>\n<li>Add explicit STATIC library type for the existing <code>sample-core<\/code> &#8212; just for clarity, not technically required.<\/li>\n<li>Enable <a href=\"https:\/\/cmake.org\/cmake\/help\/latest\/prop_tgt\/POSITION_INDEPENDENT_CODE.html\">POSITION_INDEPENDENT_CODE<\/a> for <code>sample-core<\/code>; this is required to avoid compilation errors when linking the shared library with the static library.<\/li>\n<li>Add the new shared library <code>sample-lib<\/code>.<\/li>\n<li>Generate an export header for <code>sample-lib<\/code> (with custom settings for nicer symbol names).<\/li>\n<li>Link <code>sample-lib<\/code> with <code>sample-core<\/code>.<\/li>\n<li>Link <code>sample-app<\/code> with <code>sample-lib<\/code>.<\/li>\n<\/ul>\n<p>Now we need to implement the code. We&#8217;ll start with the public header file, <code>SampleLib.h<\/code>:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#ifndef SAMPLE_INC_LIB_SAMPLELIB_H\r\n#define SAMPLE_INC_LIB_SAMPLELIB_H\r\n\r\n#include &lt;samplelib_export.h&gt;\r\n\r\nextern &quot;C&quot;\r\n{\r\n    SAMPLE_LIB_EXPORT void *SampleInit(const char *name);\r\n\r\n    SAMPLE_LIB_EXPORT const char *SampleGetName(void *p);\r\n\r\n    SAMPLE_LIB_EXPORT void SampleDestroy(void *p);\r\n}\r\n\r\n#endif\r\n<\/pre>\n<p>You may notice the CMake magic showing up here; instead of using compiler-specific features, the generated <code>samplelib_export.h<\/code> header defines symbols like <code>SAMPLE_LIB_EXPORT<\/code> to mark public members. Also note the standard practice of flattening the C++ API into a plain C interface which will ensure a <a href=\"https:\/\/stackoverflow.com\/questions\/4489012\/does-c-have-a-standard-abi\">stable (enough) ABI<\/a>.<\/p>\n<p>Now the implementation file, <code>SampleLib.cpp<\/code>:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#include &lt;SampleLib.h&gt;\r\n#include &lt;Sample.h&gt;\r\n#include &lt;memory&gt;\r\n\r\nusing sample::core::Sample;\r\n\r\nextern &quot;C&quot;\r\n{\r\n    void *SampleInit(const char *name)\r\n    {\r\n        return new Sample(name);\r\n    }\r\n\r\n    const char *SampleGetName(void *p)\r\n    {\r\n        return static_cast&lt;Sample *&gt;(p)-&gt;get_name().c_str();\r\n    }\r\n\r\n    void SampleDestroy(void *p)\r\n    {\r\n        delete static_cast&lt;Sample*&gt;(p);\r\n    }\r\n}\r\n<\/pre>\n<p>Yes, that is a <a href=\"https:\/\/andrewshitov.com\/2019\/11\/11\/avoiding-naked-new-in-modern-cpp\/\">naked new and delete<\/a>. We&#8217;re forced to do this because we are defining explicit memory management functions to avoid leaking any C++ details outside the library. Bad things could happen if we tried to <a href=\"https:\/\/stackoverflow.com\/questions\/29186902\/using-stdunique-ptr-in-an-exported-class\">export a function with unique_ptr in its signature<\/a>.<\/p>\n<p>The final step is using the shared library from the executable in our <code>main.cpp<\/code>:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#include &lt;iostream&gt;\r\n#include &lt;memory&gt;\r\n#include &lt;SampleLib.h&gt;\r\n\r\nusing std::cout;\r\nusing std::endl;\r\nusing std::unique_ptr;\r\n\r\nint main(int argc, const char *argv&#x5B;])\r\n{\r\n    if (argc != 2)\r\n    {\r\n        cout &lt;&lt; &quot;Please pass a string!&quot; &lt;&lt; endl;\r\n        return 1;\r\n    }\r\n\r\n    unique_ptr&lt;void, void(*)(void*)&gt; p(SampleInit(argv&#x5B;1]), &amp;SampleDestroy);\r\n    cout &lt;&lt; &quot;Hello, &quot; &lt;&lt; SampleGetName(p.get()) &lt;&lt; &quot;!&quot; &lt;&lt; endl;\r\n    return 0;\r\n}\r\n<\/pre>\n<p>Here we are at least trying to make things safe by declaring a <a href=\"https:\/\/www.bfilipek.com\/2016\/04\/custom-deleters-for-c-smart-pointers.html\">unique_ptr with custom deleter<\/a>.<\/p>\n<p>Since our CMake project already includes tests that launch the executable we can even show that this works by running our command line build scripts on Linux:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n== ctest\r\nTest project \/mnt\/x\/root\/CMakeSampleVS\/out\/build\/Linux-Clang-Debug\r\n    Start 1: SampleTest.GetName\r\n1\/3 Test #1: SampleTest.GetName ...............   Passed    0.02 sec\r\n    Start 2: sample-app-UsageError\r\n2\/3 Test #2: sample-app-UsageError ............   Passed    0.01 sec\r\n    Start 3: sample-app-HelloWorld\r\n3\/3 Test #3: sample-app-HelloWorld ............   Passed    0.01 sec\r\n\r\n100% tests passed, 0 tests failed out of 3\r\n\r\nTotal Test time (real) =   0.09 sec\r\n<\/pre>\n<p>&#8230;and on Windows:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n== ctest.exe\r\nTest project X:\/root\/CMakeSampleVS\/out\/build\/x64-Debug\r\n    Start 1: SampleTest.GetName\r\n1\/3 Test #1: SampleTest.GetName ...............   Passed    0.01 sec\r\n    Start 2: sample-app-UsageError\r\n2\/3 Test #2: sample-app-UsageError ............   Passed    0.05 sec\r\n    Start 3: sample-app-HelloWorld\r\n3\/3 Test #3: sample-app-HelloWorld ............   Passed    0.01 sec\r\n\r\n100% tests passed, 0 tests failed out of 3\r\n\r\nTotal Test time (real) =   0.12 sec\r\n<\/pre>\n<p>GitHub repo with all the changes so far: <a href=\"https:\/\/github.com\/bobbymcr\/CMakeSampleVS\/tree\/c1aaaa1e59a3a5048482d70b82fb3406244f6468\">CMakeSampleVS<\/a><\/p>\n<p>As you can see, cross-platform support for C\/C++ is possible without any real sacrifice. You can use the VS IDE, you can use command line build scripts, and you can even create shared libraries for each OS from one codebase. Vive la [plate-forme?] diff\u00e9rence!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Continuing from our cross-platform project skeleton, let&#8217;s try to add a shared library. Windows users may know these as dynamic link libraries (DLLs), while Linux users would recognize them as shared objects (.so). To make things even trickier, most Windows&hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[107,101],"tags":[],"class_list":["post-5787","post","type-post","status-publish","format-standard","hentry","category-cross-platform","category-native"],"_links":{"self":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5787","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5787"}],"version-history":[{"count":2,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5787\/revisions"}],"predecessor-version":[{"id":5789,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5787\/revisions\/5789"}],"wp:attachment":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5787"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5787"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5787"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}