Results 1 to 38 of 38

Thread: [RESOLVED] Trying to Figure Out How to Communicate with a C DLL

  1. #1

    Thread Starter
    New Member
    Join Date
    Feb 2019
    Posts
    14

    Resolved [RESOLVED] Trying to Figure Out How to Communicate with a C DLL

    I've been writing code in C and C++ for many years (since the 80s), but I have little experience with VB. For the past few years I've been maintaining a suite of programs most written in C back in the late 80s with a VB6 program written in the early 90s. The programs were updated for Windows 95 and again around 2006, but are essentially the same at their core.

    The VB program has been communicating with the C programs via DDE and the C programs communicate with one another with Windows messages and a DLL with a block of shared memory.

    I've been working on a new way to communicate between the VB6 program and the C programs but VB6 is a lot harder to debug than C. I created a logging function in the DLL to see if things are getting called and if the arguments are getting passed correctly (I can't figure out how to step into a DLL like you can when debugging C/C++).

    All apps that use the DLL need to call an init call when the program is initializing. From the log I can see the initialization call is happening. However the call that is supposed to transfer data via the DLL is getting weird data.

    The function has three arguments, and integer which is supposed to be between 1 and 10, a string pointer, and another integer. The first integer is 3, and the last is 0. But the log shows the first integer as 12517379. The last argument is 0. The string also comes through as three garbage characters: T{¿

    There is obviously something off with string, but the integer came through weird too. I converted it to hex and it's: BF0003(H) which might be a valid memory address, but doesn't look typical. Addresses are usually at least divisible by 2, and usually some higher power of 2 (8, 16, or 32).

    At the moment I'm just trying to get communication from the VB program to the C programs, but the VB program needs to be able to retrieve blocks of text data, sometimes as much as 16K, from the C programs as well as be able to receive messages from the C programs. Is there a way for VB6 programs to receive messages from a DLL or a C program? If so, how?

    Obviously I'm doing something wrong, but I'm baffled. I have a feeling I'm going to have a lot of questions before I'm done.

    Thanks in advance...

  2. #2
    PowerPoster Zvoni's Avatar
    Join Date
    Sep 2012
    Location
    To the moon and then left
    Posts
    4,418

    Re: Trying to Figure Out How to Communicate with a C DLL

    Can you show us how you've declared that c-function in VB6?
    The common pitfall are ByVal/ByRef-Parameters
    Last edited by Zvoni; Tomorrow at 31:69 PM.
    ----------------------------------------------------------------------------------------

    One System to rule them all, One Code to find them,
    One IDE to bring them all, and to the Framework bind them,
    in the Land of Redmond, where the Windows lie
    ---------------------------------------------------------------------------------
    People call me crazy because i'm jumping out of perfectly fine airplanes.
    ---------------------------------------------------------------------------------
    Code is like a joke: If you have to explain it, it's bad

  3. #3
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,673

    Re: Trying to Figure Out How to Communicate with a C DLL

    You can just create a shared COM object and communicate between 2 programs without any additional DLLs - COM will make all work to ensure the synchronous calls from your VB app to the C++/C app and vice versa. The advantages are you can transparently communicate between 32/64 bit apps, you can place second app to other machine, you can use the object from any application and scripts.
    From VB and C/C++ it looks like just call a method.

  4. #4
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: Trying to Figure Out How to Communicate with a C DLL

    You might want to consider DispCallFunc API which can call a variety of DLL conventions, including C_DECL. You can find many examples of that API on this site.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  5. #5
    PowerPoster
    Join Date
    Jun 2015
    Posts
    2,224

    Re: Trying to Figure Out How to Communicate with a C DLL

    You might want to start by posting some C code and the VB code that is calling it. As already pointed out - the most obvious issues are calling convention, and the VB declaration.

  6. #6
    Frenzied Member
    Join Date
    Jun 2015
    Posts
    1,057

    Re: Trying to Figure Out How to Communicate with a C DLL

    [...]
    Last edited by dz32; Apr 26th, 2019 at 11:07 AM.

  7. #7

    Thread Starter
    New Member
    Join Date
    Feb 2019
    Posts
    14

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by Zvoni View Post
    Can you show us how you've declared that c-function in VB6?
    The common pitfall are ByVal/ByRef-Parameters
    On the VB side I did have ByRef for the string and when I changed it to ByVal the string came through. The first integer is still wrong though. The decalaration in VB looks like:
    Private Declare Function ttw_SendToApp Lib "TTWLib32" (ByVal xfertype As Integer, ByVal CommandStr As String, ByVal bExpectReturn As Integer) As Integer

    On the DLL side it is declared:
    int WinApi ttw_SendToApp(int type, LPSTR cmd, int bExpectReturn);

    I'm thinking about the return data. It can be a large block of data sometimes. On the C side it's stored in a handle that gets transferred in some way by the DDE system currently. If I used a ByRef in the return would I return the handle, or a pointer to the data in the locked handle and how would I get at the data on the VB side?

  8. #8

    Thread Starter
    New Member
    Join Date
    Feb 2019
    Posts
    14

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by dz32 View Post
    if the vb code is in a dll and the c code is in a dll, build an vb exe test harness for the functions so you can test the transfer mechanism.
    you can debug the c dll code in your standard c IDE by setting it to launch the vb exe on start of debug session, compile your vb exe with debug symbols you will actyallu be able to step into the vb code from teh C dll to watch it. you will also be able to set breakpoints in your C DLL normally it works perfectly, I might have a video on this somewhere I will look.

    There are several good ways to communicate across the two languages. The one i use the most if the WM_COPYDATA message which gives you like 1024 bytes per message possible. You can chunk messages but if you need a bigger buffer it might be easier to use another method. example: http://sandsprite.com/openSource.php?id=70

    If you are building a list or have to keep adding data you can also use a callback: http://sandsprite.com/openSource.php?id=53

    shared memory is not a bad way to go, even if you use a virtual alloc to hold it temporarily and just return a memory pointer and size from C then call virtualFree from vb.

    just using sendmessage itself can be enough if you can living with its user parameters and one long return value.

    to start though I would break out the declare statements and C stubs into a test project so you take all the complexity out and can watch the data transfers explicitly in the debugger. Its probably just a mismatch from expecting a pointer type and a byref or byval mismatch between the vb declare and the C code.

    some more misc info: http://sandsprite.com/CodeStuff/Why_...l_Use_VB6/?p=4
    The VB program is an EXE. I didn't know you could make VB DLLs. I found where I could compile with symbols in VB and did run it in the debugger. I figured that one out, VB defines an integer as a 16 bit variable and 32 bit C defines it as a 32 bit variable. When I changed the call to expect an int16, it worked.

    I tried going to the sandsprite.com links you sent, but my virus checker thinks that's a trojan site. It's probably a false positive, I was able to get there with Tor and downloaded the zips. They scanned fine. It looks like there may be some useful information there. I'll digest it this evening. Thanks a bunch.

  9. #9

    Thread Starter
    New Member
    Join Date
    Feb 2019
    Posts
    14

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by The trick View Post
    You can just create a shared COM object and communicate between 2 programs without any additional DLLs - COM will make all work to ensure the synchronous calls from your VB app to the C++/C app and vice versa. The advantages are you can transparently communicate between 32/64 bit apps, you can place second app to other machine, you can use the object from any application and scripts.
    From VB and C/C++ it looks like just call a method.
    I'm not creating any new DLLs, I'm just expanding on an existing DLL that is integral to the suite of programs. I think it pretty much does what a COM object would do now.

    Quote Originally Posted by LaVolpe View Post
    You might want to consider DispCallFunc API which can call a variety of DLL conventions, including C_DECL. You can find many examples of that API on this site.
    I'll check that out.

    Thanks

  10. #10

  11. #11
    PowerPoster
    Join Date
    Jun 2015
    Posts
    2,224

    Re: Trying to Figure Out How to Communicate with a C DLL

    Trick already caught the wrong types being used. But you should also be aware of VB's automatic
    Unicode<-->ANSI conversions when using string parameters.

  12. #12
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by DEXWERX View Post
    But you should also be aware of VB's automatic
    Unicode<-->ANSI conversions when using string parameters.
    Should be ok in that respect. His DLL declared the parameter as LPSTR vs LPWSTR

    If _cdecl is in play here; isn't there a risk of the stack not being cleaned up properly, even after the params issue is resolved?
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  13. #13
    PowerPoster
    Join Date
    Jun 2015
    Posts
    2,224

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by LaVolpe View Post
    Should be ok in that respect. His DLL declared the parameter as LPSTR vs LPWSTR

    If _cdecl is in play here; isn't there a risk of the stack not being cleaned up properly, even after the params issue is resolved?
    yes but he's showing a WINAPI/STDCALL calling convention on the C function... although he's got it spelled with an odd case, maybe typo?

  14. #14

    Thread Starter
    New Member
    Join Date
    Feb 2019
    Posts
    14

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by DEXWERX View Post
    yes but he's showing a WINAPI/STDCALL calling convention on the C function... although he's got it spelled with an odd case, maybe typo?
    Yes, it's a typo. In my code I actually use a #define instead of WINAPI. I copied, pasted, and made the change by hand. Some of the modules from the DLL are directly linked into some stand alone utilities used with the system, and conditionally at compile time the WINAPI is left out in that instance.

    Interesting about the Unicode/ANSI conversion. The DLL and the old programs are ASCII, in fact they predate the standard C library, but the new C++ program that will replace them is Unicode. It's something to keep in mind as I'm working with these functions.

    Edit: I do have an odd behavior when I post a reply. I click the Post button once, but then get an error saying I can't post more than 1 post per 30 seconds. When I look at the thread, my post went through. It seems to be trying to post twice in quick succession. I'm using Firefox on Windows 7 BTW.

  15. #15
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by DEXWERX View Post
    yes but he's showing a WINAPI/STDCALL calling convention on the C function...
    Thanx, I figured that out later. Not fluent with that programming language, so did some quick research and saw that WINAPI is a macro for stdCall
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  16. #16
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by wdolson View Post
    I do have an odd behavior when I post a reply. I click the Post button once, but then get an error saying I can't post more than 1 post per 30 seconds. When I look at the thread, my post went through. It seems to be trying to post twice in quick succession. I'm using Firefox on Windows 7 BTW.
    Happening with all of us -- been happening for several months now. Many people that don't visit here often don't realize that it's a "bug" of sorts, wait a few seconds, and then click to try again which results in double posts.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  17. #17
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by LaVolpe View Post
    Happening with all of us -- been happening for several months now. Many people that don't visit here often don't realize that it's a "bug" of sorts, wait a few seconds, and then click to try again which results in double posts.
    If you click on Reply, and then click on "Go Advance" below the reply window, then you can reply and avoid that bug, but it is usually quicker to ignore the error.

  18. #18

    Thread Starter
    New Member
    Join Date
    Feb 2019
    Posts
    14

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by passel View Post
    If you click on Reply, and then click on "Go Advance" below the reply window, then you can reply and avoid that bug, but it is usually quicker to ignore the error.
    Good to know it isn't just me.

    I do have another question. For transferring data back, it looks like I need to use SAFEARRAY such as with this example:
    https://www.codeproject.com/Articles...m-VC-DLL-to-VB

    All the examples I can find have the array defined on the VB side. Is that required? The data being returned from the DLL is variable in size. For example one thing returned is a list of files of a certain type in a directory. That might be 0 or it could be hundreds. I suppose I could handshake back and forth to figure out how many items are in the array coming back, allocate the space on the VB side, then transfer it, but that seems awkward. The data is transferred with DDE now and the received data is just put into a control on the hidden DDE form on the VB side, then used by the application.

    I think I can just reuse the controls on the DDE form without actually calling DDE on the VB side. The way things are now, I don't see any array sizes defined, it just gets whatever it gets and plunks it in the control.

  19. #19
    Frenzied Member
    Join Date
    Jun 2015
    Posts
    1,057

    Re: Trying to Figure Out How to Communicate with a C DLL

    [...]
    Last edited by dz32; Apr 26th, 2019 at 11:06 AM.

  20. #20
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by wdolson View Post
    I suppose I could handshake back and forth to figure out how many items are in the array coming back, allocate the space on the VB side, then transfer it, but that seems awkward.
    Many windows functions do that: Send a null data-length parameter to return size of data, then size the array, and call again with both the array pointer and the non-null data length parameter.

    Another option could be to use the same structure that WM_COPYDATA uses. That structure contains the size of the data and a pointer to the data. However, the VB side would need to copy that data to a local array via pointer/RtlMoveMemory or other methods. Your function would return a pointer to the structure.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  21. #21

  22. #22
    Fanatic Member
    Join Date
    Feb 2019
    Posts
    706

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by wdolson View Post
    Good to know it isn't just me.

    I do have another question. For transferring data back, it looks like I need to use SAFEARRAY such as with this example:
    https://www.codeproject.com/Articles...m-VC-DLL-to-VB

    All the examples I can find have the array defined on the VB side. Is that required? The data being returned from the DLL is variable in size. For example one thing returned is a list of files of a certain type in a directory. That might be 0 or it could be hundreds. I suppose I could handshake back and forth to figure out how many items are in the array coming back, allocate the space on the VB side, then transfer it, but that seems awkward. The data is transferred with DDE now and the received data is just put into a control on the hidden DDE form on the VB side, then used by the application.

    I think I can just reuse the controls on the DDE form without actually calling DDE on the VB side. The way things are now, I don't see any array sizes defined, it just gets whatever it gets and plunks it in the control.
    I think you could call SafeArrayRedim() from the C DLL to expand the SAFEARRAY, fill it with info, then return, so it's one call that way.

    Another one call option is to use BSTR, with NULL's in the middle to separate the list of files. Call SysReAllocStringLen() from the C DLL to resize the VB string. In the VB side, you have to declare the parameter "As Long", and pass "VarPtr(StrVar)". This passes BSTR to the DLL. Using "StrPtr(StrVar)" doesn't pass BSTR, but a pointer to Unicode string(And in C-side, you have to declare it as "WCHAR *StrVar" in this case). The latter case is how we call the W version of Win32 API functions.

    Declaring the parameter in VB "As String" always triggers ANSI<->Unicode conversion back and forth, so make sure to use "As Long".

  23. #23

    Thread Starter
    New Member
    Join Date
    Feb 2019
    Posts
    14

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by qvb6 View Post
    I think you could call SafeArrayRedim() from the C DLL to expand the SAFEARRAY, fill it with info, then return, so it's one call that way.

    Another one call option is to use BSTR, with NULL's in the middle to separate the list of files. Call SysReAllocStringLen() from the C DLL to resize the VB string. In the VB side, you have to declare the parameter "As Long", and pass "VarPtr(StrVar)". This passes BSTR to the DLL. Using "StrPtr(StrVar)" doesn't pass BSTR, but a pointer to Unicode string(And in C-side, you have to declare it as "WCHAR *StrVar" in this case). The latter case is how we call the W version of Win32 API functions.

    Declaring the parameter in VB "As String" always triggers ANSI<->Unicode conversion back and forth, so make sure to use "As Long".
    Sorry for the delay getting back. I was called away on some bug fixing in another area of the project.

    The way things work now is via DDE, which can only be with strings with VB6. There are a handful for formats though that appear to have a format code embedded in the return string. Everything is abstracted through the DDE interface in VB, so I can't tell for sure. On the C side there are some formatting routines that format the data based on these codes. One format is an array of strings null terminated. The DDE form has several different controls and different calls give a different control. That may be where the system figures out which return format it expects, but I'm not sure. On the VB side there are around 400 different DDE calls, so there is a lot of bits to inspect and sort through.

  24. #24

    Thread Starter
    New Member
    Join Date
    Feb 2019
    Posts
    14

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by qvb6 View Post
    I think you could call SafeArrayRedim() from the C DLL to expand the SAFEARRAY, fill it with info, then return, so it's one call that way.

    Another one call option is to use BSTR, with NULL's in the middle to separate the list of files. Call SysReAllocStringLen() from the C DLL to resize the VB string. In the VB side, you have to declare the parameter "As Long", and pass "VarPtr(StrVar)". This passes BSTR to the DLL. Using "StrPtr(StrVar)" doesn't pass BSTR, but a pointer to Unicode string(And in C-side, you have to declare it as "WCHAR *StrVar" in this case). The latter case is how we call the W version of Win32 API functions.

    Declaring the parameter in VB "As String" always triggers ANSI<->Unicode conversion back and forth, so make sure to use "As Long".
    I've been working on another part of this, but I'm getting back to the interface. I'm a little confused here, could you clarify?

    As I understand it, on the VB side I do:

    Code:
    Private Declare Function ttw_VBGetAppResult Lib "TTWLib32" (ByVal StrVar As Long, ByVal getError As Short) As Integer
    In the calling function in VB
    Code:
          Dim StrVar As Long
          Dim ret As Integer
    
          ret = ttw_VBGetAppResult(VarPtr(StrVar), True)
    And then in the DLL, I'm not sure how to handle this. Should the function be declared to use a WCHAR *StrVar, or a BSTR *StrVar.

    SysReAllocString takes a BSTR * and an OLECHAR *. OLECHAR is just typedef WCHAR. The old programs are ASCII, but strings can be converted with the USES_CONVERSION macros. However, how can I allocated enough space for the entire string array? I may need to write a custom ASCII to WCHAR converter for the array, but the stumbling block is how to allocate enough space in the BSTR. SysReAllocString is just going to allocate space for the first string in the array.

    I can just do a straight up GlobalReAlloc for the string array and the BSTR header, but it doesn't look like there is a function like SysReAllocString that can realloc a BSTR for a string array. Or am I missing something?

    Thanks

  25. #25
    PowerPoster Zvoni's Avatar
    Join Date
    Sep 2012
    Location
    To the moon and then left
    Posts
    4,418

    Re: Trying to Figure Out How to Communicate with a C DLL

    Two things:
    1) There is no "Short" in VB
    ByVal getError As Integer (16 Bit signed)
    ByVal getError As Long (32 Bit signed)
    And since it looks like a Boolean use as Long (or Boolean directly)
    Of course it has to match on the C-side.
    If you can't change the function on the C-side i'd consider going with a constant.
    Const CFALSE As Integer = 0
    Const CTRUE As Integer = 1

    I just don't like implicit conversion.....

    2)
    what qvb6 was alluding to:
    Dim StrVar As String (you have As Long)
    ret = ttw_VBGetAppResult(VarPtr(StrVar), True)

    and that, on the C-side, you construct a single String from your Array of Strings (with NUL-characters in between separating the members).
    On the VB-side then it's a simple split with Chr(0) as Delimiter.

    You could even go the "classic" WINAPI-way calling the function first with a Null-Pointer (eg. with "&0"), telling your function to return the number of space needed in the String-Variable.
    On the VB-side you allocate memory based on this result, and call your function a second time, this time sending the allocated string as shown above.
    Last edited by Zvoni; Mar 14th, 2019 at 03:55 AM.
    Last edited by Zvoni; Tomorrow at 31:69 PM.
    ----------------------------------------------------------------------------------------

    One System to rule them all, One Code to find them,
    One IDE to bring them all, and to the Framework bind them,
    in the Land of Redmond, where the Windows lie
    ---------------------------------------------------------------------------------
    People call me crazy because i'm jumping out of perfectly fine airplanes.
    ---------------------------------------------------------------------------------
    Code is like a joke: If you have to explain it, it's bad

  26. #26

    Thread Starter
    New Member
    Join Date
    Feb 2019
    Posts
    14

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by Zvoni View Post
    Two things: There is no "Short" in VB
    ByVal getError As Integer (16 Bit signed)
    ByVal getError As Long (32 Bit signed)

    what qvb6 was alluding to:
    Dim StrVar As String (you have As Long)
    ret = ttw_VBGetAppResult(VarPtr(StrVar), True)

    and that, on the C-side, you construct a single String from your Array of Strings (with NULL-characters in between separating the members).
    On the VB-side then it's a simple split with Chr(0) as Delimiter
    My bad about the Short.

    qvb6 said that the variable had to be declared as Long in VB, but I probably misunderstood. Am I going to have to manually reallocate space for the BSTR on the C side to handle the entire string array?

  27. #27
    PowerPoster Zvoni's Avatar
    Join Date
    Sep 2012
    Location
    To the moon and then left
    Posts
    4,418

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by wdolson View Post
    My bad about the Short.

    qvb6 said that the variable had to be declared as Long in VB, but I probably misunderstood. Am I going to have to manually reallocate space for the BSTR on the C side to handle the entire string array?
    Yes, in the Declare of your Function, not the Variable itself.
    StrVar is a String
    VarPtr(StrVar) takes the Address of the Variable

    As for ReAlloc: Probably yes.
    Take all strings, count the Characters (resp. the Bytes), remember the needed NUL's you need to insert, and you're set.
    String1="This is Teststring1" --> 19 Characters, 38 Bytes
    String2="This is another TestString" --> 26 Characters, 52 Bytes

    Result --> 45 Characters (90 Bytes) + 1 Character for NUL (2 Bytes)

    As mentioned above you can do the allocation on the VB-side
    Something along the lines
    VB-Side:
    First Call
    ret = ttw_VBGetAppResult(&0, True)

    C-Side:
    If AddressOFStringVariable=0 Then CalculateNeededLength
    In your function result you return in this example 46 (45 characters + 1 NUL-Character)

    VB-Side:
    StrVar=Space(ret) (Or StrVar=String(Ret, "*"))
    Second call
    ret = ttw_VBGetAppResult(VarPtr(StrVar),True)

    C-Side (I'm simluating as if the DLL was written in VB)
    If AddressOFStringVariable=0 Then
    CalculateNeededLength
    Else
    Put the String into the Address provided
    Set Result to something (Status?)
    NukeTheWhiteHouse
    End IF
    Last edited by Zvoni; Mar 14th, 2019 at 04:08 AM.
    Last edited by Zvoni; Tomorrow at 31:69 PM.
    ----------------------------------------------------------------------------------------

    One System to rule them all, One Code to find them,
    One IDE to bring them all, and to the Framework bind them,
    in the Land of Redmond, where the Windows lie
    ---------------------------------------------------------------------------------
    People call me crazy because i'm jumping out of perfectly fine airplanes.
    ---------------------------------------------------------------------------------
    Code is like a joke: If you have to explain it, it's bad

  28. #28
    Fanatic Member
    Join Date
    Feb 2019
    Posts
    706

    Re: Trying to Figure Out How to Communicate with a C DLL

    In post #6 in this thread, the last link that dz32 posted contain important links that you may want to check out, such as "Writing C DLLs for use with VB", and "SafeArrays in C".

    If the C DLL is using ANSI, then using BSTR and SAFEARRAY(to handle an array of Strings, and resize them) would be extra work. Less coding method is to let VB do the Unicode to ANSI conversion. Example:

    VB Side:

    Code:
    Private Declare Function ttw_VBGetAppResult Lib "TTWLib32" (ByRef StrVar As String, ByRef getError As Integer) As Long
    
    Dim sIn As String
    Dim sOut() As String
    Dim getError As Integer ' 16 Bits
    sIn = String(1000, 0) ' Allocate buffer, 1000 x Null. You can also use Space(1000)
    ret = ttw_VBGetAppResult(sIn, getError)
    'sIn = Left(sIn, ret) ' Trim extra characters, assuming ret=actual buffer size
    sOut = Split(sIn, Chr(0))
    For i = 0 To UBound(sOut)
       Debug.Print i; "'" & sOut(i) & "'"
    Next
    Because the first parameter is declared "As String" in ttw_VBGetAppResult(), VB would create a temp buffer, converts the string to ANSI, and when the function returns, if the parameter was declared ByVal, the result is thrown away(Not what you want), and if it was declared ByRef, then the temp buffer(which was changed by the C DLL) is converted back to Unicode. String length would stay the same.

    Using BSTR/SAFEARRAY means extra work on the C DLL, so in VB side coding is easier(No need to allocate buffer then use Split). Changing the first parameter from "As String" to "As Long" let us turn off ANSI<->Unicode conversion, and pass either BSTR(VarPtr), or WCHAR(StrPtr). BSTR consists of the length in bytes of the string(4 bytes), not including the null, immediately followed by the Unicode string, and followed by a null char(2 bytes).

    As for callbacks, in VB side you could use AddressOf operator to pass a function pointer to the C DLL. However, I think you can't use other than the GUI thread to make the callback. Another option is to use SendMessage/PostMessage to send a message to VB from the C DLL if it's multithreaded.

  29. #29

    Thread Starter
    New Member
    Join Date
    Feb 2019
    Posts
    14

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by qvb6 View Post
    In post #6 in this thread, the last link that dz32 posted contain important links that you may want to check out, such as "Writing C DLLs for use with VB", and "SafeArrays in C".

    If the C DLL is using ANSI, then using BSTR and SAFEARRAY(to handle an array of Strings, and resize them) would be extra work. Less coding method is to let VB do the Unicode to ANSI conversion. Example:

    VB Side:

    Code:
    Private Declare Function ttw_VBGetAppResult Lib "TTWLib32" (ByRef StrVar As String, ByRef getError As Integer) As Long
    
    Dim sIn As String
    Dim sOut() As String
    Dim getError As Integer ' 16 Bits
    sIn = String(1000, 0) ' Allocate buffer, 1000 x Null. You can also use Space(1000)
    ret = ttw_VBGetAppResult(sIn, getError)
    'sIn = Left(sIn, ret) ' Trim extra characters, assuming ret=actual buffer size
    sOut = Split(sIn, Chr(0))
    For i = 0 To UBound(sOut)
       Debug.Print i; "'" & sOut(i) & "'"
    Next
    Because the first parameter is declared "As String" in ttw_VBGetAppResult(), VB would create a temp buffer, converts the string to ANSI, and when the function returns, if the parameter was declared ByVal, the result is thrown away(Not what you want), and if it was declared ByRef, then the temp buffer(which was changed by the C DLL) is converted back to Unicode. String length would stay the same.

    Using BSTR/SAFEARRAY means extra work on the C DLL, so in VB side coding is easier(No need to allocate buffer then use Split). Changing the first parameter from "As String" to "As Long" let us turn off ANSI<->Unicode conversion, and pass either BSTR(VarPtr), or WCHAR(StrPtr). BSTR consists of the length in bytes of the string(4 bytes), not including the null, immediately followed by the Unicode string, and followed by a null char(2 bytes).

    As for callbacks, in VB side you could use AddressOf operator to pass a function pointer to the C DLL. However, I think you can't use other than the GUI thread to make the callback. Another option is to use SendMessage/PostMessage to send a message to VB from the C DLL if it's multithreaded.
    I have no idea if the VB program is multithreaded or not. It was originally written in 1990 and has been tweaked a number of times over the years. We had someone working to clean up the program and fix a bunch of bugs a few years ago, but we lost his support. We make heavy use of SendMessage and PostMessage in the C and C++ code and I'm well versed with how they work in C/C++, but they are not used anywhere in the VB program. I thought I would post some of the existing DDE code in VB to show how it's done now. There are three different DDE calling functions, but they are variations on the same theme. I was hoping to be able to do a straight replacement of these functions because I don't want to change the code all over the program. There are around 400 places DDE calls are made and rewriting every function would take me a long time.

    Here is one of the DDE handler functions (I made a few edits to remove some proprietary details, if the code doesn't look like it will run, that's probably an error from my editing):
    Code:
    Function DDEExecute2(topicstr As String, execontrol As Control, CmdStr As String, ErrCtl As Control) As Integer
    'This function serves as a generic api launcher for api commands that
    'expects to receive data after the execute command.
    'errctl may be a label or text control - use text control when passing
    'large amounts of data because a label has a limit of 2048 (be sure to
    'set the text control's MultiLine to true), error messages and request data
    'are placed in the caption/text of this control.
    'Debug.Print "DDEExecute2 - " & cmdstr
    
    Dim temp As String, msg As String, appname As String
    Dim applen As Integer
        
        Dim LinkMethod As String   ' Added for use in properly displaying errors via CaptureDDEActivity
        Dim DDECompareTimeout As Boolean   ' Added to support error handling of timeouts 
        
        If Not UTIAppsRunning Then Exit Function
        
        If Testing = 0 Then
            execontrol.LinkTimeout = -1
        Else
            execontrol.LinkTimeout = 3000   ' 
        End If
        
        execontrol.LinkMode = vbLinkNone
        
        'initiate link if first time
        'only one instance of the app must be loaded
         On Error GoTo applinkerr
         execontrol.LinkTopic = topicstr
         DoEvents
         execontrol.LinkMode = vbLinkManual
    
        On Error GoTo execontrol2ExeErr 'do error handling on error
        
        LinkMethod = "LinkExecute"   ' 
        execontrol.LinkExecute CmdStr
        
        CaptureDDEActivity execontrol.LinkTopic, "", CmdStr, "", "DDEExecute2", "LinkExecute", ErrCtl.Name, "N/A"   ' 
        
        DoEvents
        execontrol.LinkItem = "result"
        DoEvents
        'check type of control, different controls have different
        'text fields that they display, use the proper field to
        'save the current text because a LinkRequest command will
        'return data into the text field of the control.
        If TypeOf execontrol Is Label Then
            temp = execontrol.Caption
        ElseIf TypeOf execontrol Is TextBox Then
            temp = execontrol.text
        End If
        execontrol.LinkMode = vbLinkManual
        
        LinkMethod = "LinkRequest"   ' 
        execontrol.LinkRequest  'get data
        
        If TypeOf execontrol Is Label Then
            CaptureDDEActivity execontrol.LinkTopic, execontrol.LinkItem, "", execontrol.Caption, "DDEExecute2", "LinkRequest", "N/A", "N/A"
        Else
            CaptureDDEActivity execontrol.LinkTopic, execontrol.LinkItem, "", execontrol.text, "DDEExecute2", "LinkRequest", "N/A", "N/A"
        End If
    
        DoEvents
        'place data in the error label and restore
        'previous control text
        If TypeOf execontrol Is Label Then
            If TypeOf ErrCtl Is Label Then
                ErrCtl.Caption = execontrol.Caption
            Else
                ErrCtl.text = execontrol.text
            End If
            execontrol.Caption = temp
        ElseIf TypeOf execontrol Is TextBox Then
            If TypeOf ErrCtl Is Label Then
                ErrCtl.Caption = execontrol.text
            Else
                ErrCtl.text = execontrol.text
            End If
            execontrol.text = temp
        End If
        execontrol.LinkMode = vbLinkNone
        Exit Function
    
    execontrol2ExeErr:  'error handling routine
    
        Select Case LinkMethod
            
            Case "LinkExecute"
                CaptureDDEActivity execontrol.LinkTopic, "", CmdStr, Err.Description, "DDEExecute2", "LinkExecute", ErrCtl.Name, "N/A"
                
            Case "LinkRequest"
                CaptureDDEActivity execontrol.LinkTopic, execontrol.LinkItem, "", Err.Description, "DDEExecute2", "LinkRequest", "N/A", "N/A"
                
        End Select
    
        DDEExecute2 = -1
        execontrol.LinkItem = "error"
            
        ' 9/6/2011, Added section to capture DDE timeouts during comparisons
        DDECompareTimeout = False
        If Testing = 1 And Err.Description = "Timeout while waiting for DDE response" Then
            MsgBox "Timeout waiting for response." & vbNewLine & vbNewLine & "Please close  window" & vbNewLine & "before proceeding with operation.", vbExclamation, PROGRAMNAME   
            DDECompareTimeout = True
        End If
            
        'check type of control, different controls have different
        'text fields that they display, use the proper field to
        'save the current text because a LinkRequest command will
        'return data in the text field of the controls.
        If TypeOf execontrol Is Label Then
            temp = execontrol.Caption
            
            If DDECompareTimeout = False Then   ' 9/6/2011, Added If statement to prevent LinkRequest from being called during compare timeouts
                execontrol.LinkRequest
            
                CaptureDDEActivity execontrol.LinkTopic, execontrol.LinkItem, "", execontrol.Caption, "MTCall_frm", "DDEExecute2", "LinkRequest", "N/A", "N/A"   ' 8/7/2011 - Added
            End If
            
            If TypeOf ErrCtl Is Label Then
                ErrCtl.Caption = execontrol.Caption
            Else
                ErrCtl.text = execontrol.text
            End If
            execontrol.Caption = temp
        ElseIf TypeOf execontrol Is TextBox Then
            temp = execontrol.text
            execontrol.LinkMode = vbLinkManual
            
            If DDECompareTimeout = False Then   ' 9/6/2011, Added If statement to prevent LinkRequest from being called during compare timeouts
                execontrol.LinkRequest
            
                CaptureDDEActivity execontrol.LinkTopic, execontrol.LinkItem, "", execontrol.text, "DDEExecute2", "LinkRequest", "N/A", "N/A"   ' 8/7/2011 - Added
            End If
            
            If TypeOf ErrCtl Is Label Then
                ErrCtl.Caption = execontrol.text
            Else
                ErrCtl.text = execontrol.text
            End If
            execontrol.text = temp
        Else    'cannot place error message in picture box
            MsgBox "Error occurred during DDE Execute"
        End If
        execontrol.LinkMode = vbLinkNone
        Exit Function
    
    applinkerr:
        'error occurred trying to establish a DDE link with
        'an application that is not currently running or has
        'multiple instances running.
        msg = Error$(Err)  'get error message
        msg = msg & NL & NL
        applen = InStr(topicstr, "|")
        appname = Mid$(topicstr, 1, applen - 1)
        msg = msg & "Application " & appname & " is not loaded or more than" & NL & "one instance of " & appname & " is running."
        MsgBox msg, 0, "DDE Error"
        execontrol.LinkMode = vbLinkNone
        DDEExecute2 = -1
        Exit Function
    
    End Function
    CaptureDDEActivity is a debugging window the programmer cleaning things up implemented. It's a handy tool for viewing DDE traffic.

    A typical calling function looks like this:
    Code:
    Public Function Compare_Summary(TestFileName As String) As Integer
    
    DDEcontrol = ""
    ErrorText = ""
    OutPut_txt = ""
    
    CmdStr = "[Compare.Summary(" & TestFileName & ")]"
    res = DDEExecute2("MyApp|Execute", DDEcontrol, CmdStr, ErrorText)
    
    Compare_Summary = res
    
    OutPut_txt.text = ErrorText
    
    End Function
    In some cases ErrorText might be an error if the call had a problem, or it may be the returned string, or an array of strings terminated with a double NULL (on the C side). There is a hidden form called DDE Calls in the program with a number of controls on it. One is a TextBox called Output_txt. There is also a Label called ErrorText, and a TextBox called DDEControl. There also appear to be a few controls I don't think are ever used: a Label called Label1 and a ListBox called pSort_list. I was hoping to reuse the Output_txt and ErrorText without using any of the other DDE controls.

    The string array is often fairly small, but it can be 15K in some instances. I have seen the string array null separated, but now I can't find an example. All the arrays I see now in the captured data are tab separated, which would make the return one long string. Maybe I hallucinated the null separated array?

    In any case, I don't see where the VB app sets the string size and the C apps just allocate and return what they need to return. I assume the DDE interface is doing some kind of string sizing, but that's hidden from me in the depths of the OS functions.

  30. #30
    Fanatic Member
    Join Date
    Feb 2019
    Posts
    706

    Re: Trying to Figure Out How to Communicate with a C DLL

    Standard EXE VB6 Projects are always single threaded. ActiveX EXE Projects can be multithreaded, but I doubt that you have that. If you go to Project-->Properties, the Component tab is disabled for Standard EXE projects.

    I haven't used DDE, but it's just one way to exchange data. BSTR/SAFEARRAY is another option, and so is just sending ANSI/Unicode string back and forth. I suggest that you stick with DDE unless you have reasons otherwise. I know that DDE might use HWND_BROADCAST when sending WM_DDE_INITIATE, and that could cause things to freeze for few seconds if any top level window is busy(such as single threaded VB6 apps searching for files without yielding control with DoEvents every now and then).

    The SendMessage/PostMessage approach I mentioned require more work in the VB side. You have to either add a hidden control, such as TextBox, and send WM_SETTEXT to it from the C DLL, which is similar to DDE, or subclass a VB form or control and send a custom WM_APP message to it(More work on the VB side). When SendMessage sends to a different thread, it switches to that thread, process the message, then switch back to the original thread before returning. That's why I suggested it, but it seems that your DLL is single threaded too.

    So there are several approaches, and sticking with what you have is easier.

  31. #31

  32. #32

    Thread Starter
    New Member
    Join Date
    Feb 2019
    Posts
    14

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by qvb6 View Post
    Standard EXE VB6 Projects are always single threaded. ActiveX EXE Projects can be multithreaded, but I doubt that you have that. If you go to Project-->Properties, the Component tab is disabled for Standard EXE projects.

    I haven't used DDE, but it's just one way to exchange data. BSTR/SAFEARRAY is another option, and so is just sending ANSI/Unicode string back and forth. I suggest that you stick with DDE unless you have reasons otherwise. I know that DDE might use HWND_BROADCAST when sending WM_DDE_INITIATE, and that could cause things to freeze for few seconds if any top level window is busy(such as single threaded VB6 apps searching for files without yielding control with DoEvents every now and then).

    The SendMessage/PostMessage approach I mentioned require more work in the VB side. You have to either add a hidden control, such as TextBox, and send WM_SETTEXT to it from the C DLL, which is similar to DDE, or subclass a VB form or control and send a custom WM_APP message to it(More work on the VB side). When SendMessage sends to a different thread, it switches to that thread, process the message, then switch back to the original thread before returning. That's why I suggested it, but it seems that your DLL is single threaded too.

    So there are several approaches, and sticking with what you have is easier.

    I was figuring out how something worked on the C side this week. It looks like I'm going to have to handle the return strings from one of the apps differently than the rest. But the core issue right now is getting any strings back in the proper format. I was able to get a response back, but the string was all 0xfe (FE hex).

    The functions in VB are declared as:

    Code:
    Private Declare Function ttw_SendToApp Lib "TTWLib32" (ByVal xfertype As Integer, ByVal CommandStr As String, ByVal bExpectReturn As Integer) As Integer
    Private Declare Function ttw_VBGetAppResult Lib "TTWLib32" (ByRef StrVar As String, ByVal getError As Integer) As Long
    Private Declare Function ttw_ClearAppResult Lib "TTWLib32" () As Long
    And the VB function looks like:
    Code:
    Function DDEExecute2(topicstr As String, execontrol As Control, CmdStr As String, ErrCtl As Control) As Integer
    
        Dim temp As String, msg As String, appname As String
        Dim applen As Integer
        
        Dim LinkMethod As String 
        Dim DDECompareTimeout As Boolean
        
        Dim cmdNum As Integer  
        Dim ret As Integer     
        Dim errText As String 
        Dim sIn As String
        'Dim sOut() As String
        sIn = String(2048, 0) ' Allocate buffer, 1000 x Null
         
        If Not UTIAppsRunning Then Exit Function
        
         execontrol.LinkTopic = topicstr
         DoEvents
    
        LinkMethod = "LinkExecute" 
    
        ' New code
        If topicstr = "App1|Execute" Then
            cmdNum = 3
        ElseIf topicstr = "App2|Execute" Then
            cmdNum = 2
        ElseIf topicstr = "App3|List" Then
            cmdNum = 12
        ElseIf topicstr = "App3|Execute" Then
            cmdNum = 1
        ElseIf topicstr = "App4|Execute" Then
            cmdNum = 8
        End If
        
        ret = ttw_SendToApp(cmdNum, CmdStr, 1)
         
        CaptureDDEActivity execontrol.LinkTopic, "", CmdStr, "", "DDEExecute2", "LinkExecute", ErrCtl.Name, "N/A"  
        
        DoEvents
        execontrol.LinkItem = "result"
        DoEvents
    
        'check type of control, different controls have different
        'text fields that they display, use the proper field to
        'save the current text because a LinkRequest command will
        'return data into the text field of the control.
        If TypeOf execontrol Is Label Then
            temp = execontrol.Caption
        ElseIf TypeOf execontrol Is TextBox Then
            temp = execontrol.text
        End If
    
        LinkMethod = "LinkRequest"  
    '    execontrol.LinkRequest  'get data
        ' GET DATA NEW WAY
        ret = ttw_VBGetAppResult(sIn, 0)
        
    
        If TypeOf execontrol Is Label Then
            execontrol.Caption = sIn
            CaptureDDEActivity execontrol.LinkTopic, execontrol.LinkItem, "", execontrol.Caption, "DDEExecute2", "LinkRequest", "N/A", "N/A"
        Else
            execontrol.text = sIn
            CaptureDDEActivity execontrol.LinkTopic, execontrol.LinkItem, "", execontrol.text, "DDEExecute2", "LinkRequest", "N/A", "N/A"
        End If
    
        DoEvents
        'place data in the error label and restore
        'previous control text
        If TypeOf execontrol Is Label Then
            If TypeOf ErrCtl Is Label Then
                ErrCtl.Caption = execontrol.Caption
            Else
                ErrCtl.text = execontrol.text
            End If
            execontrol.Caption = temp
        ElseIf TypeOf execontrol Is TextBox Then
            If TypeOf ErrCtl Is Label Then
                ErrCtl.Caption = execontrol.text
            Else
                ErrCtl.text = execontrol.text
            End If
            execontrol.text = temp
        End If
    
        ret = ttw_ClearAppResult()
        Exit Function
    
    execontrol2ExeErr:  'error handling routine
    
        Select Case LinkMethod
            
            Case "LinkExecute"
                CaptureDDEActivity execontrol.LinkTopic, "", CmdStr, Err.Description, "DDEExecute2", "LinkExecute", ErrCtl.Name, "N/A"
                
            Case "LinkRequest"
                CaptureDDEActivity execontrol.LinkTopic, execontrol.LinkItem, "", Err.Description, "DDEExecute2", "LinkRequest", "N/A", "N/A"
                
        End Select
    
        DDEExecute2 = -1
        execontrol.LinkItem = "error"
            
        DDECompareTimeout = False
        If Testing = 1 And Err.Description = "Timeout while waiting for DDE response" Then
            'MsgBox "App is taking a long time to respond." & vbNewLine & vbNewLine & "Check to see if App has crashed and restart if necessary.", vbExclamation, PROGRAMNAME
            'MsgBox "Error occured during compare.  Timeout waiting for results." & vbNewLine & vbNewLine & "Check  'Compare Trace' summary window for results.", vbExclamation, PROGRAMNAME
            MsgBox "Timeout waiting for response", vbExclamation, PROGRAMNAME 
            DDECompareTimeout = True
        End If
            
        'check type of control, different controls have different
        'text fields that they display, use the proper field to
        'save the current text because a LinkRequest command will
        'return data in the text field of the controls.
        If TypeOf execontrol Is Label Then
            temp = execontrol.Caption
            
            If DDECompareTimeout = False Then 
                execontrol.LinkRequest
            
                CaptureDDEActivity execontrol.LinkTopic, execontrol.LinkItem, "", execontrol.Caption, "DDEExecute2", "LinkRequest", "N/A", "N/A"  
            End If
            
            If TypeOf ErrCtl Is Label Then
                ErrCtl.Caption = execontrol.Caption
            Else
                ErrCtl.text = execontrol.text
            End If
            execontrol.Caption = temp
        ElseIf TypeOf execontrol Is TextBox Then
            temp = execontrol.text
            
            If DDECompareTimeout = False Then
                execontrol.LinkRequest
            
                CaptureDDEActivity execontrol.LinkTopic, execontrol.LinkItem, "", execontrol.text, "DDEExecute2", "LinkRequest", "N/A", "N/A" 
            End If
            
            If TypeOf ErrCtl Is Label Then
                ErrCtl.Caption = execontrol.text
            Else
                ErrCtl.text = execontrol.text
            End If
            execontrol.text = temp
        Else    'cannot place error message in picture box
            MsgBox "Error occurred during DDE Execute"
        End If
        r = DoEvents()
        ret = ttw_ClearAppResult()
        Exit Function
    On the C side, the functions look like:
    Code:
    HRESULT CALLTYPE ttw_VBGetAppResult(BSTR *pVarRet, short getError)
    {
    	USES_CONVERSION;
    	char *b0;
    	BSTR *b1;
    	HRESULT  ret=-1;
    
    	// Size the array for the correct size
    	if(gApiReturnLength==0 || getError!=FALSE)
    		return(-1);
    
    	if(getError==FALSE)
    	{
    		b1 = pVarRet;
    		*(long *)b1 = gApiReturnLength;
    		b1 = pVarRet+2;
    		ret = gApiReturnLength;
    		wcscpy_s((WCHAR *)b1,gApiReturnLength,A2W(gszReturnApiString+DDE_OFFSET));
    	}
    	else
    	{
    		b1 = pVarRet;
    		ret = *(long *)b1 = strlen(gszLastApiError);
    		b1 = pVarRet+2;
    		wcscpy_s((WCHAR *)b1,ret,A2W(gszLastApiError));
    	}
    
    
    	return(ret);
    }
    From the debugger, I can see that the return strings look fine before I put them into StrVar (pVarRet on the C side) to send them back to the VB program.

    I'm obviously doing something wrong. As far as C is concerned, a BSTR is defined as:

    Code:
    typedef OLECHAR *BSTR;
    
    typedef WCHAR OLECHAR;
    And the format of a BSTR is a 4 byte header with the length of the string and the rest as the string in wide characters.

  33. #33
    Fanatic Member
    Join Date
    Feb 2019
    Posts
    706

    Re: Trying to Figure Out How to Communicate with a C DLL

    Close. You can't adjust VB's BSTR length in C the way you are doing, because it would cause leaks. To "adjust" the string length, you either call SysStringXXX functions, which creates a new string(That's the way it's done in VB, despite being inefficient, for efficiency, we use the Mid Statement), or let VB trim the extra nulls. The following use the first approach(Using SysStringXXX () functions).

    You need to change the VB declaration to the following:

    Private Declare Function ttw_VBGetAppResult Lib "TTWLib32" (ByVal StrVar As Long, ByVal getError As Integer) As Long

    In C side call SysReAllocString() or SysReAllocStringLen().

    And in DDEExecute2() in VB side, call it this way:

    ret = ttw_VBGetAppResult(VarPtr(sIn), 0)

    That's how you pass BSTR to C ByRef, despite the parameter was declared ByVal, which passes 32-Bit pointer to C side. This also eliminates the need to use "sIn = String(2048, 0)" in VB side.

    Approach 2: Letting VB trim the extra nulls:

    1 - You still need to change the Declare statement to be the same as above.
    2 - In the C side, remove the line that changes VB string length.
    3 - In VB, call the function below to trim any extra nulls:

    Code:
    Private Sub TrimNull(s As String)
        Dim pos As Long
    
        pos = InStr(s, Chr$(0))
    
        If pos <> 0 Then
            s = Left$(s, pos - 1)
        End If
    End Sub
    Last edited by qvb6; Mar 25th, 2019 at 03:57 AM. Reason: Please see corrections.

  34. #34

    Thread Starter
    New Member
    Join Date
    Feb 2019
    Posts
    14

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by qvb6 View Post
    Close. You can't adjust VB's BSTR length in C the way you are doing, because it would cause leaks. To "adjust" the string length, you either call SysStringXXX functions, which creates a new string(That's the way it's done in VB, despite being inefficient, for efficiency, we use the Mid Statement), or let VB trim the extra nulls. The following use the first approach(Using SysStringXXX () functions).

    You need to change the VB declaration to the following:

    Private Declare Function ttw_VBGetAppResult Lib "TTWLib32" (ByVal StrVar As Long, ByVal getError As Integer) As Long

    In C side call SysReAllocString() or SysReAllocStringLen().

    And in DDEExecute2() in VB side, call it this way:

    ret = ttw_VBGetAppResult(VarPtr(sIn), 0)

    That's how you pass BSTR to C ByRef, despite the parameter was declared ByVal, which passes 32-Bit pointer to C side. This also eliminates the need to use "sIn = String(2048, 0)" in VB side.

    Approach 2: Letting VB trim the extra nulls:

    1 - You still need to change the Declare statement to be the same as above.
    2 - In the C side, remove the line that changes VB string length.
    3 - In VB, call the function below to trim any extra nulls:

    Code:
    Private Sub TrimNull(s As String)
        Dim pos As Long
    
        pos = InStr(s, Chr$(0))
    
        If pos <> 0 Then
            s = Left$(s, pos - 1)
        End If
    End Sub
    Thanks a bunch, that finally worked. It also wasn't working because I didn't initialize my buffers on the C DLL side correctly. The programs swap data back and forth using shared memory between instances of the DLL running with each app. When I inherited the project I saw note in the code from the guy who ported the code to 32 bit saying that the shared memory variables had to be initialized, but didn't explain why. I had been initializing up to now, but forgot with two variables with this and it was driving me crazy that data put into the shared memory in one place wasn't seen in another program. Once I noticed I had forgotten to initialize, it worked! I used SysReAllocStringLen() and that did the job.

    In another part of the program the VB program needs to answer a SendMessage call from the DLL. Is there a primer somewhere on how to answer SendMessage/PostMessage calls with VB? In this case it should be simple, all the C program needs to do is send a flag.

    Thanks again!

  35. #35
    PowerPoster Zvoni's Avatar
    Join Date
    Sep 2012
    Location
    To the moon and then left
    Posts
    4,418

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by wdolson View Post
    In another part of the program the VB program needs to answer a SendMessage call from the DLL. Is there a primer somewhere on how to answer SendMessage/PostMessage calls with VB? In this case it should be simple, all the C program needs to do is send a flag.

    Thanks again!
    I take it, the C-side expects an Address to a function? (kinda like: VB sending the Address of a procedure/function to the c-dll "Hey, this is the address to dump all messages from your c-side")
    Last edited by Zvoni; Tomorrow at 31:69 PM.
    ----------------------------------------------------------------------------------------

    One System to rule them all, One Code to find them,
    One IDE to bring them all, and to the Framework bind them,
    in the Land of Redmond, where the Windows lie
    ---------------------------------------------------------------------------------
    People call me crazy because i'm jumping out of perfectly fine airplanes.
    ---------------------------------------------------------------------------------
    Code is like a joke: If you have to explain it, it's bad

  36. #36

    Thread Starter
    New Member
    Join Date
    Feb 2019
    Posts
    14

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by Zvoni View Post
    I take it, the C-side expects an Address to a function? (kinda like: VB sending the Address of a procedure/function to the c-dll "Hey, this is the address to dump all messages from your c-side")
    I was working on a part of the DLL where pointers needed to be passed, but that's working now. For the SendMessage/PostMessage part, one of the C programs needs to send a notification to the VB program that it's finished. So all I need to do is send a flag, ie set lParam or wParam to 1 to tell the VB program that the processing is complete. How it's done currently is that the C program establishes a DDE channel to the VB program, sends a text string saying it's complete, but I'm replacing the DDE mechanism.

    If there is a cleaner way than SendMessage/PostMessage, I'm open to suggestions. With the existing structure of the programs it's very easy to add new messages between C programs, but VB is a different animal.

    BTW, going through the DLL is much faster than using DDE. With DDE there is noticeable lag as the C programs and the VB program send slews of messages back and forth to set up the processing, but it all happens to fast through the DLL that there is no noticeable lag anymore.

  37. #37
    PowerPoster Zvoni's Avatar
    Join Date
    Sep 2012
    Location
    To the moon and then left
    Posts
    4,418

    Re: Trying to Figure Out How to Communicate with a C DLL

    Quote Originally Posted by wdolson View Post
    How it's done currently is that the C program establishes a DDE channel to the VB program, sends a text string saying it's complete, but I'm replacing the DDE mechanism.

    If there is a cleaner way than SendMessage/PostMessage, I'm open to suggestions. With the existing structure of the programs it's very easy to add new messages between C programs, but VB is a different animal.
    In any way you use, you need an identification of the VB-receiver to pass to the c-dll first, so IMO a callback-function would be my first try, since VB offers the AddressOf-Operator.
    That way, when establishing contact with the dll, the VB-program passes the Address to its function in which it would like to receive the notifications of the c-dll.
    Since you have access to both sources, you can even setup the prototype of the funtion yourself
    Note: AddressOf only works for Functions/Subs in standard bas-modules.
    Doesn't work on Object-Methods (Forms, Classes)

    Aircode!
    Code:
    'In a *.bas-module
    Private Const C_DLL_SENDER As Long = 1
    Private Const C_DLL_MSG_DONE As Long = 100 
    
    Private Declare Function HandShake Lib "MyCDll" (ByVal AddrCallBack As Long) As Long   'You have to provide an access to your c-dll to pass the Address
    
    Public Sub MyCallback(ByVal SenderID As Long, ByVal Message As Long)
    If SenderID = C_DLL_SENDER And Message=C_DLL_MSG_DONE Then
       NukeTheWhiteHouse
    Else
       GoFeedTheDucks
    End If
    End Sub
    
    'Somewhere Else
    Public Sub EstablishConnectionToDll()
    Dim Result As Long
        Result=HandShake(AddressOf(MyCallback))
    End Sub
    I take it you know how to use/call callback-functions from C....
    Last edited by Zvoni; Tomorrow at 31:69 PM.
    ----------------------------------------------------------------------------------------

    One System to rule them all, One Code to find them,
    One IDE to bring them all, and to the Framework bind them,
    in the Land of Redmond, where the Windows lie
    ---------------------------------------------------------------------------------
    People call me crazy because i'm jumping out of perfectly fine airplanes.
    ---------------------------------------------------------------------------------
    Code is like a joke: If you have to explain it, it's bad

  38. #38

    Thread Starter
    New Member
    Join Date
    Feb 2019
    Posts
    14

    Re: Trying to Figure Out How to Communicate with a C DLL

    Just a note on my progress. It turned out I didn't need the SendMessage functionality. When I went in and found what the VB program was doing while the C program was running, it turned out the VB program was just polling until it got a DDE connection from the C program. All I needed to do was change the loop to poll a flag in the DLL. The C program just changes the flag when it's done and the VB program gets on with it's job. Very fast and a heck of a lot cleaner than the old way.

    The VB interface is downright zippy now. It sometimes took a second or two to do things via DDE, but now directly through the DLL there is no noticeable lag at all. That should make the end customers happy.

    I ran into another DDE function I had to replace at the end of last week and I wanted to make sure it was going to work OK, but I followed what I did for the other functions and that worked fine.

    Thanks so much for all the help!

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width