Monday, March 21, 2011

Using log4net with shared DLLs


I've got a C# DLL that is used from a number of different places, including .Net EXEs, VB6 EXEs, Work and Outlook COMAddIns and also Outlook forms. It uses log4net for tracing and debugging.

I'd found log4net to be pretty reliable BUT sometimes messages weren't ending up in my log file (usually when I really needed them!). Most recently this happened today when I was trying to trace a problem in my Outlook form. I'd drag the appointment in Outlook which would fire the Item Write event on the form and, although I could loo kin the DB and see that my DLL was working, nothing was showing up in the log4net log file.



How to Investigate the Problem


To find out what was really happening, I used log4net's internal debugging. In the case of an Outlook Form, I switched it on like this:




  1. Started Outlook

  2. Started Visual Studio and opened my DLL project

  3. Switched on log4net internal debugging by modifying my log4net configuration file and changed the <log4net> to read <log4net debug="true">

  4. Used "Tools-Attach to Process" and attached to the Outlook.exe process

  5. Placed a breakpoint in the project on a line that I knew it should encounter

  6. Ran the test again...



The Problem


When I ran the test again with log4net debugging switched on, I could clearly see that log4net was unable to write to my logfile because it was locked. I didn't obviously have it locked (I had it open in TextPad but closing that didn't fix the problem). A quick scout around revealed that log4net with the RollingFileAppender by default will lock the file and keep it locked. Clearly this was the problem in my case where multiple programs all use my DLL which writes to the same logfile.



The Solution


I do want all of the instances of my DLL to write to the same log file, but I don't want them to lock the file and keep each other out. The solution was to add the following to the appender section of my log4net configuration file:




<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />



This instructs log4net to get the lock, write and release the lock every time - so it doesn't keep the file locked. With this in place, all of my DLL instances now write to the logfile, ALL of the time! There is a slight performance overhead to this approach, but then I'd rather than it works slowly, than not working quickly!

Tuesday, July 27, 2010

ORA-06508 error calling package in different schema


I had a PL/SQL package (let's call it PACKAGE_A) in schema SCHEMA_A which called a procedure in PACKAGE_B in SCHEMA_B. When I added a new parameter to the procedure in PACKAGE_B, I got some very strange errors showing up in PACKAGE_A. After trying all combinations of compiling PACKAGE_A and PACKAGE_B and REVOKEing and GRANTing EXECUTE rights to PACKAGE_B, I was consistently coming up with this Oracle error when I ran PACKAGE_A:



ORA-06508: PL/SQL: could not find program unit being called


Note that PACKAGE_A would compile fine, it just wouldn't run. I finally managed to get rid of this error by logging off my PL/SQL session in SCHEMA_A and then logging back in. After that, my PACKAGE_A call worked fine. "When in doubt, reboot!" A pretty old-school solution but it was the answer in this case!



NB. Oracle Version 10g Enterprise Edition 10.2.0.2.0

Monday, February 22, 2010

"The form required to view this message cannot be displayed" error accessing Organizational Forms Library in Outlook 2003


I had a VB6 program running on a server using Outlook 2003 client to create appointments. The appointments used a custom Outlook form stored in the Organizational Forms Library. Suddenly the program started spitting out this error:



"The form required to view this message cannot be display. Contact your administrator."



If I used the Outlook Client (on the server) to try to design the form via Tools-Forms-Design Form-Organizational Forms Library-{shift}+Open then I got the following error message:



"The form you selected cannot be displayed. The form required to view this message cannot be display. Contact your administrator."



The solution was to clear the Outlook Forms cache. This can be done via:





  1. Click on the "Tools" menu

  2. Click on "Options"

  3. Click on the "Other" tab

  4. Click the "Advanced Options" button

  5. Click the "Custom Forms" button

  6. Click the "Manage Forms" button

  7. Click the "Clear Cache" button

  8. Close and OK through all Outlook Property/Option windows




Credit for these steps from http://www.experts-exchange.com/Software/Server_Software/Email_Servers/Exchange/Q_24342937.html

Wednesday, February 17, 2010

Compile All Invalid Oracle objects

If you add a column to a table that invalidates lots of packages, triggers, views etc then you'll need to compile all of these invalidated objects. And each of these objects might in turn invalidate more - so this can take a few cycles.





I use the SQL statement below to generate the COMPILE statements for all invalid objects. I then copy and paste the COMPILE statements into a SQL session and run them (compiling them). Then I run the code below again (and compile again...etc) until no more are found.


SELECT 'ALTER '
||decode(object_type,'PACKAGE BODY','PACKAGE', object_type)
||' '||object_name||' COMPILE'
||decode(object_type,'PACKAGE BODY',' BODY', '')||';'
FROM user_objects
WHERE status = 'INVALID'
AND object_type <> 'SYNONYM'


The output from this (which I paste into a SQL window to run) will be something like:


ALTER TRIGGER CUST_AUI COMPILE;
ALTER PACKAGE CUST_PKG COMPILE;
ALTER PACKAGE CUST_PKG COMPILE BODY;
ALTER VIEW CUST_VW COMPILE;
ALTER PACKAGE CUST_AUDIT_PKG COMPILE BODY;

Monday, October 5, 2009

Adding HTTP Authentication for a user in Subversion

If you've already got your HTTP Authentication setup (instructions for that can be found here) then you can add a new user (eg. xyz) like this:


$htpasswd2 -m /etc/svn-auth-file xyz


Note that for Apache 2, you use the htpasswd2 command, NOT htpasswd.

Monday, May 4, 2009

Solution to the ORABEL-08021 "partnerlink not found" problem!

I was seeing an ORABPEL-08021 error when calling a Oracle BPEL web service via C# in .Net. Specifically, the error returned was:


<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header/>
<env:Body>
<env:Fault xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<faultcode xmlns="">env:Server</faultcode>
<faultstring xmlns="">ORABPEL-08021 Cannot find partner wsdl. parnterLink "MyService" is not found in process "MyService" (revision "1.0") Please check the deployment descriptor of the process to find the correct partnerLink name.
</faultstring>
<faultactor xmlns=""></faultactor>
</env:Fault>
</env:Body>
</env:Envelope>


The problem was that I was using the wrong URI to access the service. I was using:

http://myserver:8888/orabpel/default/MyService/1.0/MyService

and I should have been using:

http://myserver:8888/orabpel/default/MyService/1.0

So, my own silly fault really! Normally I can take the WSDL address (eg. http://myserver:8888/orabpel/default/MyService/1.0/MyService?wsdl) and just drop the "?wsdl" off the end to get the service endpoint. Not with these Oracle BPEL services though. If I had bothered to read the WSDL, I would have seen that the endpoint address is clearly specified. One more thing to note: with an Oracle BPEL service, you can also drop the version number off the end and it will still work, calling the latest version by default. eg:

http://myserver:8888/orabpel/default/MyService

Wednesday, March 25, 2009

Where should I put my .Net DLL for COM Interop?

I have a .Net DLL which I want to use from VB6 via COM interop. Where should I put it when I deploy it? (look here to see how to make a .Net DLL useable via VB6)



The key thing is that in order to use your .Net dll from VB6 you do need to register the .Net DLL. You don't need to to do anything special in the VB6 program - it doesn't matter how you reference the .Net DLL in your VB6 program. The command to register a .Net DLL is "regasm". This lives under "C:\Windows\Microsoft.Net\Framework\v2.0.50727" (or whatever .Net framework version you have).



There are three options for where to put your .Net DLL:



1. Put it in the GAC. If you want to share the same .Net DLL with more than one VB6 program then you can put it in the GAC (Global Assembly Cache). To do this, you can use an MSI file, or manually, you can copy it to c:\windows\assembly, and then register it via "regasm c:\winnt\assembly\mydotnet.dll".



2. Put it in the EXE folder. If you want the .Net DLL to live in the same place as your VB6 EXE then you can copy it to that folder, and then use "regasm c:\vb6\exe\folder\mydotnet.dll". Note that the .Net DLL has to be in the same folder as the VB6 EXE. So if you have VB6 EXE calling a VB6 DLL which calls your .Net dll then it must go in the EXEs folder - it won't find it in VB6 DLLs folder.



3. Put it in any folder via codebase. If you want the .Net DLL to live anywhere else, you can use use "regasm c:\program files\vanwills\mydotnet.dll /codebase". This will register the .Net DLL to the path you provide. This is a bit like the old VB6 regsvr32 command. So using this we can have the .Net DLL sitting in the "c:\vb6\dll\folder" folder.



The only other point to note is that .Net will always check the GAC first before other locations. So if we have a .Net DLL in the GAC, then even if we register the same DLL somewhere else via the "/codebase" option, it will always use the GAC DLL. You can get around this by changing the version number on the DLL - it will always load the version with the highest number first.