C++ - Reddit
228 subscribers
48 photos
8 videos
1 file
25.4K links
Stay up-to-date with everything C++!
Content directly fetched from the subreddit just for you.

Join our group for discussions : @programminginc

Powered by : @r_channels
Download Telegram
Proposal for testing global functions

I am writing tests for an embedded project and found myself wondering why testing global / static or free functions is so controversial and why it would have to contain expensive trickery with the linker or other parts of the compiler in order to achieve them ([related stackoverflow post](https://stackoverflow.com/questions/28392277/mocking-free-function)).

This is why wanted to share my approach on this which is based on:

* some sort of \`typeid\` method ([with no rtti for embedded](https://stackoverflow.com/questions/8001207/compile-time-typeid-without-rtti-with-gcc))
* a mapping of function pointers (used unordered\_map here for simplicity)
* a macro-based check in every mocked function with some sort of call-forwarding

The drawback with this method is obviously the last point which requires that every function you want to mock out has to have this check at the beginning, and the need to have a globally available mocking repository. Its a POC at best and I'm looking for ways on how to improve it further. Thoughts about the usefulness of this approach in larger projects?

#pragma once

#include <unordered_map>

#ifndef __TEST__

#define ADD_MOCK(FOO, MOCK)
#define CLEAR_MOCK(FOO)
#define CLEAR_MOCKS()
#define CHECK_MOCK(FOO, ...)

#else

#define ADD_MOCK(FOO, MOCK) Mocks::add(FOO, MOCK)
#define CLEAR_MOCK(FOO) Mocks::remove(FOO)
#define CLEAR_MOCKS() Mocks::clear()
#define CHECK_MOCK(FOO, ...) \
do { \
auto _mock = Mocks::get(FOO); \
if (!_mock) break; \
return _mock(__VA_ARGS__); \
} while (0)

template <class T>
size_t typeidNoRtti(__attribute__((unused)) const T value = nullptr) {
static T *marker = NULL;
return (size_t)&marker;
}

namespace Mocks
{
struct MockFn {
void *mock;
size_t type;
};

extern std::unordered_map<void *, MockFn> _mocks;

template <typename T>
static inline void add(const T &fn, const T &mock) {
_mocks.insert(std::make_pair((void *)fn, MockFn{(void *)mock, typeidNoRtti(fn)}));
}

template <typename T>
static inline void remove(const T &fn) { _mocks.erase((void *)fn); }
static inline void clear() { _mocks.clear(); }

template <typename T>
static inline T *get(const T &fn) {
if (!_mocks.count((void *)fn)) return nullptr;
const auto &mock = _mocks.at((void *)fn);
if (typeidNoRtti(fn) != mock.type) return nullptr;
return (T *)mock.mock;
}
};

#endif

Usage:

#include "test.hpp"

static int8_t foo(int8_t a) {
CHECK_MOCK(foo, a);
return a;
}
static int8_t foo_mock(int8_t a) { return -a; }
static int bar(int a) { return 10 * a; }

UNIT_TEST(Mock, Basic) {
ADD_MOCK(foo, foo_mock);

EXPECT_EQ(Mocks::get(foo), foo_mock);
EXPECT_EQ(Mocks::get(bar), nullptr);
EXPECT_EQ(Mocks::get((void *)foo), nullptr);
EXPECT_EQ(foo(1), -1);

CLEAR_MOCK(foo);

EXPECT_EQ(Mocks::get(foo), nullptr);
EXPECT_EQ(foo(1), 1);
}

https://redd.it/1fqlb4c
@r_cpp
Efficient Softmax jacobian and gradient algorithms

I am writing a function in C++, for the task of using the output of the Softmax() function, and calculating the jacobians before using them to calculate the gradients of the output W.R.T. the Softmax input. Normally, this would be a relatively straightforward task, but I am working with an architecture that uses tensor data. the function must be adaptive enough to handle input data(the output of the Softmax function) of any shape, where Softmax() was applied along any dimension. I have made a version that works, but it is extremely slow, and I need a faster method. Here is my current function:

static void softmaxJacobian(const float* x, float* y, const float* outputGrad, const int dimension, const int* shape, const int jSize, const int dataSize, const int blockStride) {
    using namespace std::chrono;
    
    int numBlocks = dataSize / jSize;
    
    // Allocate memory once outside the loop
    float* jacobian = new float[jSize * jSize];
    
    auto start = high_resolution_clock::now();
    
    // Parallelize over blocks, and use thread-private arrays for slices
    #pragma omp parallel
    {
        float* slice = new float[jSize];
        float* outputSlice = new float[jSize];
        
        #pragma omp for
        for (int blockIndex = 0; blockIndex < numBlocks; blockIndex++) {
            int blockStartIndex = (blockIndex / blockStride) * (blockStride * jSize) + (blockIndex % blockStride);
            
            // Efficiently extract data for the current block
            for (int i = 0; i < jSize; i++) {
                int index = blockStartIndex + i * blockStride;
                slice[i] = x[index];
                outputSlice[i] = outputGrad[index];
            }
            
            // Construct the Jacobian matrix (optimize for diagonal and off-diagonal)
            for (int i = 0; i < jSize; i++) {
                for (int j = 0; j < jSize; j++) {
                    jacobian[i * jSize + j] = (i == j) ? slice[i] * (1 - slice[i]) : -(slice[i] * slice[j]);
                }
            }
            
            // Perform matrix-vector multiplication (Jacobian * outputSlice)
            for (int i = 0; i < jSize; i++) {
                float sum = 0.0f;
                for (int j = 0; j < jSize; j++) {
                    sum += jacobian[i * jSize + j] * outputSlice[j];
                }
                int index = blockStartIndex + i * blockStride;
                y[index] = sum;  // Write the result back
            }
        }
        
        // Cleanup thread-local memory
        delete[] slice;
        delete[] outputSlice;
    }
    
    auto end = high_resolution_clock::now();
    std::cout << "Total time for function: " << duration_cast<milliseconds>(end - start).count() << " ms\n";
    
    // Cleanup
    delete[] jacobian;
}

Any suggestions on improved algorithms?

https://redd.it/1g5i8m8
@r_cpp
ImGui::NewFrame() throwing an error after second call

The error is: "abort() has been called"

This is the code:





#include <enet/enet.h>

#include <glad/glad.h>

#include <GLFW/glfw3.h>

#include <stb_image/stb_image.h>

#include <stb_truetype/stb_truetype.h>

#include "gl2d/gl2d.h"

#include <iostream>

#include <ctime>

#include "platformTools.h"

#include <raudio.h>

#include "platformInput.h"

#include "otherPlatformFunctions.h"

#include "gameLayer.h"

#include <fstream>

#include <chrono>

#include "errorReporting.h"

#include "imgui.h"

#include "backends/imgui_impl_glfw.h"

#include "backends/imgui_impl_opengl3.h"

#include "imguiThemes.h"





#ifdef _WIN32

#include <Windows.h>

#endif



#undef min

#undef max




int main()

{

GLFWwindow* window;







#pragma region window and opengl



permaAssertComment(glfwInit(), "err initializing glfw");

glfwWindowHint(GLFW_SAMPLES, 4);



#ifdef __APPLE__

glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, 1);

glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);

glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);

#endif





int w = 500;

int h = 500;

window = glfwCreateWindow(w, h, "Window", nullptr, nullptr);

glfwMakeContextCurrent(window);

glfwSwapInterval(1);





//permaAssertComment(gladLoadGL(), "err initializing glad");

permaAssertComment(gladLoadGLLoader((GLADloadproc)glfwGetProcAddress), "err initializing glad");







#pragma endregion



#pragma region gl2d

gl2d::init();

#pragma endregion





#pragma region imgui

ImGui::CreateContext();

imguiThemes::embraceTheDarkness();



ImGuiIO& io = ImGui::GetIO(); (void)io;

io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;

`//io.ConfigFlags` |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls

io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking

io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows





ImGuiStyle& style = ImGui::GetStyle();

if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)

{

style.Colors[ImGuiCol_WindowBg].w = 0.f;

style.Colors[ImGuiCol_DockingEmptyBg].w = 0.f;

}



ImGui_ImplGlfw_InitForOpenGL(window, true);

ImGui_ImplOpenGL3_Init("#version 330");



#pragma endregion











while (!glfwWindowShouldClose(window))

{





glfwPollEvents();





ImGui_ImplOpenGL3_NewFrame();

ImGui_ImplGlfw_NewFrame();



glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

glClear(GL_COLOR_BUFFER_BIT);





ImGui::NewFrame();

ImGui::Begin("My Scene");



ImGui::End();

ImGui::Render();





ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());



glfwSwapBuffers(window);







}







}

https://redd.it/1harvjb
@r_cpp
Can I put import inside the global module fragment?

So I am working on importizer that automatically create a module from a header file. It does so by collecting preprocessor directives, especially conditional ones, that has a #include inside and recreate it on top. For example:

// File.h
#pragma once
#ifdef COND
#include <vector>
#include <modularizedHeader.h>
#endif

will create this preamble

module;
#ifdef COND
#include <vector>
#endif
export module File;
#ifdef COND
import modularizedHeader;
#endif

which repeats the condition twice. With more complex conditions, the length will very quickly get out of hand.

Can I put import in the GMF like this to save some space?

module;
#ifdef COND
#include <vector>
import modularizedHeader;
#endif
export module File;

I was suspicious at first so I tested this approach on Godbolt (try putting the import into the GMF), and it's fine. I even read the C++ standard for modules, and I don't see any regulation about their location. Moreover, on cppreference, only preprocessing directives can go into the GMF, and import does count as one.

Is there any problem with doing it like this, and is there a better way to repeat the condition only once?

https://redd.it/1ihbb9f
@r_cpp
Inexplicable error when trying to intercept dll calls with a proxy

I am currently trying to modify an application called **EdingCNC**, which relies on a dynamic link library called `cncapi.dll` for its functionality. My goal is to **intercept calls** to the function`CncRunSingleLine` inside the DLL, modify the behavior if necessary, and then forward the call to the original function. All other functions are forwarded via `#pragma comment(linker, "/export:...=...,@...")`

The function signature (as defined in `cncapi.dll`) is:

`CNC_RC EXP2DF __stdcall CncRunSingleLine(char *text);`

# My Approach (Proxy DLL Implementation)

To intercept this function, I created a **proxy DLL** with the same export name. This DLL captures calls to `CncRunSingleLine`, executes some additional logic, and then calls the original function inside `cncapi.dll`. Here’s my current implementation:

#define C_DLL_EXPORT __declspec(dllexport) // or your defined macro if different

typedef CNC_RC(__stdcall* pfCncRunSingleLine)(char* text); // Correct function pointer type

extern "C" { // Ensures C-style linkage (no name mangling)
// Export the function with the correct return type (CNC_RC)
C_DLL_EXPORT CNC_RC CncRunSingleLine(char* text)
{
if (text == nullptr)
{
::MessageBoxW(NULL, L"Invalid input parameter: text is NULL", L"Error", MB_ICONERROR);
return CNC_RC_ERR; // Return error if the text parameter is invalid
}

// 1) Get the original DLL handle (adjust the DLL name if needed)
HMODULE hModule = ::GetModuleHandle(TEXT("_cncapi.dll")); // Adjust the DLL name if required
if (!hModule)
{
// Display error and return failure code if DLL is not found
::MessageBoxW(NULL, L"Failed to load the original CNC API DLL", L"Error", MB_ICONERROR);
return CNC_RC_ERR; // Return error code
}

// 2) Get the function address inside the original DLL
pfCncRunSingleLine pOriginalCncRunSingleLine = (pfCncRunSingleLine)::GetProcAddress(hModule, "CncRunSingleLine");
if (!pOriginalCncRunSingleLine)
{
// Display error and return failure code if function is not found
::MessageBoxW(NULL, L"Failed to find CncRunSingleLine function", L"Error", MB_ICONERROR);
return CNC_RC_ERR; // Return error code
}

// 3) Call the original function and return the result
CNC_RC result = pOriginalCncRunSingleLine(text); // Call the function

// Check if the result is valid
if (result != CNC_RC_OK) // Replace CNC_RC_OK with the correct successful return code if necessary
{
::MessageBoxW(NULL, L"CncRunSingleLine function call failed", L"Error", MB_ICONERROR);
}

return result; // Return the result from the original function
}
}

It crashes after "successfully executing" the fusddnction. The result i want to achive is made in the program but then it crashes right after.

# Problem

* The function executes successfully, meaning that the modification takes effect inside EdingCNC.
* However, immediately after execution, the application crashes.
* The crash happens after returning from CncRunSingleLine(), even though the expected result is achieved.

# What I Have Tried

* Verified that the intercepted function is indeed called, and its result matches the expected behavior.
* Checked the return value and confirmed that CncRunSingleLine completes execution before the crash.
* Confirmed that hModule correctly retrieves \_cncapi.dll and that the function pointer is valid.
* Used MessageBoxW() before and after the function call, confirming that the crash occurs after returning from CncRunSingleLine().

https://redd.it/1iwkp55
@r_cpp
Synthetisizing lightweight forward modules

I have ported the C++ sources of our [Windows application](https://www.cadifra.com/) from header files to using C++ 20 modules.

Our codebase is heavily using forward declarations for classes wherever possible.

The code is devided into ~40 packages. Every package uses a namespace and all the files of a package are part of a "Project" in Visual Studio.

Due to the strong [name attaching rules of C++20 modules](https://en.cppreference.com/w/cpp/language/modules#:~:text=In%20general%2C%20if,the%20same%20module.), I ran into problems with forward declarations.

I think I finally may have found a pattern to synthetisize a lightweight forward module per package, which can be imported instead of importing the class definition(s).

For example, in our code, we have a package `Core`.

I now have a header file `Core/Forward.h`, which just contains forward declarations of the classes in Core:

#pragma once

namespace Core
{
class CopyRegistry;
class ElementSet;
class Env;
class ExtendSelectionParam;
class IClub;
class IDiagram;
class IDirtyMarker;
class IDirtyStateObserver;
class IDocumentChangeObserver;
class IElement;
class IElementPtr;
class IFilter;
class IGrid;
class IPastePostProcessor;
class IPosOwner;
class ISelectionObserver;
class IUndoRedoCountObserver;
class IObjectRegistry;
class IUndoerCollector;
class IUndoHandler;
class IView;
class IViewElement;
class ObjectID;
class ObjectRegistry;
class PosUndoer;
class SelectionHider;
class SelectionObserverDock;
class SelectionTracker;
class SelectionVisibilityServerImp;
class Transaction;
class TransactionImp;
class Undoer;
class UndoerParam;
class UndoerRef;
class VIPointable;
class VISelectable;
class Weight;
}

I then have created a module `Core.Forward` (in file `Core/Forward.ixx`):

export module Core.Forward;

export import <Core/Forward.h>;

Which uses a [header unit](https://en.cppreference.com/w/cpp/language/modules#:~:text=A%20header%20unit%20is%20a%20separate%20translation%20unit%20synthesized%20from%20a%20header.%20Importing%20a%20header%20unit%20will%20make%20accessible%20all%20its%20definitions%20and%20declarations.).

The resulting interface module can be imported wherever just a forward declaration of a class is enough, instead of the full definition. Which means for example doing

import Core.Forward;

instead of

import Core.IElement;

when class `Core::IElement` is only used by reference in some interface.

I believe this pattern is conformant to the C++ 20 language spec.

### Previous related posts

* [The language spec of C++ 20 modules should be amended to support forward declarations](https://www.reddit.com/r/cpp/comments/1jch40x/the_language_spec_of_c_20_modules_should_be/) in r/cpp
* [C++ modules and forward declarations in partitions](https://www.reddit.com/r/cpp_questions/comments/1jb93al/c_modules_and_forward_declarations_in_partitions/) in r/cpp_questions
* [C++ modules and forward declarations](https://www.reddit.com/r/cpp/comments/1j7ue8o/c_modules_and_forward_declarations/) in r/cpp


https://redd.it/1jd7c5r
@r_cpp
variable)
            Kokkos::parallel_reduce("MaxResidual",
                Kokkos::MDRangePolicy<Kokkos::Rank<2>>({1, 1}, {NY - 1, NX - 1}),
                [=] KOKKOS_FUNCTION (const int j, const int i, double& local_maxres) { // j: y-index, i: x-index
                    double point_residual_val = Kokkos::fabs(
                        0.25 * (P_new(i + 1, j) + P_new(i - 1, j) +
                                P_new(i, j + 1) + P_new(i, j - 1)) -
                        P_new(i, j)
                    );
                   
                    if (point_residual_val > local_maxres) {
                        local_maxres = point_residual_val;
                    }
                }, Kokkos::Max<double>(maxres)); // Kokkos::Max reducer updates host variable 'maxres'
           
            Kokkos::fence("ResidualCalculationComplete");
            if (iter % 100 == 0) {
                std::cout << "Iter: " << iter << " maxres: " << maxres << std::endl;
            }
            if (maxres < TOL) {
                break; // Exit loop if converged
            }
            std::swap(phi_current_ptr, phi_next_ptr);
        }
       
        Kokkos::fence("SolverLoopComplete");
        double end_time = timer.seconds();

        std::cout << "Time taken (seconds): " << end_time << std::endl;

    }
   
    Kokkos::finalize();

    return 0;
}

The OpenMP code: Takes between 1.2-2.5 seconds on my PC with 16 OMP threads

   


#include <iostream>
    #include <cmath>
    #include <cstdlib>
    #include <ctime>
    #include <omp.h>

    #define NX (128 * 8 + 2)
    #define NY (128 * 6 + 2)
    #define MAX_ITER 10000
    #define TOL 1.0e-6
    #define DX (1.0 / (NX - 1))
    #define DY (1.0 / (NY - 1))

    int main() {
        std::cout << "Start \n";
        std::cout << "Nx="<<NX<<", NY="<<NY<<"\n";
        double phi_old[NX][NY];
        double phi_new[NX][NY];
        double residual[NX][NY];
        double maxres, diff;
        int iter, i, j;

        int num_threads = omp_get_max_threads();
        std::cout << "Using " << num_threads << " OpenMP threads\n";

        std::srand(static_cast<unsigned int>(std::time(nullptr)));
        for (j = 0; j < NY; ++j)
            for (i = 0; i < NX; ++i)
                phi_old[i][j] = static_cast<double>(std::rand()) / RAND_MAX;

        for (j = 0; j < NY; ++j)
            for (i = 0; i < NX; ++i)
                phi_new[i][j] = phi_old[i][j];

        for (i = 0; i < NX; ++i) {
            phi_old[i][0] = phi_old[i][NY - 1] = 0.0;
            phi_new[i][0] = phi_new[i][NY - 1] = 0.0;
        }
        for (j = 0; j < NY; ++j) {
            phi_old[0][j] = phi_old[NX - 1][j] = 0.0;
            phi_new[0][j] = phi_new[NX - 1][j] = 0.0;
        }

        std::cout << "Start solving...\n";
        double start_time = omp_get_wtime();

        for (iter = 1; iter <= MAX_ITER; ++iter) {
            maxres = 0.0;

            #pragma omp parallel default(shared) private(i, j)
            {
                // phi_old=phi_new. Would be more efficient to switch pointers.
                #pragma omp for schedule(static)
                for (i = 0; i < NX; ++i)
                    for (j = 0; j < NY; ++j)
                    phi_old[i][j] = phi_new[i][j];

                // Jacobi
                #pragma omp for schedule(static)
                for (i = 1; i < NX-1; ++i)
                    for (j = 1; j < NY-1; ++j)  
                        phi_new[i][j] = 0.25 * (
                            phi_old[i + 1][j] + phi_old[i - 1][j] +
                            phi_old[i][j + 1] + phi_old[i][j - 1]);

                // calculate Linf residue
               
#pragma omp for reduction(max:maxres) schedule(static)
                for (i = 1; i < NX-1; ++i){
                    for (j = 1; j < NY-1; ++j){        
                    residual[i][j] = 0.25 * (
                            phi_new[i + 1][j] + phi_new[i - 1][j] +
                            phi_new[i][j + 1] + phi_new[i][j - 1]) - phi_new[i][j];
                        maxres = std::max(maxres, std::abs(residual[i][j]));
                    }
                }
            }

            if (iter % 100 == 0)
                std::cout << "Iter: " << iter << " maxres: " << maxres << "\n";

            if (maxres < TOL)
                break;
        }

        double end_time = omp_get_wtime();
        std::cout << "Time taken (seconds): " << end_time - start_time << "\n";

        return 0;
    }

https://redd.it/1kx89hx
@r_cpp
Parallel bubble sort with OpenMP — any chance it outperforms sequential version?

Hey everyone,
I’ve been experimenting with OpenMP and tried parallelizing bubble sort — I know it's a bad algorithm overall, but it's a good toy example to test parallelism.

When I try it on integers, the parallel version ends up slower than the single-threaded one, which makes sense: bubble sort is inherently sequential due to the element-by-element comparisons and swaps. The overhead of synchronizing threads and managing shared memory probably kills performance.

But here's where it gets interesting:
When I switch to using floating-point numbers instead of integers, I notice that the performance gap shrinks. In some cases, it's even slightly faster than the sequential version. I have a theory — modern CPUs are optimized for float operations in SIMD/FPU pipelines, so the cost per operation is lower than with integer compare-and-swap logic.

My questions:

Is there any realistic scenario where bubble sort (or odd-even transposition sort) can actually run faster in parallel than sequentially?
Is my observation about float vs int performance plausible, or am I misinterpreting something?
Are there hardware-specific quirks (e.g., FPU vs ALU pipelines, SIMD instructions, cache behavior) that could explain this?

Again, I’m not trying to use bubble sort in production — just using it to understand low-level parallel behavior and OpenMP tradeoffs. Any thoughts or benchmarks would be appreciated!

Update: here's the code I currently use for testing. It’s an odd-even transposition variant, parallelized with OpenMP.

void parallelBubbleSort(vector<int> &arr)
{
    size_t n = arr.size();
    bool swapped = true;

    for (size_t k = 0; k < n - 1 && swapped; ++k)
    {
        swapped = false;

#pragma omp parallel for shared(arr, swapped)
        for (size_t i = 0; i < n - 1; i += 2)
        {
            if (arr[i] > arr[i + 1])
            {
                swap(arr[i], arr[i + 1]);
#pragma omp atomic write
                swapped = true;
            }
        }

#pragma omp parallel for shared(arr, swapped)
        for (size_t i = 1; i < n - 1; i += 2)
        {
            if (arr[i] > arr[i + 1])
            {
                swap(arr[i], arr[i + 1]);
#pragma omp atomic write
                swapped = true;
            }
        }
    }
}

I ran this on my university’s cluster with:

Intel Xeon E5-2670 v3 (2 sockets × 12 cores × 2 threads = 48 threads)
L3 cache: 30 MB
125 GiB RAM
AlmaLinux 8.7

The parallel version (with static scheduling and large arrays) still tends to be slower than the sequential one.
I'm wondering how much of this is due to:

cache contention / false sharing
small workload per thread
overhead of synchronization

https://redd.it/1lbx8lp
@r_cpp
(advanced) linker madness on visual studio 2022.

Hi,

Im really stuck here and cannot for the life of me figure out what's going on. Im thinking an issue with visual studio linker, but not sure.

I have generated code (its protoc generated) and there are LOT and LOTS of generated classes. So many that we hit the COFF/PE 64k limit on exported symbols at link time. This is a critical issue for us.

Right now the nature of our app , doesnt currently allow us to split/separate out the .protos. Its just the way it is (for the moment).

My solution to reducing the exported symbol count;

Instead of having the protoc generated classes export every thing like this;

class PROTOBUF_EXPORTS Object : public google::protobuf::Message
{

// all the methods / constructor/destructor etc.
// lots and lots of unused methods etc exported.
}

I have a python script that correctly alters the generated code to ONLY export the symbols we need. In addition it adds one (the key) virtual void foo(); function.


so the script modified code looks like;

class Object : public google::protobuf::Message
{
PROTOBUF_EXPORTS Object();
PROTOBUF_EXPORTS virtual \~Object();
PROTOBUF_EXPORTS virtual void Swap(Object* other);
PROTOBUF_EXPORTS virtual void foo();
// a few other key methods that our clients will call.....
};

the added "virtual void foo()" is added to the .cc file correctly.

i.e. the intention is to export (via __declspec(dllexport) ONLY the functions our client code needs, thereby significantly reducing the number of symbols exported in the .dll)

Despite the fact that the "virtual void foo()" function is in there (key function for vtable emission, as I understand it) , I was getting unresolved externals for all these Objects;

"unresolved external Object::`vftable"
"unresolved external Bar::`vftable"
"unresolved external Foo::`vftable"
"unresolved external Blah::`vftable"

(lots of others too, for all our Message objects. The only way I could get the library in question to link correctly (tried #pragma link /export and #pragma link /include but to no avail) , was to use a .def file and for the vftable to be exported. this works a treat for the dll being built in question.


With this approach


dumpbin /exports on the dll works and I can see all the mangled Object::`vftable symbols. Similarly in the corresponding .lib file, "dumpbin /symbols" on the .lib file shows everything exactly as I want it (all the vftable symbols are in there.)


BUT ... and this is the big blocker I CANNOT resolve;

When I link OUR dll (the client... that imports those same symbols via __declspec(dllimport)) against the dll above, the vftable unresolved externals reappear. They shouldnt, they are defined in the dll and .lib and dumpbin /exports and dumpbin /symbols on the .dll and .lib respectively proves it. The names are IDENTICAL (trust me I've verified).


Can anybody help me?

https://redd.it/1mepvlg
@r_cpp