Sharing a DNSServiceRef using kDNSServiceFlagsShareConnection stalls my program - c

I'm building a client using dns-sd api from Bonjour. I notice that there is a flag called kDNSServiceFlagsShareConnection that it is used to share the connection of one DNSServiceRef.
Apple site says
For efficiency, clients that perform many concurrent operations may want to use a single Unix Domain Socket connection with the background daemon, instead of having a separate connection for each independent operation. To use this mode, clients first call DNSServiceCreateConnection(&MainRef) to initialize the main DNSServiceRef. For each subsequent operation that is to share that same connection, the client copies the MainRef, and then passes the address of that copy, setting the ShareConnection flag to tell the library that this DNSServiceRef is not a typical uninitialized DNSServiceRef; it's a copy of an existing DNSServiceRef whose connection information should be reused.
There is even an example that shows how to use the flag. The problem i'm having is when I run the program it stays like waiting for something whenever I call a function with the flag. Here is the code:
DNSServiceErrorType error;
DNSServiceRef MainRef, BrowseRef;
error = DNSServiceCreateConnection(&MainRef);
BrowseRef = MainRef;
//I'm omitting when I check for errors
error = DNSServiceBrowse(&MainRef, kDNSServiceFlagsShareConnection, 0, "_http._tcp", "local", browse_reply, NULL);
// After this call the program stays waiting for I don't know what
//I'm omitting when I check for errors
error = DNSServiceBrowse(&BrowseRef, kDNSServiceFlagsShareConnection, 0, "_http._tcp", "local", browse_reply, NULL);
//I'm omitting when i check for errors
DNSServiceRefDeallocate(BrowseRef); // Terminate the browse operation
DNSServiceRefDeallocate(MainRef); // Terminate the shared connection
Any ideas? thoughts? suggestion?

Since there are conflicting answers, I dug up the source - annotations by me.
// If sharing...
if (flags & kDNSServiceFlagsShareConnection)
{
// There must be something to share (can't use this on the first call)
if (!*ref)
{
return kDNSServiceErr_BadParam;
}
// Ref must look valid (specifically, ref->fd)
if (!DNSServiceRefValid(*ref) ||
// Most operations cannot be shared.
((*ref)->op != connection_request &&
(*ref)->op != connection_delegate_request) ||
// When sharing, pass the ref from the original call.
(*ref)->primary)
{
return kDNSServiceErr_BadReference;
}
The primary fiels is explained elsewhere:
// When using kDNSServiceFlagsShareConnection, there is one primary _DNSServiceOp_t, and zero or more subordinates
// For the primary, the 'next' field points to the first subordinate, and its 'next' field points to the next, and so on.
// For the primary, the 'primary' field is NULL; for subordinates the 'primary' field points back to the associated primary
The problem with the question is that DNSServiceBrowse maps to ref->op==browse_request which causes a kDNSServiceErr_BadReference.
It looks like kDNSServiceFlagsShareConnection is half-implemented, because I've also seen cases in which it works - this source was found by tracing back when it didn't work.

Service referenses for browsing and resolving may unfortunately not be shared. See the comments in the Bonjour documentation for the kDNSServiceFlagsShareConnection-flag. Since you only browse twice I would just let them have separate service-refs instead.
So both DNSServiceBrowse() and DNSServiceResolve() require an unallocated service-ref as first parameter.
I can't explain why your program chokes though. The first DNSServiceBrowse() call in your example should return immediately with an error code.

Although an old question, but it should help people looking around for answers now.
The answer by vidtige is incorrect, the may be shared for any operation, provided you pass the 'kDNSServiceFlagsShareConnection' flag along with the arguments. Sample below -
m_dnsrefsearch = m_dnsservice;
DNSServiceErrorType mdnserr = DNSServiceBrowse(&m_dnsrefsearch,kDNSServiceFlagsShareConnection,0,
"_workstation._tcp",NULL,
DNSServiceBrowseReplyCallback,NULL);
Reference - http://osxr.org/android/source/external/mdnsresponder/mDNSShared/dns_sd.h#0267

Related

How to detect unreleased lock in multi-task C project using static analysis tools?

Is there any way, using static analysis tools(I'm using Codesonar now), to detect unreleased lock problems (something like unreleased semaphores) in the following program?(The comment part marked by arrows)
The project is a multi-task system using Round-robin scheduling, where new_request() is an interrupt task comes randomly and send_buffer() is another period task.
In real case, get_buffer() and send_buffer() are various types of wrappers, which contains many call layers until actual lock/unlock process. So I can't simply specify get_buffer() as lock function in settings of static analysis tool.
int bufferSize = 0; // say max size is 5
// random task
void new_request()
{
int bufferNo = get_buffer(); // wrapper
if (bufferNo == -1)
{
return; // buffer is full
}
if (check_something() == OK)
{
add_to_sendlist(bufferNo); // for asynchronous process of send_buffer()
}
else // bad request
{
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// There should be clear_buffer placed here
// but forgotten. Eventually the buffer will be
// full and won't be cleared since 5th bad request comes.
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
do_nothing();
// clear_buffer(bufferNo);
}
}
int get_buffer()
{
if(bufferSize < 5)
{
bufferSize++;
return bufferSize;
}
else
{
wait_until_empty(); // wait until someone is sent by send_buffer()
return -1;
}
}
// clear specifiled one in buffer
void clear_buffer(int bufferNo)
{
delete(bufferNo)
bufferSize--;
}
// period task
void send_buffer()
{
int sent = send_1st_stuff_in_list();
clear_buffer(sent);
}
yoyozi - Fair disclosure: I'm an engineer at GrammaTech who works on CodeSonar.
First some general things. The relevant parts of the manual for this are on the page: codesonar/doc/html/C_Module/LibraryModels/ConcurrencyModelsLocks.html. Especially the bottom of the page on Resolving Lock Operation Identification Problems.
Based on your comments, I think you have already read this, since you address setting the names in the configuration settings.
So then the question is how many different wrappers do you have? If it is only a few, then the settings in the configuration file are the way to go. If there are many, that gets tedious. And if there are very many it becomes practically impossible.
So knowing some estimate for how many wrapper sets you have would help.
Even with the wrappers accounted for, it may be that the deadlock and race detectors aren't quite what you need for your problem.
If I understand your issue correctly, you have a queue with limited space, and by accident malformed items don't get cleaned out of the queue, and so the queue gets full and that stalls all processing. While you may have multiple threads involved in this implementation, the issue itself would still be a problem in a basically serial setting.
The best way to work with an issue like this is to try and make a simpler example that displays the same core problem. If you can do this in a way that can be shared with GrammaTech, we can work with you on ways to adjust settings or maybe provide hints to the analysis so it can find this issue.
If you would like to talk about this in more detail, and with prodetction against public disclosure of your code, please contact us at support_at_grammatech_dot_com, where the at and dot should be replaced as needed to make a well formed email address.

(Why) Does Windows "Calc.exe" lack a WndProc?

I am fiddling with wndprocs and WinSpy++ and i stumbled upon a strange thing with calc.exe.
It appears to lack a WndProc.
Here is my screenshot: a test program I made, the WinSpy++ window,, showing N/A, and the culprit.
Maybe the tool is a bit outdated, but the empirical evidence proves no WndProc is there.
I don't know if this is by design(this would be strange), or if I am missing something...
Here is referenced code:
Function FindWindow(title As String) As IntPtr
Return AutoIt.AutoItX.WinGetHandle(title)
End Function
Function GetWindowProc(handle As IntPtr) As IntPtr
Return GetWindowLong(handle, WindowLongFlags.GWL_WNDPROC)
End Function
In short (about your code): GetWindowLong() fails because you're trying to read an address in target process address space.
EXPLANATION
When GetWindowLong() returns 0 it means there is an error, from MSDN:
If the function fails, the return value is zero. To get extended error information, call GetLastError.
Check Marshal.GetLastWin32Error() and you probably see error code is ERROR_ACCESS_DENIED (numeric value is 0x5).
Why? Because GetWindowLong() is trying to get address (or handle) of window procedure (not in your code, but in target process, in theory it may even be default window procedure but I never saw an application main window that doesn't hanle at least few messages). You may use this trick (but I never tried!) to see if a window is using default procedure (you have an address or not), I don't know...someone should try.
Now think what WNDPROC is:
LRESULT (CALLBACK* WNDPROC) (HWND, UINT, WPARAM, LPARAM);
An address (valid in process A) is not callable in process B (where it makes no sense at all). Windows DLLs code segments are shared across processes (I assume, I didn't check but it's reasonable in the game between safety and performance).
Moreover CallWindowProc(NULL, ...) will understand that NULL as a special value to invoke window procedure for that window class (on HWND owner). From MSDN:
...If this value is obtained by calling the GetWindowLong function ...the address of a window or dialog box procedure, or a special internal value meaningful only to CallWindowProc.
How Microsoft Spy++ does it (and maybe WinSpy++ does not)? Hard to say without WinSpy++ source code. For sure it's not such easy like GetWindowLong() and right way should involve CreateRemoteThread() and to do LoadLibrary() from that but both Microsoft Spy++ and WinSpy++ source code aren't available (AFAIK) for further inspection...
UPDATE
WinSpy++ inspection/debugging is pretty off-topic with the question (you should post a ticket to developers, your source code may fail for what I explained above, you should - always - check error codes) but we may take a look for fun.
In InjectThread.c we see it uses WriteProcessMemory + CreateRemoteThread then ReadProcessMemory to read data back (not relevant code omitted):
// Write a copy of our injection thread into the remote process
WriteProcessMemory(hProcess, pdwRemoteCode, lpCode, cbCodeSize, &dwWritten);
// Write a copy of the INJTHREAD to the remote process. This structure
// MUST start on a 32bit boundary
pRemoteData = (void *)((BYTE *)pdwRemoteCode + ((cbCodeSize + 4) & ~ 3));
// Put DATA in the remote thread's memory block
WriteProcessMemory(hProcess, pRemoteData, lpData, cbDataSize, &dwWritten);
hRemoteThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)pdwRemoteCode, pRemoteData, 0, &dwRemoteThreadId);
// Wait for the thread to terminate
WaitForSingleObject(hRemoteThread, INFINITE);
// Read the user-structure back again
if(!ReadProcessMemory(hProcess, pRemoteData, lpData, cbDataSize, &dwRead))
{
//an error occurred
}
Window procedure in "General" tab and in "Class" tab differs (in "Class" tab it correctly display a value). From DisplayClassInfo.c:
//window procedure
if(spy_WndProc == 0)
{
wsprintf(ach, _T("N/A"));
}
else
{
wsprintf(ach, szHexFmt, spy_WndProc);
if(spy_WndProc != spy_WndClassEx.lpfnWndProc)
lstrcat(ach, _T(" (Subclassed)"));
}
//class window procedure
if(spy_WndClassEx.lpfnWndProc == 0)
wsprintf(ach, _T("N/A"));
else
wsprintf(ach, szHexFmt, spy_WndClassEx.lpfnWndProc);
As you see they're different values (obtained in different ways). Code to fill spy_WndProc is in WinSpy.c and GetRemoteWindowInfo.c. Extracted code from GetRemoteInfo() in WinSpy.c:
GetClassInfoEx(0, spy_szClassName, &spy_WndClassEx);
GetRemoteWindowInfo(hwnd, &spy_WndClassEx, &spy_WndProc, spy_szPassword, 200);
Now in GetRemoteWindowInfo() we see a call to GetClassInfoExProc (injected in the other process):
pInjData->wndproc = (WNDPROC)pInjData->fnGetWindowLong(pInjData->hwnd, GWL_WNDPROC);
pInjData->fnGetClassInfoEx(pInjData->hInst,
(LPTSTR)pInjData->szClassName, &pInjData->wcOutput);
As you can see (please follow using source code) wcOutput is what is displayed in "Class" tab and wndproc what is displayed in "General" tab. Simply GetWindowLong() fails but GetClassInfoEx does not (but they do not necessarily retrieve same value because (if I'm not wrong) what you have in WNDCLASSEX is what you registered with RegisterClassEx but what you get with GetWindowLong() is what you hooked with SetWindowLong().
You are right. It does not have a WndProc(...) function. It is just simply using a DlgProc to process the dialog events. I now this as I have written 'server/thin client' code in C/C++ to capture direct calls into windows API functions like WndProc(...). Any Windows GUI function really - BeginPaint(...) as an example. I used CALC.EXE as a test and executable runs on server while GUI calls are relayed/returned to/from the thin client. Have only tested calc.exe versions thru Vista. There is a chance the newer versions have been 'programmed' differently - meaning not using Win32 SDK. But, even MFC is just a shell to the Win32 SDK,

Shared Memory and semaphores for (multiple) client and server game

I have to program a little game for a course in C and it has to be done with using shared-memory, semaphores and a client/server architecture which can handle multiple clients (the exact requirement for the game is 2).
The two clients need to do their turns in turns and they are represented by the very same program (no fork() involved here - both started with ./client)
The server has to create all the resources when it starts up. So my main problem is regarding the semaphores. (The shared memory and the game-logic stuff works or isn't really difficult to implement.)
To decide if the server or a client has access to the shared-memory I need one semaphore. I need a second one to decide which of the clients has access. Am I right?
So I got a hint that it could be done with assigning IDs to the clients. So the shared-memory has three additional variables like so:
struct game
{
int id_needed, client_id, id_ready;
... // additional stuff that is needed for the game logic itself
};
As the server boots up I'm initializing one semaphore to be 0 and the other one to be 1.
When the first client appears it checks if his ID is still 0 (it's initialized as zero)
If so, it tries this:
while(my_id == 0)
{
if(semaphore_down(semaphore_1) == 0) // Check if I have access to shared mem
{
shared_memory->id_needed = 1;
if(shared_memory->id_ready == 1)
{
my_id = shared_memory->client_id;
(void) printf("DEBUGGING: My ID is %d\n", my_id);
}
}
}
shared_memory->id_needed = 0;
And in the server I do ...
while(1)
{
if(shared_memory->id_needed = 1)
{
(void) printf("DEBUGGING: ID is needed for another client!");
shared_memory->client_id++;
shared_memory->id_ready = 1;
(void) printf("DEBBUGING: Dispatched new ID: %d", shared_memory->client_id);
}
// If enough players, start the game ...
}
I'm really stuck here. The server just increments the ID (which is only logical), but I'm stuck as to resolve this problem.
I just want the clients to work alternately on the shared-memory and the server to check the values of the game etc.
I've never worked with semaphores before and all the documentation or examples I find do just work with one client and one server, but never multiple clients.
Please enlighten me!
I see one strange thing and two things that obviously are mistakes here
I see semaphore_down but no semaphore_up in the code you showed
you assign instead of comparing: if(shared_memory->id_needed = 1)
even if it was a comparison, it was not right anyway since compiler was free to optimize it out. Make this variable volatile to hint compiler that variable can change outside of the serial code flow. Or better declare it atomic.

Microsoft Crypto API Disable Use of RSAES-OAEP Key Transport Algorithm

I'm using CryptEncryptMessage to generate a PKCS#7 enveloped message. I'm using szOID_NIST_AES256_CBC as the encryption algorithm.
The generated message appears to be valid but is the RSAES-OAEP for the Key Transport Algorithm which has limited support in the wild (Thunderbird, OpenSSL SMIME Module among many others don't support it).
I'll like for CAPI to revert to the older RSAencryption for key transport.
Is there any possible way to do that, I could revert to the low level messaging functions if there is a way rather than to use CryptEncryptMessage but I can't find a way to do that even using the low level functions.
Code:
CRYPT_ENCRYPT_MESSAGE_PARA EncryptMessageParams;
EncryptMessageParams.cbSize = sizeof(CMSG_ENVELOPED_ENCODE_INFO);
EncryptMessageParams.dwMsgEncodingType = PKCS_7_ASN_ENCODING;
EncryptMessageParams.ContentEncryptionAlgorithm.pszObjId = szOID_NIST_AES256_CBC;
EncryptMessageParams.ContentEncryptionAlgorithm.Parameters.cbData = 0;
EncryptMessageParams.ContentEncryptionAlgorithm.Parameters.pbData = 0;
EncryptMessageParams.hCryptProv = NULL;
EncryptMessageParams.pvEncryptionAuxInfo = NULL;
EncryptMessageParams.dwFlags = 0;
EncryptMessageParams.dwInnerContentType = 0;
BYTE pbEncryptedBlob[640000];
DWORD pcbEncryptedBlob = 640000;
BOOL retval = CryptEncryptMessage(&EncryptMessageParams, cRecipientCert, pRecipCertContextArray, pbMsgText, dwMsgTextSize, pbEncryptedBlob, &pcbEncryptedBlob);
The Key Transport Algorithm is a bit tricky to handle, and it may not serve its purpose (I see you noted that you'd like CAPI to support RSAencryption; trust me, I would too). It looks like you've alaready detected the bulk of your problem - The generated message appears is valid, but your method makes it necessary to use CryptEncryptMessage, which won't work well/at all in the long run.
Step 1 - Examine the Code
CRYPT_ENCRYPT_MESSAGE_PARA EncryptMessageParams;
EncryptMessageParams.cbSize = sizeof(CMSG_ENVELOPED_ENCODE_INFO);
EncryptMessageParams.dwMsgEncodingType = PKCS_7_ASN_ENCODING;
EncryptMessageParams.ContentEncryptionAlgorithm.pszObjId = szOID_NIST_AES256_CBC;
EncryptMessageParams.ContentEncryptionAlgorithm.Parameters.cbData = 0;
EncryptMessageParams.ContentEncryptionAlgorithm.Parameters.pbData = 0;
EncryptMessageParams.hCryptProv = NULL;
EncryptMessageParams.pvEncryptionAuxInfo = NULL;
EncryptMessageParams.dwFlags = 0;
EncryptMessageParams.dwInnerContentType = 0;
BYTE pbEncryptedBlob[640000];
DWORD pcbEncryptedBlob = 640000;
BOOL retval = CryptEncryptMessage(&EncryptMessageParams, cRecipientCert, pRecipCertContextArray, pbMsgText, dwMsgTextSize, pbEncryptedBlob, &pcbEncryptedBlob);
Pretty basic, isn't it? Although efficient, it's not really getting the problem done. If you look at this:
EncryptMessageParams.dwFlags = 0;
EncryptMessageParams.dwInnerContentType = 0;
you will see that it is pre-defined, but used only in the definition of retval. However, I could definitely see this as a micro-optimization, and not really useful if we're going to re-write the code. However, I've outlined the basic steps blow to integrate this without a total re-do of the code (so you can keep on using the same parameters):
Step 2 - Editing the Parameters
As #owlstead mentioned in his comments, the Crypto API is not very user-friendly. However, you've done a great job with limited resources. What you'll wanna add is a Cryptographic Enumeration Provider to help narrow down the keys. Make sure you have either Microsoft Base Cryptographic Provider version 1.0 or Microsoft Enhanced Cryptographic Provider version 1.0 to use these efficiently. Otherwise, you'll need to add in the function like so:
DWORD cbName;
DWORD dwType;
DWORD dwIndex;
CHAR *pszName = NULL;
(regular crypt calls here)
This is mainly used to prevent the NTE_BAD_FLAGS error, although technically you could avoid this with a more low-level declaration. If you wanted, you could also create a whole new hash (although this is only necessary if the above implementation won't scale to the necessary factor of time/speed):
DWORD dwBufferLen = strlen((char *)pbBuffer)+1*(0+5);
HCRYPTHASH hHash;
HCRYPTKEY hKey;
HCRYPTKEY hPubKey;
BYTE *pbKeyBlob;
BYTE *pbSignature;
DWORD dwSigLen;
DWORD dwBlobLen;
(use hash as normal w/ crypt calls and the pbKeyBlobs/Signatures)
Make sure to vaildate this snippet before moving on. You can do so easily like so:
if(CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) {
printf("CSP context acquired.\n");
}
If you're documenting or releasing, might want to add a void MyHandleError(char *s) to catch the error so someone who edits but fails can catch it quickly.
By the way, the first time you run it you'll have to create a new set because there's no default. A nice one-liner that can be popped into an if is below:
CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)
Remember that syncing server resources will not be as efficient as doing the re-work I suggested in the first step. This is what I will be explaining below:
Step 3 - Recode and Relaunch
As a programmer, re-coding might seem like a waste of time, but it can definitely help you out in the long run. Remember that you'll still have to code in the custom params when encoding/syncing; I'm not going to hand-feed you all the code like a baby. It should be well sufficient to show you the basic outlines.
I'm definitely assuming that you're trying to handle to the current user's key container within a particular CSP; otherwise, I don't really see the use of this. If not, you can do some basic edits to suit your needs.
Remember, we're going to bypass CryptEncryptMessage by using CryptReleaseContext, which directly releases the handle acquired by the CryptAcquireContext function. Microsoft's standard on the CAC is below:
BOOL WINAPI CryptAcquireContext(
_Out_ HCRYPTPROV *phProv,
_In_ LPCTSTR pszContainer,
_In_ LPCTSTR pszProvider,
_In_ DWORD dwProvType,
_In_ DWORD dwFlags
);
Note that Microsoft's scolding you if you're using a user interface:
If the CSP must display the UI to operate, the call fails and the NTE_SILENT_CONTEXT error code is set as the last error. In addition, if calls are made to CryptGenKey with the CRYPT_USER_PROTECTED flag with a context that has been acquired with the CRYPT_SILENT flag, the calls fail and the CSP sets NTE_SILENT_CONTEXT.
This is mainly server code, and the ERROR_BUSY will definitely be displayed to new users when there are multiple connections, especially those with a high latency. Above 300ms will just cause a NTE_BAD_KEYSET_PARAM or similar to be called, due to the timeout without even a proper error being received. (Transmission problems, anyone with me?)
Unless you're concerned about multiple DLL's (which this doesn't support due to NTE_PROVIDER_DLL_FAIL errors), the basic set up to grab crypt services clientside would be as below (copied directly from Microsoft's examples):
if (GetLastError() == NTE_BAD_KEYSET)
{
if(CryptAcquireContext(
&hCryptProv,
UserName,
NULL,
PROV_RSA_FULL,
CRYPT_NEWKEYSET))
{
printf("A new key container has been created.\n");
}
else
{
printf("Could not create a new key container.\n");
exit(1);
}
}
else
{
printf("A cryptographic service handle could not be "
"acquired.\n");
exit(1);
}
However simple this may seem, you definitely don't want to get stuck passing this on to the key exchange algorithm (or whatever else you have handling this). Unless you're using symmetric session keys (Diffie-Hellman/KEA), the exchange keypair can be used to encrypt session keys so that they can be safely stored and exchanged with other users.
Someone named John Howard has written a nice Hyper-V Remote Management Configuration Utility (HVRemote) which is a large compilation of the techniques discussed here. In addition to using the basic crypts and keypairs, they can be used to permit ANONYMOUS LOGON remote DCOM access (cscript hvremote.wsf, to be specific). You can see many of the functions and techniques in his latest crypts (you'll have to narrow the query) on his blog:
http://blogs.technet.com/b/jhoward/
If you need any more help with the basics, just leave a comment or request a private chat.
Conclusion
Although it's pretty simple once you realize the basic server-side methods for hashing and how the client grabs the "crypts", you'll be questioning why you even tried the encryption during transmits. However, without the crypting clientside, encrypts would definitely be the only secure way to transmit what was already hashed.
Although you might argue that the packets could be decrypted and hashed off the salts, consider that both in-outgoing would have to be processed and stored in the correct timing and order necessary to re-hash clientside.

How do I call a web service from iSeries COBOL?

We need to be able to call an internal web service from COBOL code running on an iSeries LPAR (V6R1). I worked through the one complete example that I was able to find online here. So, my next step was to try to repeat the process and call one of our existing web services.
I used the WSDL2WS command from QSH to generate the C client stubs. I modified the example COBOL client program and attempted a call to my web service. The problem that I'm running into seems to be related to the fact that the example C client methods are returning pointers and the COBOL code is assigning them to pointers. I suspect that an error in my COBOL code is the root of my problem, because the C method that that WSDL2WS created for my web service method returns a simple xsdc_string not a pointer to a custom result type:
xsdc__string TestUnsuccessfulMessage(AXISCHANDLE stub)
{
AXISCHANDLE call = axiscStubGetCall(stub);
xsdc__string Ret = NULL;
axiscCallSetSoapFaultNamespace(call, "http://myserver/PSItemMaintenance/ItemMaintenanceService.svc");
// ======================================================================
// Initialize client engine, set SOAP version, SOAPAction, operation, etc.
// ======================================================================
if (AXISC_SUCCESS != axiscCallInitialize(call, C_DOC_PROVIDER ))
return Ret;
if (NULL==axiscCallGetTransportProperty(call,"SOAPAction",0))
axiscCallSetTransportProperty(call,AXISC_SOAPACTION_HEADER , "http://tempuri.org/IItemMaintenanceService/TestUnsuccessfulMessage");
axiscCallSetSOAPVersion(call, SOAP_VER_1_1);
axiscCallSetOperation(call, "TestUnsuccessfulMessage", "http://tempuri.org/");
// ======================================================================
// Apply SSL configuration properties and user-set SOAP headers.
// ======================================================================
axiscStubIncludeSecure(stub);
axiscStubApplyUserPreferences(stub);
// ======================================================================
// Invoke web service, send/receive operation. Handle output parameters, if any.
// ======================================================================
if (AXISC_SUCCESS == axiscCallSendAndReceive(call))
{
if(AXISC_SUCCESS == axiscCallValidateMessage(call, "TestUnsuccessfulMessageResponse", "http://tempuri.org/", true_))
{
Ret = axiscCallGetElementAsString(call, "TestUnsuccessfulMessageResult", 0);
}
axiscStubCheckForExtraneousElements(stub);
}
axiscCallUnInitialize(call);
return Ret;
}
I can setup the web service in debug mode and can see the call from the iSeries when I run the program that calls the COBOL and C modules. I can also see that I'm returning back a simple string value.
The problem comes when the COBOL program tries to take the return value and use it. I believe that the relevant bits of the COBOL code are:
LINKAGE PROCEDURE FOR "TestSuccessfulMessage"
USING ALL DESCRIBED
LINKAGE SECTION.
01 LookupResult PIC X(1000).
CALL PROCEDURE "TestSuccessfulMessage"
USING BY VALUE STUB
RETURNING LookupResult.
I get a MCH3601 when it's coded this way. If I return into a pointer and then set the address to LookupResult, I end up with a null value.
I hope that I'm just missing a small detail somewhere. I have very little COBOL experience. I'm just trying to create a reference app as a proof of concept for another team within our company. Any help, or suggestions of what to try would be appreciated. I can supply more code.
Update: I tried just moving the LookupResult declaration into working storage. While that eliminated the MCH3601 error, I just got a bunch of garbage data in my display. I can see pieces of info about my iSeries session (i.e. deviceID, etc...) within the data.
I also tried leaving the LookupResult in the linkage section and creating a pointer in working storage. Then I added a "SET Address of LookupResult TO ResultPointer". Again, the call ended without error, but when I display LookupResult I get garbage data. However, it's different than the data that comes back if I return straight into LookupResult. I can see pieces of the SOAP envelope in this data.
Final:
All of the changes that I had to make were in the COBOL code. Here are the relevant pieces:
WORKING-STORAGE SECTION.
01 Endpoint PIC X(100).
01 STUB USAGE POINTER.
01 ResultPointer USAGE POINTER.
LINKAGE SECTION.
01 pszEndpoint PIC X(100).
01 LookupResult PIC X(7).
CALL PROCEDURE "TestSuccessfulMessage"
USING BY VALUE STUB
RETURNING INTO ResultPointer.
SET Address of LookupResult TO ResultPointer.
If I return into a pointer and then set the address to LookupResult, I
end up with a null value.
I am a COBOL person, but not on iSeries. Caveat emptor.
If xsdc__string resolves to a pointer, then your COBOL code should have a variable of type POINTER for the RETURNING portion of the CALL. Perhaps the C function is in fact returning NULL, the code certainly allows for it - maybe axiscCallInitialize isn't returning AXISC_SUCCESS.
At least on z/OS, you would want the COBOL pointer variable to be in either Working-Storage or Local Storage, and then you would SET ADDRESS OF LookupResult TO that pointer variable. I'm presuming that doesn't change just because you're on a different machine architecture.
...FWIW

Resources