I have an existing Win32 C++ DLL, which needs to be accessed by a VB6 client. One of the exported functions is defined as follows:
__declspec(dllexport) long __stdcall Foo(long nId, LPCWSTR pszwPath = nullptr);
Unfortunately, VB6 always converts strings to ANSI when calling DLL functions via a Declare statement. In order to bypass this limitation, I have embedded a type library, which features the following signature for the function:
[uuid(...)]
library FooLib
{
[
helpstring("FooLib"),
dllname("Foo.dll")
]
module FooMdl
{
[entry("Foo")]
long __stdcall Foo([in] long nId, [in,unique,string,defaultvalue(0)] LPCWSTR pszwPath);
}
};
This won't compile however, as MIDL generates the following error:
error MIDL2020 : error generating type library : AddFuncDesc failed : Foo
My next attempt involved utilizing the optional attribute:
long __stdcall Foo([in] long nId, [in,unique,string,optional] LPCWSTR pszwPath);
While this type library can be compiled successfully, the VB6 client crashes with an access violation as soon as the optional string value is omitted.
I am aware that I could change the LPCWSTR argument to a BSTR type, thereby remedying the problem. However, this would also require me to change the signature and implementation of the existing DLL.
Is it therefore possible to define a char / wchar_t pointer argument as optional or with a NULL default value? Or am I simply out of luck here?
Related
I have read a lot about how to change the WinMain entry point procedure, some say you can change the entry point from the linker and some others say you can put the WinMain into DLL (dllMain) and so on.
Honestly, I am confused. I believe that there are one or more ways to change the entry point procedure to a custom procedure because there are some examples like MFC don't have a direct WinMain function and the Qt framework also has a custom entry point procedure it's similar to the console application main function int main(int argc, char *argv[]), so, there are ways as I expected.
I want a whatever way to replace/change the entry point procedure for GUI application on Windows from the traditional procedural WinMain to int main(int argc, char *argv[]) like Qt or even any other custom function but it must be compatible with (MS, GCC, Clang) compilers.
///////////Windows main/////////////
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdParam, int cmdShow){
}
///////////Console main and Qt framework////////////
int main(int argc, char *argv[]) {
}
//////////MFC////////////
class CMyFrame : public CFrameWnd {
public:
CMyFrame() {}
};
class CExample : public CWinApp {
BOOL InitInstance() {}
};
CExample theApp;
How do I do that?
entry point of exe can by any function with signature
ULONG CALLBACK ep(void* )
possible and use ULONG CALLBACK ep() - despite on x86 will be wrong stack pointer (esp) after return, this not lead to error, because windows just call ExitThread after entry return, if it return control at all - usually it call ExitProcess instead return.
the name of this entry point of course not play any role at all - it can be any valid c/c++ name. entry point found/called not by name but by AddressOfEntryPoint offset from IMAGE_OPTIONAL_HEADER
but when we build PE - we need tell linker name of this function, for it can set AddressOfEntryPoint, but this info (name of function) used only during build process (not used in runtime)
different linkers of course have different options for do this, link.exe have option /ENTRY. this option is optional and by default, the starting address is a function name from the C run-time library.
if /ENTRY:MyEntry explicitly stated - it used as is - MyEntry will be used as entry point. if no /ENTRY option set - used default:
if /SUBSYSTEM:CONSOLE set - used mainCRTStartup or if it not found wmainCRTStartup
if /SUBSYSTEM:WINDOWS set - used WinMainCRTStartup or if it not found wWinMainCRTStartup
but in most case c/c++ developers use CRT libraries. regardless of whether static or dynamic linking used with CRT - some lib code always statically linked with your exe and this code containing function which you used as entry point. for ms windows crt - this is mainCRTStartup or wmainCRTStartup (for console apps), WinMainCRTStartup or wWinMainCRTStartup for gui apps.
in all this 4 functions - called hardcoded function by name
mainCRTStartup call main
wmainCRTStartup call wmain
WinMainCRTStartup call WinMain
wWinMainCRTStartup call wWinMain
of course called function must be implemented somewhere in your code or in another lib code. for example if you use MFC - it implement wWinMain by self and called your code in another way ( via calling virtual functions on object which you override - InitApplication and InitInstance)
if back to question how change name of your custom entry point - but for what ? you really not need change name. you need only understand how your entry point is called. if you understand this - you can do almost all.
assume we want use main as "entry point". i take this in quotes because we really want have real entry point in CRT code and we want that CRT code call exactly main function.
possible ? simply !
set /ENTRY: mainCRTStartup linker option. so mainCRTStartup will be real entry point and it call main.
another question, i personally think that this is senseless trick, which nothing change and nothing give
also possible simply call main from WinMain
typedef struct
{
int newmode;
} _startupinfo;
/*
* new mode flag -- when set, makes malloc() behave like new()
*/
EXTERN_C _CRTIMP int __cdecl _query_new_mode( );
EXTERN_C _CRTIMP int __cdecl _set_new_mode( _In_ int _NewMode);
EXTERN_C
_CRTIMP int __cdecl __getmainargs(__out int * _Argc,
__deref_out_ecount(*_Argc) char *** _Argv,
__deref_out_opt char *** _Env,
__in int _DoWildCard,
__in _startupinfo * _StartInfo);
int __cdecl main(__in int _Argc, __in_ecount_z(_Argc) char ** _Argv, ...);
int CALLBACK WinMain( _In_ HINSTANCE , _In_opt_ HINSTANCE , _In_ LPSTR , _In_ int )
{
int _Argc, r;
char ** _Argv;
char ** _Env;
_startupinfo _StartInfo { _query_new_mode( ) };
if (!(r = __getmainargs(&_Argc, &_Argv, &_Env, 0, &_StartInfo)))
{
r = main(_Argc, _Argv, _Env);
if (_Argv) free(_Argv);
}
return r;
}
it must be compatible with (MS, GCC, Clang) compilers
How you do that depends on your compiler. Most of them will have some flags to choose which "subsystem" (the Windows term) you are targeting and even customize the entry point manually.
Put another way, there is no standard way of doing so because this is outside the scope of the C++ standard.
Having said that, some compilers provide the means to emulate the flags of other compilers. For instance, Clang can imitate Microsoft's.
I want to make this method remain in native because on compiling the __fastcall is being converted into __stdcall How do I make sure that the clr compiler remains this method native?
static void* __fastcall FindCallOffset(VMTBasePointer* _this, unsigned char* addr);
I'm trying to create a DLL using Visual C++ that is called from a Delphi 5 program. The Delphi program passes in a record, which is then edited in the DLL, and the Delphi program uses the results.
For example, the Delphi code is similar to the following:
Type dll_btvar = record
age : smallint;
name : array[0..11] of char;
value : Double;
end;
// Import the function from the dll
function foo(CVars : dll_btvar):integer; external 'example.dll';
// Call the dll
function callFoo(var passedVar:dll_btvar):integer;
begin
result := foo(passedVar);
// Use passedVar.value
end;
A sample of the C++ code:
In example.h:
#pragma once
#include "dllVar.h"
extern "C" {
__declspec(dllexport) int foo(DLL_Var var);
}
In example.cpp:
#include "example.h"
int foo(DLL_Var var){
var.value = var.age + var.name[0];
return 0;
}
In dllVar.h:
#pragma once
#pragma pack(8)
extern "C" {
struct DLL_Var {
short age;
char name[12];
double value;
}
}
I use #pragma pack(8) as that value gave correct alignment so that the passed record is read correctly in the DLL.
In the sample code, when passing an age and name, I expect value to be set by the DLL, which can then be recovered in the Delphi program. Result would be some sort of error code.
Using identical code in C++ Builder 5 did work, however it is of course outdated and I haven't moved all the code in my DLL over (nor do I want to), only the minimum you see here.
I tested a couple of ways to have Delphi pass an address/pointer to the dll, however they didn't change anything.
Right now, the return value is sent correctly, but the fields of the record (i.e. value) remain unchanged.
What changes do I need to make to either the Delphi or C++ to capture changes in the passed record? I am happy to work extensively with the C++ but I'd prefer to keep the Delphi changes to a minimum since this is old software that I don't want to break.
function foo(CVars : dll_btvar):integer; external 'example.dll';
The problem starts here, in the Delphi code. The record is passed by value. That is, the caller's record variable is copied to a new variable which is then passed to the function. This means that modifications by the callee to this copy of the record are not seen by the caller. You must therefore pass the parameter as a var parameter:
function foo(var CVars : dll_btvar):integer; external 'example.dll';
The next problem is the calling convention. You must use the same calling convention for both sides. Your code uses the default register convention on the Delphi side, which is not supported by non-Borland/Embarcadero tools. Use stdcall or cdecl instead. Let's opt for cdecl, the default for most C++ tools:
function foo(var CVars : dll_btvar):integer; cdecl; external 'example.dll';
To make the C++ code match, pass the argument by reference:
__declspec(dllexport) int __cdecl foo(DLL_Var &var);
Or explicitly use a pointer:
__declspec(dllexport) int __cdecl foo(DLL_Var *var);
In the latter option, the implementation needs to be changed because of the use of a pointer:
int foo(DLL_Var *var){
var->value = var->age + var->name[0];
return 0;
}
Using identical code in C++ Builder 5 did work.
No it did not, because the Delphi code in your question cannot modify the caller's record.
I a porting a large amount of code from Visual Studio to mingw unicode enabled project and I ran into this issue.It seems that the parameters I am passing from the derived class to the base class do not match. Atleast thats my assumption. I am suspecting that the issue is with the type LPCSTR Looking into this type in mingw I get the follwoing typedef
typedef CONST CHAR *LPCSTR,*PCSTR;
Now this is the base class
#ifdef UNICODE
CBasePin(
__in_opt LPCSTR pObjectName,
__in CBaseFilter *pFilter,
__in CCritSec *pLock,
__inout HRESULT *phr,
__in_opt LPCWSTR pName,
PIN_DIRECTION dir);
#endif
This is the class that inherits from the base class and passes the parameters to the base class.
CAsyncOutputPin::CAsyncOutputPin(
HRESULT * phr,
CAsyncReader *pReader,
CAsyncIo *pIo,
CCritSec * pLock)
: CBasePin(
TEXT("Text Pin"),
pReader,
pLock,
phr,
TEXT("Output"),
PINDIR_OUTPUT
),
m_pReader(pReader),
m_pIo(pIo)
,m_bQueriedForAsyncReader(false) ////
{
}
This is the linker error I am getting at the constructor initialization list
undefined reference to `CBasePin::CBasePin(wchar_t const*, CBaseFilter*, CCritSec*, long*, wchar_t const*, _PinDirection)'|
any suggestions on why I am getting this linker error. I am a bit puzzled as to why this is a linker error. I was guessing if it was a type mismatch or something it would show up as a compiler error. Any suggestions on how I can resolve this issue for Mingw 64bit gcc ?
your CBasePin has a parameter LPCSTR pObjectName (which is of type const char*) and a parameter LPCWSTR pName (whichis of type const wchar_t*) and in your call to it you're using TEXT-macro on a char-literal ,so both those literals getting the L-prefix making them const wchar_t* when UNICODE is defined ,so there is a type mismatch on the pObjectName.
If UNICODE is not defined then you would get the same error but on pName.
Remove the first TEXT and it should be Ok (for UNICODE that is).
Why are you using the TEXT macro? You have one char * parameter and one wchar_t * parameter, and it can't do the right thing for both.
Just write what you mean. Something like
"Text Pin", ..., L"Output", ...
I have a C++ DLL that I wrote that has a single exposed function, that takes a function pointer (callback function) as a parameter.
#define DllExport extern "C" __declspec( dllexport )
DllExport bool RegisterCallbackGetProperty( bool (*GetProperty)( UINT object_type, UINT object_instnace, UINT property_identifer, UINT device_identifier, float * value ) ) {
// Do something.
}
I want to be able to call this exposed C++ DLL function from within a Delphi application and register the callback function to be used at a future date. But I am unsure of how to make a function pointer in Delphi that will work with the exposed C++ DLL function.
I have the Delphi application calling a simple exposed c++ DLL functions from the help I got in this question.
I am building the C++ DLL and I can change its parameters if needed.
My questions are:
How to I create a function pointer in Delphi
How to I correctly call the exposed C++ DLL function from within a Delphi application so that the C++ DLL function can use the function pointer.
Declare a function pointer in Delphi by declaring a function type. For example, the function type for your callback could be defined like this:
type
TGetProperty = function(object_type, object_instnace, property_identifier, device_identifier: UInt; value: PSingle): Boolean; cdecl;
Note the calling convention is cdecl because your C++ code specified no calling convention, and cdecl is the usual default calling convention for C++ compilers.
Then you can use that type to define the DLL function:
function RegisterCallbackGetProperty(GetProperty: TGetProperty): Boolean; cdecl; external 'dllname';
Replace 'dllname' with the name of your DLL.
To call the DLL function, you should first have a Delphi function with a signature that matches the callback type. For example:
function Callback(object_type, object_instnace, property_identifier, device_identifier: UInt; value: PSingle): Boolean cdecl;
begin
Result := False;
end;
Then you can call the DLL function and pass the callback just as you would any other variable:
RegisterCallbackGetProperty(Callback);