For anyone still interested, I think there's a macro solution. Usually when creating an API I define a "BUILDDLL" macro in the API project's settings that is used to define DLL export/import like such:
#ifdef BUILDDLL
#define DLL __declspec(dllexport)
#else
#define DLL __declspec(dllimport)
#endif
So I also use it to define an INTERNAL macro:
#ifdef BUILDDLL
#define INTERNAL public
#else
#define INTERNAL private
#endif
BUILDDLL is defined in the DLL project's settings (in VS, properties->C\C++->preprocessor->preprocessor definitions), but not the consumer project's settings. So for the consumer, the macro is replaced with "private", but it's "public" within the DLL project. You'd use it just like any other access modifier:
class DLL Foo
{
public:
int public_thing;
INTERNAL:
int internal_thing;
};
It's not a perfect solution (you can't use it with member functions whose definitions appear in header files, for example, and there's nothing stopping the end user from overriding it), but it seems to work without error. I think it fits as one of the "good" uses for macros, and it's pretty clear for an outsider looking at the code (who is familiar with the C# keyword) what it means.